安卓性能测试之cpu占用率统计方法总结

发表于:2018-6-14 11:16

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:隋胖胖LoveFat    来源:简书

  安卓性能测试的重要方面是对各项性能指标的采集和分析,如常见性能指标内存、cpu、电量、流量等,本文整理了cpu占有率统计方法和基本原理。
  安卓性能指标cpu主要关注两点:
  (1)cpu总体使用率(2)应用程序cpu占用率。
  cpu指标的查看方式有多种,最直接的就是android自带的DDMS可视化工具,也可以在IDE(Android Studio)的Monitor中实时查看。另外就是通过linux系统/proc/stat和/proc/<pid>/stat文件进行占用率的计算,也可以利用top命令或者dumpsys cupinfo等命令实时查看当前cpu情况。我们接下来详细看下每一种方法是如何查看和获得cpu占用率数据。
  一、DDMS等可视化工具
  我们可以通过安卓SDK自带的DDMS工具查看当前安卓应用程序的CPU和内存使用情况:

  另外也可以通过Android Studio自带的cpu Monitor来实时查看CPU使用情况:
  二、/proc/stat及/proc/<pid>/stat文件计算
  /proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为内核与进程提供通信的接口。用户和应用程序可以通过/proc得到系统的信息,并可以改变内核的某些参数。由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取/proc目录中的文件时,/proc文件系统是动态从系统内核读出所需信息并提交的。
  我们关注的安卓性能指标cpu关注的cpu总体使用率和应用程序cpu占用率主要与两个proc文件相关,分别是/proc/stat和/proc/<pid>/stat文件(pid是该应用程序的进程号)。
  1./proc/stat与cpu总体使用率的计算
  /proc/stat文件包含了所有CPU活动的信息,该文件中的所有值都是从系统启动开始累计到当前时刻的统计值。不同内核版本中该文件的格式可能不大一致,以下通过实例来说明数据该文件中各字段的含义。
  不管是mac还是windows,我们是无法直接看到/proc/stat文件的,我们可以使用adb shell命令进入Android shell命令模式(可以理解成进入当前设备的Linux Shell系统)进行查看,这时候模拟器或者手机要启用并且正确接通。
  进入目标设备的Linux Shell环境, 在该环境中可以执行一些Linux命令。如在Linux Shell环境中执行ps可以查看android设备中运行的所有进程。在Linux Shell环境中执行exit可以退出Linux Shell环境。
  1.1 /proc/stat文件分析
  我们在Linux Shell环境中使用cat命令查看/proc/stat文件。
  该文件显示的第一行就是cpu总体的使用情况,每列数值是后面几行逻辑处理器每列数据的相加值,可见该cpu共有4个逻辑处理器(Processor),我们也可以通过/proc/cpuinfo文件去查看该Linux内核的cpu具体情况。我们依据Linux用户手册详细看下第一行cpu数据的每列数据代表的含义。
  1)这些数值的单位都是 jiffies,jiffies 是内核中的一个全局变量,用来记录系统启动以来产生的节拍数,在 Linux 中,一个节拍大致可以理解为操作系统进程调度的最小时间片,不同的 Linux 系统内核这个值可能不同,通常在 1ms 到 10ms 之间。
  2)cpu 552 56 1496 266573 652 0 224 0 0 0
  **user(552) ** Time spent in user mode.
  从系统启动开始累积到当前时刻,处于用户态的运行时间,不包含 nice 值为负的进程。
  nice(56) Time spent in user mode with low priority(nice).
  系统启动开始累积到当前时刻,nice 值为负的进程所占用的 CPU 时间。Nice值是类UNIX操作系统中表示静态优先级的数值。 每个进程都有自己的静态优先级,优先级高的进程得以优先运行。
  system(1496) Time spent in system mode.
  从系统启动开始累积到当前时刻,处于核心态的运行时间。
  idle(266573) Time spent in the idle task.
  从系统启动开始累积到当前时刻,除 IO 等待时间以外的其他等待时间。
  **iowait(652) ** Time waiting for I/O to complete.
  从系统启动开始累积到当前时刻,IO 等待时间。(since 2.5.41,内核版本,下同)
  **irq(0) ** Time servicing interrupts.
  从系统启动开始累积到当前时刻,服务中断时间。(since 2.6.0-test4)
  softirq(224) Time servicing softirqs.
  从系统启动开始累积到当前时刻,软中断时间。(since 2.6.0-test4)
  **stealstolen(0) ** Stolen time, which is the time spent in other operating systems when running in a virtualized environment.
  从系统启动开始累积到当前时刻,在虚拟环境运行时花费在其他操作系统的时间。(since 2.6.11)
  **guest(0) ** Which is the time spent running a virtual CPU for guest operating systems under the control of the Linux kernel.
  从系统启动开始累积到当前时刻,在Linux内核控制下的操作系统虚拟cpu花费的时间。(since 2.6.24)
  guest_nice Time spent running a niced guest (virtual CPU for guest operating systems under the control of the Linux kernel).
  从系统启动开始累积到当前时刻,在Linux内核控制下的操作系统虚拟cpu花费在nice进程上的时间。(since Linux 2.6.33)
  PS:这里涉及到Linux内核的版本情况,我们可以通过uname -a和/proc/version来查看自己Android设备的Linux内核情况。下图为我的Android设备(genymotion模拟器)的Linux内核情况,可见我的内核版本是3.10版本,所以我查看到的/proc/stat文件里首行会显示所有的列数据。
  其他行数据可以通过继续Linux用户手册来查看。
  1.2 cpu总体使用率的计算
  cpu总的使用时间计算如下:
  totalCPUTime = user + nice + system + idle + iowait + irq + softirq + stealstolen + guest + guest_nice
  也就是/proc/stat的首行所有列数据的加和值。我们的目标是计算总体cpu使用率。
  通常我们都会选择较短的时间进行取样两次cpu数据1和2。
  常用cpu占用率方法可描述为:
  totalCPUrate = (非空闲cpu时间2-非空闲cpu时间1)/(cpu总时间2-cpu总时间1)x100%
  换成变量描述为:
  totalCPUrate =( (totalCPUTime2-idle2)-(totalCPUTime1-idle1))/(totalCPUTime2-totalCPUTime1)x100%
  那么根据/proc/stat文件可以得到cpu总体使用率的计算方法:
  1.采样两个足够短的时间间隔的cpu数据,分别记作t1、t2,其中t1、t2的结构均为:
  (user、nice、system、idle、iowait、irq、softirq、stealstolen、guest、guest_nice)的10元组;(当然这里依据Linux内核的不同有些数据可能没有,就不必计入)
  2.计算t1、t2总的cpu时间片totalCPUTime
  a) 把第一次的所有cpu10元组数据求和,得到totalCPUTime1;
  b) 把第二次的所有cpu10元组数据求和,得到totalCPUTime2;
  3.计算空闲时间idle
  cpu空闲时间对应第四列的数据
  a)获得第一次的idle数据,记为idle1
  b)获得第二次的idle数据,记为idle2
  4.计算cpu使用率
  totalCPUrate = ((totalCPUTime2-idle2)-(totalCPUTime1-idle1))/(totalCPUTime2-totalCPUTime1)x100%
  了解了整体的计算方法后,我们可以使用java代码进行简要实现。
  1.3 cpu总体使用率计算的java实现
  核心的计算方法抽取出来如下:(参考了腾讯GT的CPU数据实现方法)
  public String getCpuUsage() {
          double usage = 0.0;
          boolean initCpu = true;
          if (initCpu) {
              initCpu = false;
              RandomAccessFile reader = null;
                  reader = new RandomAccessFile("/proc/stat","r");
                  String load = reader.readLine();
                  String[] toks = load.split(" ");
                  o_idle = Double.parseDouble(toks[5]);
                  o_cpu = Double.parseDouble(toks[2])
                          + Double.parseDouble(toks[3])
                          + Double.parseDouble(toks[4])
                          + Double.parseDouble(toks[6])
                          + Double.parseDouble(toks[7])
                          + Double.parseDouble(toks[8])
                          + Double.parseDouble(toks[9]);
                  FileUtil.closeRandomAccessFile(reader);
          } else {
              RandomAccessFile reader = null;
                  reader = new RandomAccessFile("/proc/stat", "r");
                  String load;
                  load = reader.readLine();
                  String[] toks = load.split(" ");
                  double c_idle = Double.parseDouble(toks[5]);
                  double c_cpu = Double.parseDouble(toks[2])
                          + Double.parseDouble(toks[3])
                          + Double.parseDouble(toks[4])
                          + Double.parseDouble(toks[6])
                          + Double.parseDouble(toks[7])
                          + Double.parseDouble(toks[8]);
                          + Double.parseDouble(toks[9]);
                  if (0 != ((c_cpu + c_idle) - (o_cpu + o_idle))) {
                       usage = DoubleUtils.div((100.00 * ((c_cpu - o_cpu))),
                              ((c_cpu + c_idle) - (o_cpu + o_idle)), 2);
                  }
                  o_cpu = c_cpu;
                  o_idle = c_idle;
                  FileUtil.closeRandomAccessFile(reader);
           }
          return String.valueOf(usage) + "%";
      }```
  需要注意的是,上述代码只是一个核心算法的示例,真正实现的话还需要考虑Linux内核的不同导致取值范围的不同,另外计算出usage值为负或者为大于100的特殊情况处理。
  #### 2./proc/<pid>/stat与应用程序cpu占有率的计算
  /proc/<pid>/stat文件包含了某一进程所有的活动的信息,该文件中的所有值都是从系统启动开始累计到当前时刻的统计值。以下通过实例数据来说明该文件中各字段的含义。
  ##### 2.1 /proc/<pid>/stat文件分析
  我们在Linux Shell环境中使用cat命令查看/proc/<pid>/stat文件。我们可以先通过top命令获得目标应用的进程id。
  ![](http://upload-images.jianshu.io/upload_images/1430132-283d098b3127289e.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  这里我们的目标进程id是1378。这时候我们去内存中查看/proc/1378/stat文件:
  ![](http://upload-images.jianshu.io/upload_images/1430132-27d83e139a70bb10.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  根据[Linux用户手册](http://man7.org/linux/man-pages/man5/proc.5.html)我们可以看到/proc/1387/stat文件中各个字段代表的含义,其中与我们计算应用程序cpu占用率相关的数据主要有如下几个:
  >pid=1378                          进程号
  utime=1286                       该任务在用户态运行的时间,单位为jiffies
  stime=1854                      该任务在核心态运行的时间,单位为jiffies
  cutime=6                            所有已死线程在用户态运行的时间,单位为jiffies
  cstime=2                            所有已死在核心态运行的时间,单位为jiffies
  ##### 2.2 cpu总体使用率的计算
  应用程序cpu的使用时间计算如下:
  >processCPUTime = utime + stime,该值包括其所有线程的cpu时间。
  通常我们都会选择较短的时间进行取样两次总体cpu数据和应用程序cpu数据,分别记为1和2。常用应用程序cpu占用率方法可描述为:
  >processCPUrate = ( processCPUTime2 – processCPUTime1) / (totalCPUTime2 – totalCPUTime1) x100%
  那么根据/proc/stat文件和/proc/<pid>/stat文件可以得到某应用程序cpu占用率的计算方法:
  1.   **采样两个足够短的时间间隔的cpu数据和进程cpu数据,分别记作t1、t2,其中t1、t2的cpu数据结构均为:
  (user、nice、system、idle、iowait、irq、softirq、stealstolen、guest、guest_nice)的10元组;t1、t2的进程cpu数据结构为(utime、stime)的2元组。**
  2.  **计算t1、t2总的cpu时间totalCPUTime和进程cpu时间processCPUTime
  a)         把第一次的所有cpu10元组数据求和,得到totalCPUTime1;
  b)         把第二次的所有cpu10元组数据求和,得到totalCPUTime2;
  c) 把第一次的所有进程cpu4元组数据求和,得到processCPUTime1;
  d) 把第二次的所有进程cpu4元组数据求和,得到processCPUTime2**
  3. **计算应用程序cpu使用率
  processCPUrate = ( processCPUTime2 – processCPUTime1) / (totalCPUTime2 – totalCPUTime1) x100%**
  了解了整体的计算方法后,我们可以使用java代码进行简要实现。
  ##### 2.3 cpu总体使用率计算的java实现
  核心的计算方法抽取出来如下:(参考了腾讯GT的CPU数据实现方法)
  public String getProcessCpuUsage(int pid) {
      String result = "";
      String[] result1 = null ;
      String[] result2 = null;
      if (pid >= 0) {
          result1 = getProcessCpuAction(pid);
          if (null != result1) {
              pCpu = Double.parseDouble(result1[1])
                      + Double.parseDouble(result1[2]);
          }
          result2 = getCpuAction();
          if (null != result2) {
              aCpu = 0.0;
              for (int i = 2; i < result2.length; i++) {
                  aCpu += Double.parseDouble(result2[i]);
              }
          }
          double usage = 0.0;
          if ((aCpu - o_aCpu) != 0) {
              usage = DoubleUtils.div(((pCpu - o_pCpu) * 100.00),
                      (aCpu - o_aCpu), 2);
          }
          o_pCpu = pCpu;
          o_aCpu = aCpu;
          result = String.valueOf(usage) + "%";
      }
      p_jif = pCpu;
      return result;
  }```
  这里getProcessCpuAction(pid)和getCpuAction()分别处理/proc/<pid>/stat文件和/proc/stat文件,将应用程序的cpu和总体cpu数据分别保存在result1和result2变量中,然后进行计算。由于篇幅有限,处理文件的方法具体实现就不展开了。
  三、基本命令获取cpu使用情况
  除了可以从/proc文件系统中计算出cpu使用率外,我们也可以采用直观简单的命令来拿到cpu使用情况。当然了,不管是Linux还是Android系统提供的命令行查看,都是基于proc文件系统计算得到的,不同命令方法获得的数据可能会有差异,甚至与自己计算的也有不同,这是因为采用不同的计算方法和计算精度得到的,误差可接受。
  1.top命令
  top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。top是一个动态显示过程,即可以通过用户按键来不断刷新当前状态。如果在前台执行该命令,它将独占前台,直到用户终止该程序为止。top命令提供了实时的对系统处理器的状态监视。它将显示系统中CPU最“敏感”的任务列表。该命令可以按CPU使用、内存使用和执行时间对任务进行排序。
  使用top命令,可以让我们一眼直观地看到应用程序cpu占用率的情况。
  PID是进程的ID,是唯一的
  CPU不用说了,就是CPU占用比率
  VSS(Virtual Set Size)虚拟耗用内存(包含共享库占用的内存)
  PSS(Proportional Set Size)实际使用的物理内存(比例分配共享库占用的内存)
  RSS(Resident Set Size)实际耗用的物理内存(包含共享库占用的内存)
  USS(Unique Set Size)进程独自占用的物理内存(不包含共享库占用的内存)
  使用此方法的优点就是获取信息简单容易。使用次方法的缺点也一目了然,就是精确度不是很高。另外一些较为常用的top命令:
  可查看占用cpu最高的前10个程序(-t 显示进程名称,-s 按指定行排序,-n 在退出前刷新几次,-d 刷新间隔,-m 显示最大数量):
  top -m 10 -s cpu
  如果你想筛选出你自己的应用的话可以用下面这一命令:
  adb shell top -n 1| grep PackageName
  2.dumpsys cpuinfo命令
  Android提供的dumpsys工具可以用于查看感兴趣的系统服务信息与状态,dumpsys cpuinfo可以用来查看安卓系统当前的cpu使用情况。
  这个能够统计到所有应用的CPU占用信息,要比top -n 1获得的信息更加详细一些。
  缺点就是这种方法会有权限问题,ROOT了之后才能使用。
  以上便是安卓端统计cpu占用率的主要方法,拿到数据比较简单,难的是如何分析拿到的cpu数据,找到性能瓶颈点和优化点,这也是做安卓性能测试的重要方向。




上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号