背景
cpu 信息作為性能監(jiān)控四大常用指標(biāo) (cpu、內(nèi)存榛泛、網(wǎng)絡(luò)、磁盤)之一噩斟,對衡量設(shè)備性能曹锨,分析、確認(rèn)一些線上性能問題有著較為重要的作用剃允。
這里舉個應(yīng)用場景: 在得物APM平臺的頁面慢啟動監(jiān)控中沛简,樣本存在慢啟動對應(yīng)的函數(shù)火焰圖中并未提取到明顯耗時(shí)函數(shù)的情況,但通過分析其啟動階段的CPU使用率信息斥废、CPU頻率信息椒楣,發(fā)現(xiàn)其系統(tǒng)CPU使用率極高,而進(jìn)程牡肉、主線程CPU使用率并不高捧灰,啟動階段主要的資源使用是cpu 及 io, 因?yàn)槲创嬖贗O阻塞函數(shù),因此可以將此類問題歸因?yàn)橄到y(tǒng)CPU負(fù)載高统锤、主線程未分配到充足時(shí)間片來執(zhí)行啟動階段的函數(shù)毛俏,因此導(dǎo)致頁面慢啟動問題。
計(jì)算系統(tǒng)CPU使用率
/proc 及 /sys 偽文件系統(tǒng)
在介紹具體實(shí)現(xiàn)時(shí)饲窿,需要先了解 一些關(guān)鍵的偽文件系統(tǒng)(pseudo filesystems)煌寇,如procfs、sysfs逾雄。它們以文件的方式為內(nèi)核與進(jìn)程提供通信的接口阀溶,但其信息只存在于內(nèi)存當(dāng)中,而不占用外存空間嘲驾。 用戶和程序可以通過 讀取 這些文件下的 目錄及子目錄下的相關(guān)文件得到系統(tǒng)提供的一些資源信息,我們所知道的 ps淌哟、top、free等程序底層實(shí)現(xiàn)也是通過讀取這些fs來獲取系統(tǒng)的相關(guān)信息的辽故。
舉個例子徒仓,比如在/proc/cpuinfo 文件提供了每個cpu的相關(guān)信息(型號、緩存大小等)誊垢。
processor : 0
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x41
CPU architecture: 8
CPU variant : 0x2
CPU part : 0xd05
CPU revision : 0
processor : 1
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x41
CPU architecture: 8
CPU variant : 0x2
CPU part : 0xd05
CPU revision : 0
而/proc/stat文件提供了所有CPU的活動信息掉弛,該文件中的所有數(shù)值都是從系統(tǒng)啟動開始累計(jì)到當(dāng)前時(shí)刻
cpu 60174457 9663009 55832451 71782723 217812 9886952 2586380 0 0 0
cpu0 11196635 2001943 11939773 68088651 212914 2441300 665882 0 0 0
cpu1 11507874 2276717 11213445 436700 1056 2143323 556399 0 0 0
cpu2 11412154 2242954 11019498 440953 1110 2136361 523968 0 0 0
cpu3 4944155 744241 8900551 498496 1205 1987431 635147 0 0 0
cpu4 6428646 540160 3749526 564847 464 368445 63627 0 0 0
cpu5 6457629 570385 3797990 568408 499 369781 58906 0 0 0
cpu6 6400553 560137 3879073 567166 417 370833 57719 0 0 0
cpu7 1826808 726469 1332591 617497 144 69475 24730 0 0 0
intr 5891780796 0 0 0 1237792393 0 0 0 0 0 228957851 5575 4061 1310143 0 77591952 0 1928 1425 164383405 0 0 294267 0 17817217 14494461 90294 0 0 0 0 0 0 0 0 0 180421 41024658 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 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 1522 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 124 800439 0 0 0 0 0 0 0 0 0 0 0 0 47816585 614983 189532 0 0 0 0 0 0 0 0 0 0 0 0 2568146 62612 15144952 148716 211433 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 49109042 0 0 67340327 0 0 0 0 0 0 0 0 264827 32197 0 2 207 214 81540 48 2384 81 2165 19707 716458 0 2 0 2 355498 421 0 0 0 0 1788049 6956 2428 0 0 2007479 0 5997 0 0 0 0 0 0 0 10 0 0 0 0 0 0 0 0 0 3 673471 3 59 0 200861 1 6 11270883 0 0 1 0 1 0 1 0 1 0 1 0 1 42 9 10 0 0 10 1967 152466 3854 67 0 0 2354590 0 0 0 0 0 0 14536775 0 15033 142 395394 338959 60 903 12122483 1289409 0 0 0 0 0 0 32346 4617898 2602221 2081525 2177844 0 0 1140925 0 0 0 0 0 0 0 0 0 0 0 407084 46869118 32724648 785 2 0 0 0 0 0 0 0 0 0 0 0 0 4943944
ctxt 8547174056
btime 1658839741
processes 9876338
procs_running 1
procs_blocked 0
softirq 1499737914 1595873 498961882 9622280 70242366 145843255 0 15928568 411156254 37489 346349947
對于進(jìn)程來說症见,通過/proc/${pid}/ 目錄下提供的文件,可以獲取單個進(jìn)程的一些統(tǒng)計(jì)信息
[圖片上傳失敗...(image-4ad5dc-1661864540228)]
除了procfs,另一個常用的文件系統(tǒng)是 sysfs殃饿,其根目錄為/sys谋作。 sysfs 是在procfs之后引入的,它將很多原本存在于procfs的信息遷移到sysfs乎芳,sysfs被設(shè)計(jì)用來導(dǎo)出設(shè)備樹中呈現(xiàn)的信息遵蚜,這樣就不會使得 /procfs文件顯得混亂。se有一個procfs sysfs相關(guān)區(qū)別問題的討論奈惑,可以了解下: https://unix.stackexchange.com/questions/4884/what-is-the-difference-between-procfs-and-sysfs吭净。
在本文中,主要關(guān)注 /sys/devices/system/cpu/ 目錄下的信息肴甸,它提供了cpu的一些詳細(xì)配置及活動信息寂殉,如 cpu最小、最大頻率原在、cpu各頻率活動時(shí)間友扰、cpu idle累計(jì)時(shí)間等。
示例:讀取 cpu${index}/cpufreq/scaling_cur_freq 可以獲取某個cpu當(dāng)前的工作頻率
mars:/ $ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
1209600
Android 8.0及以下
在Android低版本設(shè)備中庶柿,可以通過讀取 /proc/stat 文件實(shí)現(xiàn)村怪, /proc/stat 內(nèi)容首行有8個數(shù)值, 分別提供了所有CPU在 用戶態(tài)(user)澳泵、用戶態(tài)-低優(yōu)先級(nice)实愚、內(nèi)核態(tài)(sys)、空閑態(tài)(idle)兔辅、io等待(iowait)、硬中斷(irq)击喂、軟中斷(softirq) 狀態(tài) 下的時(shí)間總和维苔,將這些值累加作為系統(tǒng)總的CPU時(shí)間(cpuTime),計(jì)算 iowait/cpuTime 為系統(tǒng)的CPU空閑率懂昂,1-cpu空閑率 及為cpu利用率 介时。注意這里的時(shí)間單位為 jiffies,通常一個jiffies 等于10ms凌彬。 在Android 系統(tǒng)下也可以通過Os.sysconf(OsConstants. _SC_CLK_TCK) 得到每秒的jiffies數(shù)沸柔。
[圖片上傳失敗...(image-4927d1-1661864540228)]
以下是解析 /proc/stat 獲取當(dāng)前最新cpuTime的示例代碼
private float getCPUTime() {
long cpuTime=0;
try {
if (mProcStatFile == null) {
mProcStatFile = new RandomAccessFile("/proc/stat", "r");
} else {
mProcStatFile.seek(0L);
}
String procStatString = mProcStatFile.readLine();
String procStats[] = procStatString.split(" ");
cpuTime = Long.parseLong(procStats[2]) + Long.parseLong(procStats[3])
+ Long.parseLong(procStats[4]) + Long.parseLong(procStats[5])
+ Long.parseLong(procStats[6]) + Long.parseLong(procStats[7])
+ Long.parseLong(procStats[8]);
return cpuTime
} catch (Exception e) {
}
return cpuTime;
}
Android 高版本實(shí)現(xiàn)方案
在Android 8.0以上版本,為了防止旁路攻擊(Side Channel Attack)铲敛,相關(guān)討論可見 https://issuetracker.google.com/issues/37140047, 普通應(yīng)用程序已經(jīng)無法訪問/proc/stat 文件褐澎,所以無法通過/proc/stat 的方式計(jì)算系統(tǒng)cpu利用率。
另外說明下伐蒋,部分線下性能監(jiān)控相關(guān)的開源庫 如Dokit 會在Android8.0以上的設(shè)備 通過執(zhí)行shell 命令 top -n 1 來直接獲取某個進(jìn)程CPU使用率信息工三,不過這種方式在高版本設(shè)備上也是無法使用的迁酸,得到的CPU使用率總是為0。
private float getCpuDataForO() {
java.lang.Process process = null;
try {
//調(diào)用shell 執(zhí)行 top -n 1
process = Runtime.getRuntime().exec("top -n 1");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
int cpuIndex = -1;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (TextUtils.isEmpty(line)) {
continue;
}
int tempIndex = getCPUIndex(line);
if (tempIndex != -1) {
cpuIndex = tempIndex;
continue;
}
if (line.startsWith(String.valueOf(Process.myPid()))) {
if (cpuIndex == -1) {
continue;
}
String[] param = line.split("\s+");
if (param.length <= cpuIndex) {
continue;
}
String cpu = param[cpuIndex];
if (cpu.endsWith("%")) {
cpu = cpu.substring(0, cpu.lastIndexOf("%"));
}
float rate = Float.parseFloat(cpu) / Runtime.getRuntime().availableProcessors();
return rate;
}
}
} catch (IOException e) {
//...
} finally {
//...
}
return 0;
}
計(jì)算系統(tǒng)cpu使用率關(guān)鍵是獲取cpu時(shí)間 及idle 時(shí)間俭正。下面介紹另一種獲取cputime 及 idletime的方式.
獲取cputime
在 /sys/devices/system/cpu/cpu[x]/cpufreq/stats/ 目錄下包含一些提供cpu頻率相關(guān)的統(tǒng)計(jì)信息, 關(guān)于該目錄下文件的具體說明可參考kernel文檔:https://www.kernel.org/doc/Documentation/cpu-freq/cpufreq-stats.txt,
knight-zxw:/ $ ls /sys/devices/system/cpu/cpu0/cpufreq/stats/
reset time_in_state total_trans trans_table
其中 time_in_state 提供了cpu 在每個頻率下的運(yùn)行時(shí)間 (單位為10ms)奸鬓,該文件內(nèi)容格式為多行文本,每行左側(cè)為頻率值掸读、右側(cè)為在該頻率下運(yùn)行的時(shí)間串远。
knight-zxw:/ $ cat /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state
300000 0
403200 0
499200 0
595200 0
691200 55897525
806400 2729597
902400 1315020
998400 1019161
1094400 11892764
1209600 3945629
1305600 6093815
1401600 1252173
1497600 1166578
1612800 1782695
1708800 978913
1804800 20824808
我們將文件內(nèi)容 每行右邊的數(shù)值累加 便是 cpuX 當(dāng)前的運(yùn)行時(shí)間。 因?yàn)橐粋€設(shè)備可能包含多個cpu 所以需要讀取多個文件進(jìn)行累加儿惫。
除了讀取 /sys/devices/system/cpu/cpu[X]/cpufreq/stats/time_in_state 文件抑淫,還有一個方式是讀取
/sys/devices/system/cpu/cpufreq/policy[X]/stats/time_in_state, 其文件內(nèi)容一致蕉拢。
關(guān)于 sysfs policy 相關(guān)內(nèi)容可閱讀linux 文檔:https://www.kernel.org/doc/html/v4.14/admin-guide/pm/cpufreq.html左敌。 這里簡單描述下 /sys/devices/system/cpu/cpufreq/policy[X]/ 表示一個cpu頻率策略,通常每個policy 控制多個cpu筷转,同時(shí)進(jìn)行頻率控制(這些cpu硬件配置一般也相同)筐喳。以我的8核手機(jī)設(shè)備為例(目前Android的主流設(shè)備一般都包含8個核心)催式,會提供 policy0、policy4避归、policy7 (通常分別控制 大中小核)荣月,每個policy控制的cpu可以通過讀取/sys/devices/system/cpu/cpufreq/policy[x]/affected_cpus獲得:
knight-zxw:/ $ cat /sys/devices/system/cpu/cpufreq/policy0/affected_cpus
0 1 2 3
該policy的最大頻率、最小頻率梳毙、當(dāng)前頻率 可以分別通過讀取scaling_max_freq哺窄、scaling_min_freq、scaling_cur_freq獲取账锹。 因?yàn)閜olicy下提供的 time_in_state 對應(yīng)的是多個cpu的萌业,因此讀取 policy的 time_in_state相對 之前的方式可以讀取更少的文件。
獲取 idleTime
同頻率相關(guān)的統(tǒng)計(jì)信息類似奸柬,在 /sys/devices/system/cpu/cpu[X]/cpuidle 下提供了每個cpu 在idle狀態(tài)下運(yùn)行相關(guān)的統(tǒng)計(jì)信息生年,具體信息見文檔:https://www.kernel.org/doc/Documentation/cpuidle/sysfs.txt。
cpuidle目錄下的子目錄通常包含 driver 廓奕、以及多個 state[x]文件夾抱婉。
knight-zxw:/ $ ls /sys/devices/system/cpu/cpu0/cpuidle/
driver state0 state1
這里的 state[x] 表示idle狀態(tài) 休眠的深度,x值越大表示休眠狀態(tài)越深桌粉,功耗越小蒸绩、但進(jìn)入和退出該狀態(tài)的成本也越大。 以不同狀態(tài)的延遲度為例铃肯,在我的設(shè)備上 state0的延遲度為43, 而state1 的延遲度為531患亿。
knight-zxw:/ $ cat /sys/devices/system/cpu/cpu0/cpuidle/state0/latency
43
knight-zxw:/ $ cat /sys/devices/system/cpu/cpu0/cpuidle/state1/latency
531
回到正文,/sys/devices/system/cpu/cpu7/cpuidle/state[x]/time 提供了 cpu 在 idle state[x] 下停留的時(shí)間缘薛,因此通過累加 /sys/devices/system/cpu/cpu[x]/cpuidle/state[x]/time 的值即可獲取 系統(tǒng)在idle狀態(tài)的運(yùn)行時(shí)間(注意 這里的時(shí)間單位是微秒)
knight-zxw:/ $ cat /sys/devices/system/cpu/cpu0/cpuidle/state0/time
429942749686
而在 /sys/devices/system/cpu/cpu1/cpuidle/state1/usage下 記錄了這個狀態(tài)進(jìn)入的次數(shù)
knight-zxw:/ $ cat /sys/devices/system/cpu/cpu1/cpuidle/state0/usage
555273877
idle 計(jì)算調(diào)整
上節(jié)講了idle的計(jì)算方式窍育,在測試過程中 馬上就發(fā)現(xiàn)了一些問題:
- 在采樣周期內(nèi)(如果較短) time并不一定會被更新卡睦,有可能會經(jīng)過幾個采樣周期才會更新一次。 因此漱抓,如果沒有注意到這個場景表锻,在采樣周期內(nèi) 因?yàn)?cpu的 idle time值不變而認(rèn)為當(dāng)前cpu處于100%使用就是錯誤的,其實(shí) cpu 是 100% 空閑的乞娄,因此計(jì)算出的cpu使用率可能比實(shí)際高瞬逊。
- 同樣的問題, 因?yàn)閠ime 可能在幾個采樣周期后才更新,計(jì)算出的idle值 可能超過 這個采樣周期內(nèi)對應(yīng)的cputime仪或,因此如果沒有正確處理這個場景的确镊,這個采樣周期計(jì)算出的idle時(shí)間過大,cpu使用率比實(shí)際低范删。
上面的問題蕾域,如果采樣周期足夠長(比如10s采樣一次)通常不會有問題,有誤差也在可接受范圍內(nèi)到旦,如果是1s采樣一次就會偏差很大旨巷。下面簡單描述下這個問題的簡單處理方案:
- 每次采樣時(shí),同時(shí)采集當(dāng)前cpu的頻率添忘,當(dāng)發(fā)現(xiàn)某個cpu在采樣間隔內(nèi) idle time時(shí)間沒有變化的時(shí)候采呐,判斷當(dāng)前cpu是否處于最高頻率下工作,如果是最高頻率則無需調(diào)整搁骑,如果不是則 調(diào)整idletime 為采樣周期的時(shí)間 (理論上也可以通過讀取 state[x]/usage 計(jì)算cpu 進(jìn)入idle狀態(tài)的次數(shù)來判斷斧吐,個人暫未驗(yàn)證)
- 針對第二個問題,如果采樣時(shí) 計(jì)算出的 CPU idle time 大于 采樣周期的時(shí)間仲器,則將idle 調(diào)整為 采樣周期的時(shí)間煤率,即認(rèn)為在這個周期內(nèi)該cpu完全處于 idle狀態(tài)。
上述調(diào)整的示例代碼如下
public fun getSysIdleDeltaTime(
allCpu: List<Cpu>,
intervalMills: Long,
): Long {
//采樣間隔 微妙
val realSampleIntervalMicros = intervalMills * 1000L;
// 返回的 采樣周期內(nèi)的 idle時(shí)間
var totalIdleDeltaTime = 0L
for (cpu in allCpu) {
//獲取cpu當(dāng)前最新的idle時(shí)間
val nowIdleTime = cpu.idleTime()
//獲取上一次記錄的該cpu idle時(shí)間
val lastIdleTime = lastCpuIdleTimes[cpu.cpuIndex]
lastCpuIdleTimes[cpu.cpuIndex] = nowIdleTime
//第一次調(diào)用娄周,只更新數(shù)據(jù)涕侈,直接跳過
if (lastIdleTime == null) {
continue
}
var deltaIdleTime = (nowIdleTime - lastIdleTime)
if (deltaIdleTime == 0L) { //間隔采樣區(qū)間內(nèi)idle時(shí)間為0, 判斷是CPU 100% use 還是 100% idle
//判斷當(dāng)前CPU是否處于基本滿頻運(yùn)行
var maxFreq = 0L
//當(dāng)前調(diào)頻頻率
val scalingCurFreq = cpu.cpuFreq.scalingCurFreq()
if (!allowReadScalingMaxFeqFile) {
maxFreq = cpu.cpuFreq.maxFreq()
} else {
try {
//讀取當(dāng)前的頻率
maxFreq = cpu.cpuFreq.scalingMaxFreq()
} catch (e: Exception) {
//部分機(jī)型出現(xiàn)過讀取失敗的問題,未確認(rèn)原因
allowReadScalingMaxFeqFile = false
maxFreq = cpu.cpuFreq.maxFreq()
}
}
//當(dāng)前是否運(yùn)行在最高頻
val isRunningAtMaxFreq = maxFreq == scalingCurFreq
if (!isRunningAtMaxFreq) {
deltaIdleTime = realSampleIntervalMicros
}
} else if ((deltaIdleTime) > realSampleIntervalMicros) {
//通常idle時(shí)間過長 基本是剛從idle狀態(tài)退出煤辨,此時(shí)只能容錯取采樣周期作為idle時(shí)長
//因此本次采樣周期的CPU使用率和實(shí)際情況有細(xì)微差別
deltaIdleTime = realSampleIntervalMicros
}
totalIdleDeltaTime += deltaIdleTime
}
return totalIdleDeltaTime / 1000;
}
計(jì)算 CPU SPEED
除了計(jì)算CPU利用率,我們也可以采集cpu frequency 統(tǒng)計(jì)CPU頻率相關(guān)的信息木张。
可以以cpu cluster為單位統(tǒng)計(jì)众辨,在 /sys/devices/system/cpu/cpufreq/policy[x]/ 包含以下文件或目錄
knight-zxw:/ $ ls /sys/devices/system/cpu/cpufreq/policy0/
affected_cpus cpuinfo_min_freq scaling_available_frequencies scaling_cur_freq scaling_max_freq schedutil
cpuinfo_cur_freq cpuinfo_transition_latency scaling_available_governors scaling_driver scaling_min_freq stats
cpuinfo_max_freq related_cpus scaling_boost_frequencies scaling_governor scaling_setspeed
這里 cpuinfo_xxx 文件表示這些cpu硬件上支持的頻率信息( 最小頻率、最大頻率舷礼、當(dāng)前頻率)鹃彻,而 scaling_xxx 表示當(dāng)前CPUFreq系統(tǒng)用相應(yīng)調(diào)頻驅(qū)動及策略進(jìn)行調(diào)節(jié)時(shí)所支持的頻率信息,scaling_driver 表示當(dāng)前的調(diào)頻驅(qū)動妻献,scaling_governor表示當(dāng)前調(diào)頻策略蛛株,比如目前常見的 schedutil governor团赁。
系統(tǒng)出于一些性能上考慮,普通應(yīng)用程序是無法讀取 cpuinfo_cur_freq文件 谨履,在Java層直接讀取該文件會拋出FileNotFoundException 欢摄,而 cpuinfo_min_freq 和 cpuinfo_max_freq 可以正常讀取,畢竟這些值是不變的笋粟。
因此計(jì)算cpu的當(dāng)前頻率使用情況 可以累加所有cpu的 scaling_cur_freq (單位為kHz)數(shù)值得出怀挠, cpu利用率百分比 可以通過 scaling_cur_freq/cpuinfo_max_freq 得出。
fun scalingMaxFreq(): Long {
return File(policyFile, "scaling_max_freq").readLong()
}
fun scalingCurFreq(): Long {
return File(policyFile, "scaling_cur_freq").readLong()
}
計(jì)算進(jìn)/線程CPU使用率
通過讀取/proc/{pid}/stat 文件可以獲取進(jìn)程的CPU使用信息, Android應(yīng)用當(dāng)前進(jìn)程的pid 可以通過Process.myPid()獲取害捕。 在我的Android 12版本設(shè)備下 /proc/${pid}/stat的文件內(nèi)容如下
14330 (uapp.apm.sample) S 845 845 0 0 -1 1077936448 34734 812 29 0 261 55 1 2 10 -10 41 0 181359922 7155191808 36467 18446744073709551615 1 1 0 0 0 0 4608 1 1073775868 0 0 0 17 1 0 0 0 0 0 0 0 0 0 0 0 0 0
因?yàn)椴煌腁ndroid系統(tǒng) 對應(yīng)linux內(nèi)核版本不同绿淋,因此該文件的內(nèi)容包含的信息也可能不同(一般是在后面新增一些信息),在linux3.5及以上一共包含52項(xiàng)信息。下面列出前25項(xiàng)信息的含義
按照順序其每個token表示的信息為:
(1) pid: 進(jìn)程ID
(2) comm: 以圓括號包裹的程序名尝盼, 超過TASK_COMM_LEN(通常為16個字符)的名稱會被截?cái)?/p>
(3) state: 進(jìn)程狀態(tài), R表示Running吞滞、S 表示Sleeping in an interruptible wait ,詳見 文檔 /proc/[pid]/stat 部分
(4) ppid: 父進(jìn)程ID
(5) pgrp: 進(jìn)程組ID
(6) session: 進(jìn)程會話組ID
(7) tty_nr: The controlling terminal of the process.
(8) tpgid: The ID of the foreground process group of the controlling terminal of the process.
(9) flags: 當(dāng)前進(jìn)程內(nèi)核標(biāo)識位
(10) minflt: 次要缺頁中斷次數(shù) (次要缺頁表是無需從磁盤加載內(nèi)存頁)
(11) cminflt: 當(dāng)前進(jìn)程等待子進(jìn)程的minflt
(12) majflt: 主要缺頁中斷次數(shù) (主要缺頁表是需要從磁盤加載內(nèi)存頁)
(13) cmajflt: 當(dāng)前進(jìn)程等待子進(jìn)程的majflt
(14) utime: 當(dāng)前進(jìn)程處于用戶態(tài)運(yùn)行的時(shí)間盾沫,單位為jiffies
(15) stime: 當(dāng)前進(jìn)程處于內(nèi)核內(nèi)運(yùn)行的時(shí)間裁赠,單位為jiffies
(16) cutime: 當(dāng)前進(jìn)程的所有子進(jìn)程(包括子進(jìn)程的子進(jìn)程)在內(nèi)核態(tài)執(zhí)行的時(shí)間
(17) cstime: 當(dāng)前進(jìn)程的所有子進(jìn)程(包括子進(jìn)程的子進(jìn)程)在內(nèi)核態(tài)執(zhí)行的時(shí)間
(18) priority: 動態(tài)優(yōu)先級, 值是由系統(tǒng)分析之后動態(tài)調(diào)整的,用戶不能直接修改
(19) nice: 靜態(tài)優(yōu)先級疮跑,nice值取值范圍[19,-20], 值越小表時(shí)優(yōu)先級越高
(20) num_threads: 線程個數(shù)
(21) itrealvalue: 內(nèi)核2.6.17后廢棄组贺,值恒為0
(22) starttime: 自系統(tǒng)啟動后的進(jìn)程創(chuàng)建時(shí)間
(23) vsize: 進(jìn)程的虛擬內(nèi)存大小,單位為bytes
(24) rss: 進(jìn)程獨(dú)占+共享庫的內(nèi)存頁數(shù)
(25) rsslim: rss大小上線
通過解析該文本 獲得 utime stime 計(jì)算 (utime+stime)/cputime 祖娘,即可得出進(jìn)程的CPU使用率失尖。
// 解析 procstat文件
fun readProcStatSummary(statFile: File): ProcStatSummary {
val procStatSummary = ProcStatSummary()
val statInfo = statFile.readText()
val segments = StringUtil.splitWorker(statInfo, ' ', false)
procStatSummary.pid = segments[0]
if (segments[1].endsWith(")")) {
procStatSummary.name = segments[1].substring(1, segments[1].length - 1)
}
procStatSummary.state = segments[2]
procStatSummary.utime = segments[13].toLong()
procStatSummary.stime = segments[14].toLong()
procStatSummary.cutime = segments[15].toLong()
procStatSummary.cstime = segments[16].toLong()
procStatSummary.nice = segments[18]
procStatSummary.numThreads = segments[19].toInt()
procStatSummary.vsize = segments[22].toLong()
return procStatSummary
}
// 通過 兩次采樣 計(jì)算采樣間隔內(nèi)的 進(jìn)程cpu使用率
fun calculateProcCpuUsage(prevProcStateSummary: ProcStatSummary, nowProcStateSummary: ProcStatSummary) {
procUsedCpuTimeMs = nowProcStateSummary.totalUsedCpuTimeMs - prevProcStateSummary.totalUsedCpuTimeMs
if (cpuTime > 0) {
procCpuUsage = procUsedCpuTimeMs.toFloat() / cpuTime
}
}
在/proc/[processId]/task 包含子線程相關(guān)的信息,在Java平臺每個Java Thread都對應(yīng)一個真實(shí)的系統(tǒng)線程渐苏,遍歷 task目錄掀潮,內(nèi)容如下:
knight-zxw:/proc/4348/task $ ls
4348 4357 4359 4361 4363 4365 4368 4383 4406 4444 4465 4477 4480 4482 4487 4490 4495 4501
4356 4358 4360 4362 4364 4367 4378 4386 4411 4463 4476 4478 4481 4484 4489 4491 4498
這里每個文件夾都對應(yīng)進(jìn)程4348創(chuàng)建的一個子線程,其名稱為線程的 系統(tǒng)thread id, 在 task/[tid]/目錄下同樣也包含 stat文件 記錄該線程資源使用相關(guān)的統(tǒng)計(jì)信息
2|mars:/proc/4348/task $ cat /proc/4348/task/4357/stat
4357 (perfetto_hprof_) S 845 845 0 0 -1 4194368 9 2670 0 1 0 0 5 21 0 -20 30 0 198399608 7024758784 35104 18446744073709551615 1 1 0 0 0 0 20996 1 1073775868 0 0 0 -1 3 0 0 0 0 0 0 0 0 0 0 0 0 0
一些注意事項(xiàng)
兼容性問題
不同的廠商機(jī)型可能存在一些權(quán)限問題琼富,因此在進(jìn)行 cpu使用率監(jiān)控模塊采樣線程運(yùn)行前仪吧,應(yīng)先測試相應(yīng)文件是否能正常讀取,如果不能正常讀取則判斷為當(dāng)前設(shè)備不支持鞠眉。 在APM系統(tǒng)中薯鼠,通常也是抽樣進(jìn)行監(jiān)控,因此部分設(shè)備不支持不影響整體度量指標(biāo)的有效度械蹋。
性能優(yōu)化
在對文件文本內(nèi)容進(jìn)行解析時(shí)取值時(shí)出皇,盡量不要采用正則匹配的方式 (比如調(diào)用 String.split(" ")), 比如使用StringTokenier 比 Stirng.split 分詞性能會好幾倍哗戈, 如果你需要采集所有的線程使用率信息郊艘,這個成本就會被放大了,因?yàn)榇笮虯PP 運(yùn)行時(shí)可能包含 幾百個線程 (各種三方庫內(nèi)部會創(chuàng)建線程、線程池)纱注。
在android 系統(tǒng)源碼中也存在 解析 procstat等文件的代碼畏浆,我們可以參考其實(shí)現(xiàn) 如: ProcStatsUtil、ProcTimeInStateReader