APM系列之三-原理篇-进程CPU峰值采集

前言

本篇是APM系列文章的第三篇,主要介绍如何通过一个第三方应用,去监控整个系统中所有应用进程的CPU占用,线程数量等信息,从而辅助排查问题。

文章开始之前,抛出2个问题:

1.为什么要进行采集CPU峰值?

2.直接使用TOP命令采集不可以吗?

第一个问题,采集应用的CPU峰值,可以辅助我们排查ANR问题,以及检测各个应用之间资源抢占的问题。

第二个问题,为什么不用TOP?为什么TOP采集的是瞬时值,比如0.01S的时间内突然CPU占用率很高,恰好又被TOP命令采集到,这种其实是不影响使用的,也不是问题,而使用TOP其实就存在这样的问题(TOP采集的是0.005S范围内的)。

其实这里还有第三个优势,那就是在APM中进行CPU采集,不依赖外部自动化框架,甚至可以应用于生产环境,要远比使用自动化测试框架执行脚本方便的多。

一.如何采集系统的CPU占用

了解如何采集系统CPU负载之前,我们需要先了解一下Linux的一个知识点。Linux中,内核层的进程相关的一些参数,会映射到外部文件上,其实就包括CPU。

APM系列之三-原理篇-进程CPU峰值采集_第1张图片

CPU的信息就在stat中,相关的信息如下:

cpu  68724093 7457530 63397397 925179093 133980 12530536 5032898 0 0 0
cpu0 10112649 219978 5476279 162917877 56663 902499 71777 0 0 0
cpu1 6708277 290737 10433269 152733544 20066 5946336 4106893 0 0 0
cpu2 7910158 363569 7767095 162047420 21765 1395025 165457 0 0 0
cpu3 7764109 362644 7439487 161488289 24479 2588136 188684 0 0 0
cpu4 18176591 3311604 16252285 142544691 5175 853398 313223 0 0 0
cpu5 18052306 2908995 16028979 143447269 5829 845139 186861 0 0 0
intr 9609584710 0 0 0 2763090998 0 240905789 7 113975428 88 321676 0 0 0 266 18095257 0 128 170362452 0 0 0 585219272 434 0 0 33 0 0 100 0 0 0 0 0 40580 11321182 32310 30406 45 7 1 291167 30377 77009 30399 2 2511542 31631 0 0 0 0 0 0 0 0 0 0 0 0 113969235 104206676 6 99 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 13 2 0 0 0 0 0 0 0 26050299 42922 0 0 0 0 2 0 0 0 20403273 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 12661077
ctxt 18927154346
btime 1697697895
processes 6541854
procs_running 1
procs_blocked 0
softirq 2632078945 756013040 411005607 55 251933491 0 0 20403721 422860516 0 769862515

第一行cpu自然自然指的是CPU的总体负载;

第2到7行指的是每个CPU的负载。

第10行btime指的是系统启动时间。

我们的关注点,自然是cpu的相关内容。

解释

0

cpu

cpu名

1

68724093

用户态占用的CPU时间片数量

2

7457530

3

63397397

内核态占用的CPU时间片数量

4

925179093

空闲的cpu时间片

5

133980

IO等待占用的CPU时间片数量

对于CPU来说,一秒有10个时间片,占用10个时间片就意味着占用一个CPU核1秒时间。

采集一段时间的CPU占用率,使用结束时的值减去初始值即可。

二.如何采集应用的CPU占用

同第一章一样,每个进程的相关信息,其实也被映射到proc文件夹下,如果想采集CPU的信息,则其文件位置为:proc/进程ID/stat

其内容如下:

27005 (chs.scenemanage) S 364 364 0 0 -1 1077952832 904400 31987 243 1 6046993 2110950 404 90 20 0 113 0 18317266 43353554944 87520 18446744073709551615 369542340608 369542358060 549678426064 0 0 0 4612 1 1073775864 0 0 0 17 5 0 0 0 0 0 369542470240 369542471680 370186252288 549678426720 549678426819 549678426819 549678428126 0

每个字段的含义:

第几列

名称

英文名

实例值

解释

0

进程PID

pid

27005

进程ID (pid)

1

进程名称

comm

chs.scenemanage

进程名,这里的进程名被截取了并不是完整的。

2

状态

state

S

R:运行, S:休眠, D:等待IO, T: 暂停, T:停止,Z:僵尸进程, X:死亡

3

父进程ID

