• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define LOG_TAG "perfstatsd_cpu"
18 
19 #include "cpu_usage.h"
20 #include <android-base/stringprintf.h>
21 #include <android-base/strings.h>
22 
23 using namespace android::pixel::perfstatsd;
24 
25 static bool cDebug = false;
26 static constexpr char FMT_CPU_TOTAL[] =
27     "[CPU: %lld.%03llds][T:%.2f%%,U:%.2f%%,S:%.2f%%,IO:%.2f%%]";
28 static constexpr char TOP_HEADER[] = "[CPU_TOP]  PID, PROCESS_NAME, USR_TIME, SYS_TIME\n";
29 static constexpr char FMT_TOP_PROFILE[] = "%6.2f%%   %5d %s %" PRIu64 " %" PRIu64 "\n";
30 
CpuUsage(void)31 CpuUsage::CpuUsage(void) {
32     std::string procstat;
33     if (android::base::ReadFileToString("/proc/stat", &procstat)) {
34         std::istringstream stream(procstat);
35         std::string line;
36         while (getline(stream, line)) {
37             std::vector<std::string> fields = android::base::Split(line, " ");
38             if (fields[0].find("cpu") != std::string::npos && fields[0] != "cpu") {
39                 CpuData data;
40                 mPrevCoresUsage.push_back(data);
41             }
42         }
43     }
44     mCores = mPrevCoresUsage.size();
45     mProfileThreshold = CPU_USAGE_PROFILE_THRESHOLD;
46     mTopcount = TOP_PROCESS_COUNT;
47 }
48 
setOptions(const std::string & key,const std::string & value)49 void CpuUsage::setOptions(const std::string &key, const std::string &value) {
50     if (key == PROCPROF_THRESHOLD || key == CPU_DISABLED || key == CPU_DEBUG ||
51         key == CPU_TOPCOUNT) {
52         uint32_t val = 0;
53         if (!base::ParseUint(value, &val)) {
54             LOG_TO(SYSTEM, ERROR) << "Invalid value: " << value;
55             return;
56         }
57 
58         if (key == PROCPROF_THRESHOLD) {
59             mProfileThreshold = val;
60             LOG_TO(SYSTEM, INFO) << "set profile threshold " << mProfileThreshold;
61         } else if (key == CPU_DISABLED) {
62             mDisabled = (val != 0);
63             LOG_TO(SYSTEM, INFO) << "set disabled " << mDisabled;
64         } else if (key == CPU_DEBUG) {
65             cDebug = (val != 0);
66             LOG_TO(SYSTEM, INFO) << "set debug " << cDebug;
67         } else if (key == CPU_TOPCOUNT) {
68             mTopcount = val;
69             LOG_TO(SYSTEM, INFO) << "set top count " << mTopcount;
70         }
71     }
72 }
73 
profileProcess(std::string * out)74 void CpuUsage::profileProcess(std::string *out) {
75     // Read cpu usage per process and find the top ones
76     DIR *dir;
77     struct dirent *ent;
78     std::unordered_map<uint32_t, ProcData> procUsage;
79     std::priority_queue<ProcData, std::vector<ProcData>, ProcdataCompare> procList;
80     if ((dir = opendir("/proc/")) != NULL) {
81         while ((ent = readdir(dir)) != NULL) {
82             if (ent->d_type == DT_DIR) {
83                 std::string pidStr = ent->d_name;
84                 std::string::const_iterator it = pidStr.begin();
85                 while (it != pidStr.end() && isdigit(*it)) ++it;
86                 if (!pidStr.empty() && it == pidStr.end()) {
87                     std::string pidStat;
88                     if (android::base::ReadFileToString("/proc/" + pidStr + "/stat", &pidStat)) {
89                         std::vector<std::string> fields = android::base::Split(pidStat, " ");
90                         uint32_t pid = 0;
91                         uint64_t utime = 0;
92                         uint64_t stime = 0;
93                         uint64_t cutime = 0;
94                         uint64_t cstime = 0;
95 
96                         if (!base::ParseUint(fields[0], &pid) ||
97                             !base::ParseUint(fields[13], &utime) ||
98                             !base::ParseUint(fields[14], &stime) ||
99                             !base::ParseUint(fields[15], &cutime) ||
100                             !base::ParseUint(fields[16], &cstime)) {
101                             LOG_TO(SYSTEM, ERROR) << "Invalid proc data\n" << pidStat;
102                             continue;
103                         }
104                         std::string proc = fields[1];
105                         std::string name =
106                             proc.length() > 2 ? proc.substr(1, proc.length() - 2) : "";
107                         uint64_t user = utime + cutime;
108                         uint64_t system = stime + cstime;
109                         uint64_t totalUsage = user + system;
110 
111                         uint64_t diffUser = user - mPrevProcdata[pid].user;
112                         uint64_t diffSystem = system - mPrevProcdata[pid].system;
113                         uint64_t diffUsage = totalUsage - mPrevProcdata[pid].usage;
114 
115                         ProcData ldata;
116                         ldata.user = user;
117                         ldata.system = system;
118                         ldata.usage = totalUsage;
119                         procUsage[pid] = ldata;
120 
121                         float usageRatio = (float)(diffUsage * 100.0 / mDiffCpu);
122                         if (cDebug && usageRatio > 100) {
123                             LOG_TO(SYSTEM, INFO) << "pid: " << pid << " , ratio: " << usageRatio
124                                                  << " , prev usage: " << mPrevProcdata[pid].usage
125                                                  << " , cur usage: " << totalUsage
126                                                  << " , total cpu diff: " << mDiffCpu;
127                         }
128 
129                         ProcData data;
130                         data.pid = pid;
131                         data.name = name;
132                         data.usageRatio = usageRatio;
133                         data.user = diffUser;
134                         data.system = diffSystem;
135                         procList.push(data);
136                     }
137                 }
138             }
139         }
140         mPrevProcdata = std::move(procUsage);
141         out->append(TOP_HEADER);
142         for (uint32_t count = 0; !procList.empty() && count < mTopcount; count++) {
143             ProcData data = procList.top();
144             out->append(android::base::StringPrintf(FMT_TOP_PROFILE, data.usageRatio, data.pid,
145                                                     data.name.c_str(), data.user, data.system));
146             procList.pop();
147         }
148         closedir(dir);
149     } else {
150         LOG_TO(SYSTEM, ERROR) << "Fail to open /proc/";
151     }
152 }
153 
getOverallUsage(std::chrono::system_clock::time_point & now,std::string * out)154 void CpuUsage::getOverallUsage(std::chrono::system_clock::time_point &now, std::string *out) {
155     mDiffCpu = 0;
156     mTotalRatio = 0.0f;
157     std::string procStat;
158 
159     // Get overall cpu usage
160     if (android::base::ReadFileToString("/proc/stat", &procStat)) {
161         std::istringstream stream(procStat);
162         std::string line;
163         while (getline(stream, line)) {
164             std::vector<std::string> fields = android::base::Split(line, " ");
165             if (fields[0].find("cpu") != std::string::npos) {
166                 std::string cpuStr = fields[0];
167                 std::string core = cpuStr.length() > 3 ? cpuStr.substr(3, cpuStr.length() - 3) : "";
168                 uint64_t user = 0;
169                 uint64_t nice = 0;
170                 uint64_t system = 0;
171                 uint64_t idle = 0;
172                 uint64_t iowait = 0;
173                 uint64_t irq = 0;
174                 uint64_t softirq = 0;
175                 uint64_t steal = 0;
176 
177                 // cpu  6013 3243 6311 92390 517 693 319 0 0 0  <-- (fields[1] = "")
178                 // cpu0 558 139 568 12135 67 121 50 0 0 0
179                 uint32_t base = core.compare("") ? 1 : 2;
180 
181                 if (!base::ParseUint(fields[base], &user) ||
182                     !base::ParseUint(fields[base + 1], &nice) ||
183                     !base::ParseUint(fields[base + 2], &system) ||
184                     !base::ParseUint(fields[base + 3], &idle) ||
185                     !base::ParseUint(fields[base + 4], &iowait) ||
186                     !base::ParseUint(fields[base + 5], &irq) ||
187                     !base::ParseUint(fields[base + 6], &softirq) ||
188                     !base::ParseUint(fields[base + 7], &steal)) {
189                     LOG_TO(SYSTEM, ERROR) << "Invalid /proc/stat data\n" << line;
190                     continue;
191                 }
192 
193                 uint64_t cpuTime = user + nice + system + idle + iowait + irq + softirq + steal;
194                 uint64_t cpuUsage = cpuTime - idle - iowait;
195                 uint64_t userUsage = user + nice;
196 
197                 if (!core.compare("")) {
198                     uint64_t diffUsage = cpuUsage - mPrevUsage.cpuUsage;
199                     mDiffCpu = cpuTime - mPrevUsage.cpuTime;
200                     uint64_t diffUser = userUsage - mPrevUsage.userUsage;
201                     uint64_t diffSys = system - mPrevUsage.sysUsage;
202                     uint64_t diffIo = iowait - mPrevUsage.ioUsage;
203 
204                     mTotalRatio = (float)(diffUsage * 100.0 / mDiffCpu);
205                     float userRatio = (float)(diffUser * 100.0 / mDiffCpu);
206                     float sysRatio = (float)(diffSys * 100.0 / mDiffCpu);
207                     float ioRatio = (float)(diffIo * 100.0 / mDiffCpu);
208 
209                     if (cDebug) {
210                         LOG_TO(SYSTEM, INFO)
211                             << "prev total: " << mPrevUsage.cpuUsage
212                             << " , cur total: " << cpuUsage << " , diffusage: " << diffUsage
213                             << " , diffcpu: " << mDiffCpu << " , ratio: " << mTotalRatio;
214                     }
215 
216                     mPrevUsage.cpuUsage = cpuUsage;
217                     mPrevUsage.cpuTime = cpuTime;
218                     mPrevUsage.userUsage = userUsage;
219                     mPrevUsage.sysUsage = system;
220                     mPrevUsage.ioUsage = iowait;
221 
222                     auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now - mLast);
223                     out->append(android::base::StringPrintf(FMT_CPU_TOTAL, ms.count() / 1000,
224                                                             ms.count() % 1000, mTotalRatio,
225                                                             userRatio, sysRatio, ioRatio));
226                 } else {
227                     // calculate total cpu usage of each core
228                     uint32_t c = 0;
229                     if (!base::ParseUint(core, &c)) {
230                         LOG_TO(SYSTEM, ERROR) << "Invalid core: " << core;
231                         continue;
232                     }
233                     uint64_t diffUsage = cpuUsage - mPrevCoresUsage[c].cpuUsage;
234                     float coreTotalRatio = (float)(diffUsage * 100.0 / mDiffCpu);
235                     if (cDebug) {
236                         LOG_TO(SYSTEM, INFO)
237                             << "core " << c << " , prev cpu usage: " << mPrevCoresUsage[c].cpuUsage
238                             << " , cur cpu usage: " << cpuUsage << " , diffusage: " << diffUsage
239                             << " , difftotalcpu: " << mDiffCpu << " , ratio: " << coreTotalRatio;
240                     }
241                     mPrevCoresUsage[c].cpuUsage = cpuUsage;
242 
243                     char buf[64];
244                     sprintf(buf, "%.2f%%]", coreTotalRatio);
245                     out->append("[" + core + ":" + std::string(buf));
246                 }
247             }
248         }
249         out->append("\n");
250     } else {
251         LOG_TO(SYSTEM, ERROR) << "Fail to read /proc/stat";
252     }
253 }
254 
refresh(void)255 void CpuUsage::refresh(void) {
256     if (mDisabled)
257         return;
258 
259     std::string out;
260     std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
261 
262     getOverallUsage(now, &out);
263 
264     if (mTotalRatio >= mProfileThreshold) {
265         if (cDebug)
266             LOG_TO(SYSTEM, INFO) << "Total CPU usage over " << mProfileThreshold << "%";
267         std::string profileResult;
268         profileProcess(&profileResult);
269         if (mProfileProcess) {
270             // Dump top processes once met threshold continuously at least twice.
271             out.append(profileResult);
272         } else
273             mProfileProcess = true;
274     } else
275         mProfileProcess = false;
276 
277     append(now, out);
278     mLast = now;
279     if (cDebug) {
280         auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
281             std::chrono::system_clock::now() - now);
282         LOG_TO(SYSTEM, INFO) << "Took " << ms.count() << " ms, data bytes: " << out.length();
283     }
284 }
285