Android 高版本采集系統(tǒng)CPU使用率的方式

背景

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)致頁面慢啟動問題。

image.png
image.png

計(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)了一些問題:

  1. 在采樣周期內(nèi)(如果較短) time并不一定會被更新卡睦,有可能會經(jīng)過幾個采樣周期才會更新一次。 因此漱抓,如果沒有注意到這個場景表锻,在采樣周期內(nèi) 因?yàn)?cpu的 idle time值不變而認(rèn)為當(dāng)前cpu處于100%使用就是錯誤的,其實(shí) cpu 是 100% 空閑的乞娄,因此計(jì)算出的cpu使用率可能比實(shí)際高瞬逊。
  2. 同樣的問題, 因?yàn)閠ime 可能在幾個采樣周期后才更新,計(jì)算出的idle值 可能超過 這個采樣周期內(nèi)對應(yīng)的cputime仪或,因此如果沒有正確處理這個場景的确镊,這個采樣周期計(jì)算出的idle時(shí)間過大,cpu使用率比實(shí)際低范删。

上面的問題蕾域,如果采樣周期足夠長(比如10s采樣一次)通常不會有問題,有誤差也在可接受范圍內(nèi)到旦,如果是1s采樣一次就會偏差很大旨巷。下面簡單描述下這個問題的簡單處理方案:

  1. 每次采樣時(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)證)
  2. 針對第二個問題,如果采樣時(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) 如: ProcStatsUtilProcTimeInStateReader

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末狞贱,一起剝皮案震驚了整個濱河市刻获,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌斥滤,老刑警劉巖将鸵,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異佑颇,居然都是意外死亡顶掉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門挑胸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痒筒,“玉大人,你說我怎么就攤上這事茬贵〔就福” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵解藻,是天一觀的道長老充。 經(jīng)常有香客問我,道長螟左,這世上最難降的妖魔是什么啡浊? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮胶背,結(jié)果婚禮上巷嚣,老公的妹妹穿的比我還像新娘。我一直安慰自己钳吟,他們只是感情好廷粒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著红且,像睡著了一般坝茎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上暇番,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天景东,我揣著相機(jī)與錄音,去河邊找鬼奔誓。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的厨喂。 我是一名探鬼主播和措,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蜕煌!你這毒婦竟也來了派阱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤斜纪,失蹤者是張志新(化名)和其女友劉穎贫母,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盒刚,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腺劣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了因块。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片橘原。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖涡上,靈堂內(nèi)的尸體忽然破棺而出趾断,到底是詐尸還是另有隱情,我是刑警寧澤吩愧,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布芋酌,位于F島的核電站,受9級特大地震影響雁佳,放射性物質(zhì)發(fā)生泄漏脐帝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一甘穿、第九天 我趴在偏房一處隱蔽的房頂上張望腮恩。 院中可真熱鬧,春花似錦温兼、人聲如沸秸滴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荡含。三九已至,卻和暖如春届垫,著一層夾襖步出監(jiān)牢的瞬間释液,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工装处, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留误债,地道東北人浸船。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像寝蹈,于是被迫代替她去往敵國和親李命。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容