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(ERROR) << "Invalid value: " << value;
55 return;
56 }
57
58 if (key == PROCPROF_THRESHOLD) {
59 mProfileThreshold = val;
60 LOG(INFO) << "set profile threshold " << mProfileThreshold;
61 } else if (key == CPU_DISABLED) {
62 mDisabled = (val != 0);
63 LOG(INFO) << "set disabled " << mDisabled;
64 } else if (key == CPU_DEBUG) {
65 cDebug = (val != 0);
66 LOG(INFO) << "set debug " << cDebug;
67 } else if (key == CPU_TOPCOUNT) {
68 mTopcount = val;
69 LOG(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(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(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(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(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(INFO) << "prev total: " << mPrevUsage.cpuUsage
211 << " , cur total: " << cpuUsage << " , diffusage: " << diffUsage
212 << " , diffcpu: " << mDiffCpu << " , ratio: " << mTotalRatio;
213 }
214
215 mPrevUsage.cpuUsage = cpuUsage;
216 mPrevUsage.cpuTime = cpuTime;
217 mPrevUsage.userUsage = userUsage;
218 mPrevUsage.sysUsage = system;
219 mPrevUsage.ioUsage = iowait;
220
221 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now - mLast);
222 out->append(android::base::StringPrintf(FMT_CPU_TOTAL, ms.count() / 1000,
223 ms.count() % 1000, mTotalRatio,
224 userRatio, sysRatio, ioRatio));
225 } else {
226 // calculate total cpu usage of each core
227 uint32_t c = 0;
228 if (!base::ParseUint(core, &c)) {
229 LOG(ERROR) << "Invalid core: " << core;
230 continue;
231 }
232 uint64_t diffUsage = cpuUsage - mPrevCoresUsage[c].cpuUsage;
233 float coreTotalRatio = (float)(diffUsage * 100.0 / mDiffCpu);
234 if (cDebug) {
235 LOG(INFO) << "core " << c
236 << " , prev cpu usage: " << mPrevCoresUsage[c].cpuUsage
237 << " , cur cpu usage: " << cpuUsage
238 << " , diffusage: " << diffUsage
239 << " , difftotalcpu: " << mDiffCpu
240 << " , ratio: " << coreTotalRatio;
241 }
242 mPrevCoresUsage[c].cpuUsage = cpuUsage;
243
244 char buf[64];
245 sprintf(buf, "%.2f%%]", coreTotalRatio);
246 out->append("[" + core + ":" + std::string(buf));
247 }
248 }
249 }
250 out->append("\n");
251 } else {
252 LOG(ERROR) << "Fail to read /proc/stat";
253 }
254 }
255
refresh(void)256 void CpuUsage::refresh(void) {
257 if (mDisabled)
258 return;
259
260 std::string out;
261 std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
262
263 getOverallUsage(now, &out);
264
265 if (mTotalRatio >= mProfileThreshold) {
266 if (cDebug)
267 LOG(INFO) << "Total CPU usage over " << mProfileThreshold << "%";
268 std::string profileResult;
269 profileProcess(&profileResult);
270 if (mProfileProcess) {
271 // Dump top processes once met threshold continuously at least twice.
272 out.append(profileResult);
273 } else
274 mProfileProcess = true;
275 } else
276 mProfileProcess = false;
277
278 append(now, out);
279 mLast = now;
280 if (cDebug) {
281 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
282 std::chrono::system_clock::now() - now);
283 LOG(INFO) << "Took " << ms.count() << " ms, data bytes: " << out.length();
284 }
285 }
286