安卓性能測(cè)試的重要方面是對(duì)各項(xiàng)性能指標(biāo)的采集和分析,如常見(jiàn)性能指標(biāo)內(nèi)存、cpu、電量、流量等,本文整理了cpu占有率統(tǒng)計(jì)方法和基本原理。
安卓性能指標(biāo)cpu主要關(guān)注兩點(diǎn):
(1)cpu總體使用率(2)應(yīng)用程序cpu占用率。
cpu指標(biāo)的查看方式有多種,最直接的就是android自帶的DDMS可視化工具,也可以在IDE(Android Studio)的Monitor中實(shí)時(shí)查看。另外就是通過(guò)linux系統(tǒng)/proc/stat和/proc/<pid>/stat文件進(jìn)行占用率的計(jì)算,也可以利用top命令或者dumpsys cupinfo等命令實(shí)時(shí)查看當(dāng)前cpu情況。我們接下來(lái)詳細(xì)看下每一種方法是如何查看和獲得cpu占用率數(shù)據(jù)。
一、DDMS等可視化工具
我們可以通過(guò)安卓SDK自帶的DDMS工具查看當(dāng)前安卓應(yīng)用程序的CPU和內(nèi)存使用情況:
另外也可以通過(guò)Android Studio自帶的cpu Monitor來(lái)實(shí)時(shí)查看CPU使用情況:
二、/proc/stat及/proc/<pid>/stat文件計(jì)算
/proc文件系統(tǒng)是一個(gè)偽文件系統(tǒng),它只存在內(nèi)存當(dāng)中,而不占用外存空間。它以文件系統(tǒng)的方式為內(nèi)核與進(jìn)程提供通信的接口。用戶(hù)和應(yīng)用程序可以通過(guò)/proc得到系統(tǒng)的信息,并可以改變內(nèi)核的某些參數(shù)。由于系統(tǒng)的信息,如進(jìn)程,是動(dòng)態(tài)改變的,所以用戶(hù)或應(yīng)用程序讀取/proc目錄中的文件時(shí),/proc文件系統(tǒng)是動(dòng)態(tài)從系統(tǒng)內(nèi)核讀出所需信息并提交的。
我們關(guān)注的安卓性能指標(biāo)cpu關(guān)注的cpu總體使用率和應(yīng)用程序cpu占用率主要與兩個(gè)proc文件相關(guān),分別是/proc/stat和/proc/<pid>/stat文件(pid是該應(yīng)用程序的進(jìn)程號(hào))。
1./proc/stat與cpu總體使用率的計(jì)算
/proc/stat文件包含了所有CPU活動(dòng)的信息,該文件中的所有值都是從系統(tǒng)啟動(dòng)開(kāi)始累計(jì)到當(dāng)前時(shí)刻的統(tǒng)計(jì)值。不同內(nèi)核版本中該文件的格式可能不大一致,以下通過(guò)實(shí)例來(lái)說(shuō)明數(shù)據(jù)該文件中各字段的含義。
不管是mac還是windows,我們是無(wú)法直接看到/proc/stat文件的,我們可以使用adb shell命令進(jìn)入Android shell命令模式(可以理解成進(jìn)入當(dāng)前設(shè)備的Linux Shell系統(tǒng))進(jìn)行查看,這時(shí)候模擬器或者手機(jī)要啟用并且正確接通。
進(jìn)入目標(biāo)設(shè)備的Linux Shell環(huán)境, 在該環(huán)境中可以執(zhí)行一些Linux命令。如在Linux Shell環(huán)境中執(zhí)行ps可以查看android設(shè)備中運(yùn)行的所有進(jìn)程。在Linux Shell環(huán)境中執(zhí)行exit可以退出Linux Shell環(huán)境。
1.1 /proc/stat文件分析
我們?cè)贚inux Shell環(huán)境中使用cat命令查看/proc/stat文件。
該文件顯示的第一行就是cpu總體的使用情況,每列數(shù)值是后面幾行邏輯處理器每列數(shù)據(jù)的相加值,可見(jiàn)該cpu共有4個(gè)邏輯處理器(Processor),我們也可以通過(guò)/proc/cpuinfo文件去查看該Linux內(nèi)核的cpu具體情況。我們依據(jù)Linux用戶(hù)手冊(cè)詳細(xì)看下第一行cpu數(shù)據(jù)的每列數(shù)據(jù)代表的含義。
1)這些數(shù)值的單位都是 jiffies,jiffies 是內(nèi)核中的一個(gè)全局變量,用來(lái)記錄系統(tǒng)啟動(dòng)以來(lái)產(chǎn)生的節(jié)拍數(shù),在 Linux 中,一個(gè)節(jié)拍大致可以理解為操作系統(tǒng)進(jìn)程調(diào)度的最小時(shí)間片,不同的 Linux 系統(tǒng)內(nèi)核這個(gè)值可能不同,通常在 1ms 到 10ms 之間。
2)cpu 552 56 1496 266573 652 0 224 0 0 0
**user(552) ** Time spent in user mode.
從系統(tǒng)啟動(dòng)開(kāi)始累積到當(dāng)前時(shí)刻,處于用戶(hù)態(tài)的運(yùn)行時(shí)間,不包含 nice 值為負(fù)的進(jìn)程。
nice(56) Time spent in user mode with low priority(nice).
系統(tǒng)啟動(dòng)開(kāi)始累積到當(dāng)前時(shí)刻,nice 值為負(fù)的進(jìn)程所占用的 CPU 時(shí)間。Nice值是類(lèi)UNIX操作系統(tǒng)中表示靜態(tài)優(yōu)先級(jí)的數(shù)值。 每個(gè)進(jìn)程都有自己的靜態(tài)優(yōu)先級(jí),優(yōu)先級(jí)高的進(jìn)程得以?xún)?yōu)先運(yùn)行。
system(1496) Time spent in system mode.
從系統(tǒng)啟動(dòng)開(kāi)始累積到當(dāng)前時(shí)刻,處于核心態(tài)的運(yùn)行時(shí)間。
idle(266573) Time spent in the idle task.
從系統(tǒng)啟動(dòng)開(kāi)始累積到當(dāng)前時(shí)刻,除 IO 等待時(shí)間以外的其他等待時(shí)間。
**iowait(652) ** Time waiting for I/O to complete.
從系統(tǒng)啟動(dòng)開(kāi)始累積到當(dāng)前時(shí)刻,IO 等待時(shí)間。(since 2.5.41,內(nèi)核版本,下同)
**irq(0) ** Time servicing interrupts.
從系統(tǒng)啟動(dòng)開(kāi)始累積到當(dāng)前時(shí)刻,服務(wù)中斷時(shí)間。(since 2.6.0-test4)
softirq(224) Time servicing softirqs.
從系統(tǒng)啟動(dòng)開(kāi)始累積到當(dāng)前時(shí)刻,軟中斷時(shí)間。(since 2.6.0-test4)
**stealstolen(0) ** Stolen time, which is the time spent in other operating systems when running in a virtualized environment.
從系統(tǒng)啟動(dòng)開(kāi)始累積到當(dāng)前時(shí)刻,在虛擬環(huán)境運(yùn)行時(shí)花費(fèi)在其他操作系統(tǒng)的時(shí)間。(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.
從系統(tǒng)啟動(dòng)開(kāi)始累積到當(dāng)前時(shí)刻,在Linux內(nèi)核控制下的操作系統(tǒng)虛擬cpu花費(fèi)的時(shí)間。(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).
從系統(tǒng)啟動(dòng)開(kāi)始累積到當(dāng)前時(shí)刻,在Linux內(nèi)核控制下的操作系統(tǒng)虛擬cpu花費(fèi)在nice進(jìn)程上的時(shí)間。(since Linux 2.6.33)
PS:這里涉及到Linux內(nèi)核的版本情況,我們可以通過(guò)uname -a和/proc/version來(lái)查看自己Android設(shè)備的Linux內(nèi)核情況。下圖為我的Android設(shè)備(genymotion模擬器)的Linux內(nèi)核情況,可見(jiàn)我的內(nèi)核版本是3.10版本,所以我查看到的/proc/stat文件里首行會(huì)顯示所有的列數(shù)據(jù)。
其他行數(shù)據(jù)可以通過(guò)繼續(xù)Linux用戶(hù)手冊(cè)來(lái)查看。
1.2 cpu總體使用率的計(jì)算
cpu總的使用時(shí)間計(jì)算如下:
totalCPUTime = user + nice + system + idle + iowait + irq + softirq + stealstolen + guest + guest_nice
也就是/proc/stat的首行所有列數(shù)據(jù)的加和值。我們的目標(biāo)是計(jì)算總體cpu使用率。
通常我們都會(huì)選擇較短的時(shí)間進(jìn)行取樣兩次cpu數(shù)據(jù)1和2。
常用cpu占用率方法可描述為:
totalCPUrate = (非空閑cpu時(shí)間2-非空閑cpu時(shí)間1)/(cpu總時(shí)間2-cpu總時(shí)間1)x100%
換成變量描述為:
totalCPUrate =( (totalCPUTime2-idle2)-(totalCPUTime1-idle1))/(totalCPUTime2-totalCPUTime1)x100%
那么根據(jù)/proc/stat文件可以得到cpu總體使用率的計(jì)算方法:
- 采樣兩個(gè)足夠短的時(shí)間間隔的cpu數(shù)據(jù),分別記作t1、t2,其中t1、t2的結(jié)構(gòu)均為:
(user、nice、system、idle、iowait、irq、softirq、stealstolen、guest、guest_nice)的10元組;(當(dāng)然這里依據(jù)Linux內(nèi)核的不同有些數(shù)據(jù)可能沒(méi)有,就不必計(jì)入) - 計(jì)算t1、t2總的cpu時(shí)間片totalCPUTime
a) 把第一次的所有cpu10元組數(shù)據(jù)求和,得到totalCPUTime1;
b) 把第二次的所有cpu10元組數(shù)據(jù)求和,得到totalCPUTime2; - 計(jì)算空閑時(shí)間idle
cpu空閑時(shí)間對(duì)應(yīng)第四列的數(shù)據(jù)
a)獲得第一次的idle數(shù)據(jù),記為idle1
b)獲得第二次的idle數(shù)據(jù),記為idle2 - 計(jì)算cpu使用率
totalCPUrate = ((totalCPUTime2-idle2)-(totalCPUTime1-idle1))/(totalCPUTime2-totalCPUTime1)x100%
了解了整體的計(jì)算方法后,我們可以使用java代碼進(jìn)行簡(jiǎn)要實(shí)現(xiàn)。
1.3 cpu總體使用率計(jì)算的java實(shí)現(xiàn)
核心的計(jì)算方法抽取出來(lái)如下:(參考了騰訊GT的CPU數(shù)據(jù)實(shí)現(xiàn)方法)
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) + "%";
}```
需要注意的是,上述代碼只是一個(gè)核心算法的示例,真正實(shí)現(xiàn)的話還需要考慮Linux內(nèi)核的不同導(dǎo)致取值范圍的不同,另外計(jì)算出usage值為負(fù)或者為大于100的特殊情況處理。
#### 2./proc/<pid>/stat與應(yīng)用程序cpu占有率的計(jì)算
/proc/<pid>/stat文件包含了某一進(jìn)程所有的活動(dòng)的信息,該文件中的所有值都是從系統(tǒng)啟動(dòng)開(kāi)始累計(jì)到當(dāng)前時(shí)刻的統(tǒng)計(jì)值。以下通過(guò)實(shí)例數(shù)據(jù)來(lái)說(shuō)明該文件中各字段的含義。
##### 2.1 /proc/<pid>/stat文件分析
我們?cè)贚inux Shell環(huán)境中使用cat命令查看/proc/<pid>/stat文件。我們可以先通過(guò)top命令獲得目標(biāo)應(yīng)用的進(jìn)程id。

這里我們的目標(biāo)進(jìn)程id是1378。這時(shí)候我們?nèi)?nèi)存中查看/proc/1378/stat文件:

根據(jù)[Linux用戶(hù)手冊(cè)](http://man7.org/linux/man-pages/man5/proc.5.html)我們可以看到/proc/1387/stat文件中各個(gè)字段代表的含義,其中與我們計(jì)算應(yīng)用程序cpu占用率相關(guān)的數(shù)據(jù)主要有如下幾個(gè):
>pid=1378 進(jìn)程號(hào)
utime=1286 該任務(wù)在用戶(hù)態(tài)運(yùn)行的時(shí)間,單位為jiffies
stime=1854 該任務(wù)在核心態(tài)運(yùn)行的時(shí)間,單位為jiffies
cutime=6 所有已死線程在用戶(hù)態(tài)運(yùn)行的時(shí)間,單位為jiffies
cstime=2 所有已死在核心態(tài)運(yùn)行的時(shí)間,單位為jiffies
##### 2.2 cpu總體使用率的計(jì)算
應(yīng)用程序cpu的使用時(shí)間計(jì)算如下:
>processCPUTime = utime + stime,該值包括其所有線程的cpu時(shí)間。
通常我們都會(huì)選擇較短的時(shí)間進(jìn)行取樣兩次總體cpu數(shù)據(jù)和應(yīng)用程序cpu數(shù)據(jù),分別記為1和2。常用應(yīng)用程序cpu占用率方法可描述為:
>processCPUrate = ( processCPUTime2 – processCPUTime1) / (totalCPUTime2 – totalCPUTime1) x100%
那么根據(jù)/proc/stat文件和/proc/<pid>/stat文件可以得到某應(yīng)用程序cpu占用率的計(jì)算方法:
1. **采樣兩個(gè)足夠短的時(shí)間間隔的cpu數(shù)據(jù)和進(jìn)程cpu數(shù)據(jù),分別記作t1、t2,其中t1、t2的cpu數(shù)據(jù)結(jié)構(gòu)均為:
(user、nice、system、idle、iowait、irq、softirq、stealstolen、guest、guest_nice)的10元組;t1、t2的進(jìn)程cpu數(shù)據(jù)結(jié)構(gòu)為(utime、stime)的2元組。**
2. **計(jì)算t1、t2總的cpu時(shí)間totalCPUTime和進(jìn)程cpu時(shí)間processCPUTime
a) 把第一次的所有cpu10元組數(shù)據(jù)求和,得到totalCPUTime1;
b) 把第二次的所有cpu10元組數(shù)據(jù)求和,得到totalCPUTime2;
c) 把第一次的所有進(jìn)程cpu4元組數(shù)據(jù)求和,得到processCPUTime1;
d) 把第二次的所有進(jìn)程cpu4元組數(shù)據(jù)求和,得到processCPUTime2**
3. **計(jì)算應(yīng)用程序cpu使用率
processCPUrate = ( processCPUTime2 – processCPUTime1) / (totalCPUTime2 – totalCPUTime1) x100%**
了解了整體的計(jì)算方法后,我們可以使用java代碼進(jìn)行簡(jiǎn)要實(shí)現(xiàn)。
##### 2.3 cpu總體使用率計(jì)算的java實(shí)現(xiàn)
核心的計(jì)算方法抽取出來(lái)如下:(參考了騰訊GT的CPU數(shù)據(jù)實(shí)現(xiàn)方法)
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文件,將應(yīng)用程序的cpu和總體cpu數(shù)據(jù)分別保存在result1和result2變量中,然后進(jìn)行計(jì)算。由于篇幅有限,處理文件的方法具體實(shí)現(xiàn)就不展開(kāi)了。
三、基本命令獲取cpu使用情況
除了可以從/proc文件系統(tǒng)中計(jì)算出cpu使用率外,我們也可以采用直觀簡(jiǎn)單的命令來(lái)拿到cpu使用情況。當(dāng)然了,不管是Linux還是Android系統(tǒng)提供的命令行查看,都是基于proc文件系統(tǒng)計(jì)算得到的,不同命令方法獲得的數(shù)據(jù)可能會(huì)有差異,甚至與自己計(jì)算的也有不同,這是因?yàn)椴捎貌煌挠?jì)算方法和計(jì)算精度得到的,誤差可接受。
1.top命令
top命令是Linux下常用的性能分析工具,能夠?qū)崟r(shí)顯示系統(tǒng)中各個(gè)進(jìn)程的資源占用狀況,類(lèi)似于Windows的任務(wù)管理器。top是一個(gè)動(dòng)態(tài)顯示過(guò)程,即可以通過(guò)用戶(hù)按鍵來(lái)不斷刷新當(dāng)前狀態(tài)。如果在前臺(tái)執(zhí)行該命令,它將獨(dú)占前臺(tái),直到用戶(hù)終止該程序?yàn)橹埂op命令提供了實(shí)時(shí)的對(duì)系統(tǒng)處理器的狀態(tài)監(jiān)視。它將顯示系統(tǒng)中CPU最“敏感”的任務(wù)列表。該命令可以按CPU使用、內(nèi)存使用和執(zhí)行時(shí)間對(duì)任務(wù)進(jìn)行排序。
使用top命令,可以讓我們一眼直觀地看到應(yīng)用程序cpu占用率的情況。
PID是進(jìn)程的ID,是唯一的
CPU不用說(shuō)了,就是CPU占用比率
VSS(Virtual Set Size)虛擬耗用內(nèi)存(包含共享庫(kù)占用的內(nèi)存)
PSS(Proportional Set Size)實(shí)際使用的物理內(nèi)存(比例分配共享庫(kù)占用的內(nèi)存)
RSS(Resident Set Size)實(shí)際耗用的物理內(nèi)存(包含共享庫(kù)占用的內(nèi)存)
USS(Unique Set Size)進(jìn)程獨(dú)自占用的物理內(nèi)存(不包含共享庫(kù)占用的內(nèi)存)
使用此方法的優(yōu)點(diǎn)就是獲取信息簡(jiǎn)單容易。使用次方法的缺點(diǎn)也一目了然,就是精確度不是很高。另外一些較為常用的top命令:
可查看占用cpu最高的前10個(gè)程序(-t 顯示進(jìn)程名稱(chēng),-s 按指定行排序,-n 在退出前刷新幾次,-d 刷新間隔,-m 顯示最大數(shù)量):
top -m 10 -s cpu
如果你想篩選出你自己的應(yīng)用的話可以用下面這一命令:
adb shell top -n 1| grep PackageName
2.dumpsys cpuinfo命令
Android提供的dumpsys工具可以用于查看感興趣的系統(tǒng)服務(wù)信息與狀態(tài),dumpsys cpuinfo可以用來(lái)查看安卓系統(tǒng)當(dāng)前的cpu使用情況。
這個(gè)能夠統(tǒng)計(jì)到所有應(yīng)用的CPU占用信息,要比top -n 1獲得的信息更加詳細(xì)一些。
缺點(diǎn)就是這種方法會(huì)有權(quán)限問(wèn)題,ROOT了之后才能使用。
以上便是安卓端統(tǒng)計(jì)cpu占用率的主要方法,拿到數(shù)據(jù)比較簡(jiǎn)單,難的是如何分析拿到的cpu數(shù)據(jù),找到性能瓶頸點(diǎn)和優(yōu)化點(diǎn),這也是做安卓性能測(cè)試的重要方向。
參考文章:
http://www.blogjava.net/fjzag/articles/317773.html
http://liandongyang.coding.me/post/%E5%85%B3%E4%BA%8EAndroid%E7%9A%84CPU%E5%86%85%E5%AD%98%E7%9B%91%E6%8E%A7/http://blog.csdn.net/q838197181/article/details/50622498