ppid

364

4

进程组ID

pgrp

364

该任务所在的会话组ID

5

会话ID

session

0

6

控制终端

tty_nr

0

该任务的tty终端的设备号,INT(0/256)=主设备号,(0-主设备号)=次设备号

7

控制进程组

tpgid

-1

终端的进程组号,当前运行在该任务所在终端的前台任务(包括shell 应用程序)的PID。

8

内核调度优先级

flags

1077952832

进程标志位,查看该任务的特性。

9

拷贝次数

min_flt

904400

该任务不需要从硬盘拷数据而发生的缺页(次缺页)的次数。

10

缺页的次数目

cmin_flt

31987

累计的该任务的所有的waited-for进程曾经发生的次缺页的次数目。

11

主缺页次数

maj_flt

243

该任务需要从硬盘拷数据而发生的缺页(主缺页)的次数。

12

缺页次数

cmaj_flt

1

累计的该任务的所有的waited-for进程曾经发生的主缺页的次数目。

13

用户模式时间

utime

6046993

14

内核模式时间

stime

2110950

15

等待子进程的用户模式时间

cutime

404

16

等待子进程的内核模式时间

cstime

90

17

优先级

priority

20

18

nice 值

nice

0

19

线程数

num_threads

113

20

无用字段

itrealvalue

0

21

启动时间

starttime

18317266

单位毫秒

22

虚拟内存大小

vsize

43353554944

23

物理内存大小

rrs

87520

后面的简略

所以求一个进程在一个时间段的CPU占用率,只要拿结束时的CPU时间片数量,减掉开始时的时间片数量,即可得到这段时间内占用的CPU时间片数量。除以总的时间片数量,得到的就是CPU占用率。

三.存在的问题

按照第一章和第二章的结论,我们可以确实可以计算出一段时间内安卓系统和某个应用的CPU占用率,甚至还可以得到进程中的线程数量。

但是实际操作的过程中,也遇到一些问题。

问题1:proc/进程ID/stat文件无权限阅读

虽然其权限值为-r--r--r--。关掉SELinux就正常的,所以最后通过配置SELinux的白名单解决。

问题2:报错FileNotFoundException

报错内容如下:

11-02 13:48:04.658 27807 27879 W System.err: java.io.FileNotFoundException: proc/10796/stat (No such file or directory)
11-02 13:48:04.659 27807 27879 W System.err:  at java.io.FileInputStream.open0(Native Method)
11-02 13:48:04.659 27807 27879 W System.err:  at java.io.FileInputStream.open(FileInputStream.java:231)
11-02 13:48:04.659 27807 27879 W System.err:  at java.io.FileInputStream.(FileInputStream.java:165)
11-02 13:48:04.659 27807 27879 W System.err:  at com.xxxx.apmcommon.IOHelper.fromFileToIputStream(IOHelper.java:210)
11-02 13:48:04.659 27807 27879 W System.err:  at com.xxxx.apmcommon.IOHelper.readFile(IOHelper.java:36)
11-02 13:48:04.659 27807 27879 W System.err:  at com.xxxx.util.WatchUtils$Companion.readStatByPid2(WatchUtils.kt:90)
11-02 13:48:04.659 27807 27879 W System.err:  at com.xxxx.watch.watchcpu.CpuPresenter.startReadAppCpu(CpuPresenter.kt:30)
11-02 13:48:04.659 27807 27879 W System.err:  at com.xxxx.watch.watchcpu.GetTopBroadcast$GetTopRunnable.run(GetTopBroadcast.kt:87)
11-02 13:48:04.659 27807 27879 W System.err:  at android.os.Handler.handleCallback(Handler.java:873)
11-02 13:48:04.659 27807 27879 W System.err:  at android.os.Handler.dispatchMessage(Handler.java:99)
11-02 13:48:04.659 27807 27879 W System.err:  at android.os.Looper.loop(Looper.java:193)
11-02 13:48:04.659 27807 27879 W System.err:  at android.os.HandlerThread.run(HandlerThread.java:65)

即使关掉了SELinux,某些进程仍然会提示这样的错误,说明和SELinux无关。而实际上,这个文件是存在的。

这个问题到目前为止仍然没有解决,由于是某些极个别的设备上的问题,不影响使用,所以就暂且搁置这个问题了。

你可能感兴趣的:(#,安卓-性能优化和稳定性,java,服务器,linux,APM,安卓,CPU)