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_io"
18
19 #include "io_usage.h"
20 #include <android-base/parseint.h>
21 #include <android-base/stringprintf.h>
22 #include <android-base/strings.h>
23 #include <cutils/android_filesystem_config.h>
24 #include <inttypes.h>
25 #include <pwd.h>
26
27 using namespace android::pixel::perfstatsd;
28 static constexpr const char *UID_IO_STATS_PATH = "/proc/uid_io/stats";
29 static constexpr char FMT_STR_TOTAL_USAGE[] =
30 "[IO_TOTAL: %lld.%03llds] RD:%s WR:%s fsync:%" PRIu64 "\n";
31 static constexpr char STR_TOP_HEADER[] =
32 "[IO_TOP ] fg bytes, bg bytes,fgsyn,bgsyn : UID PKG_NAME\n";
33 static constexpr char FMT_STR_TOP_WRITE_USAGE[] =
34 "[W%d:%6.2f%%]%12" PRIu64 ",%12" PRIu64 ",%5" PRIu64 ",%5" PRIu64 " :%6u %s\n";
35 static constexpr char FMT_STR_TOP_READ_USAGE[] =
36 "[R%d:%6.2f%%]%12" PRIu64 ",%12" PRIu64 ",%5" PRIu64 ",%5" PRIu64 " :%6u %s\n";
37 static constexpr char FMT_STR_SKIP_TOP_READ[] = "(< %" PRIu64 "MB)skip RD";
38 static constexpr char FMT_STR_SKIP_TOP_WRITE[] = "(< %" PRIu64 "MB)skip WR";
39
40 static bool sOptDebug = false;
41
42 /* format number with comma
43 * Ex: 10000 => 10,000
44 */
formatNum(uint64_t x,char * str,int size)45 static bool formatNum(uint64_t x, char *str, int size) {
46 int len = snprintf(str, size, "%" PRIu64, x);
47 if (len + 1 > size) {
48 return false;
49 }
50 int extr = ((len - 1) / 3);
51 int endpos = len + extr;
52 if (endpos > size) {
53 return false;
54 }
55 uint32_t d = 0;
56 str[endpos] = 0;
57 for (int i = 0, j = endpos - 1; i < len; i++) {
58 d = x % 10;
59 x = x / 10;
60 str[j--] = '0' + d;
61 if (i % 3 == 2) {
62 if (j >= 0)
63 str[j--] = ',';
64 }
65 }
66 return true;
67 }
68
isAppUid(uint32_t uid)69 static bool isAppUid(uint32_t uid) {
70 if (uid >= AID_APP_START) {
71 return true;
72 }
73 return false;
74 }
75
getNewPids()76 std::vector<uint32_t> ProcPidIoStats::getNewPids() {
77 std::vector<uint32_t> newpids;
78 // Not exists in Previous
79 for (int i = 0, len = mCurrPids.size(); i < len; i++) {
80 if (std::find(mPrevPids.begin(), mPrevPids.end(), mCurrPids[i]) == mPrevPids.end()) {
81 newpids.push_back(mCurrPids[i]);
82 }
83 }
84 return newpids;
85 }
86
update(bool forceAll)87 void ProcPidIoStats::update(bool forceAll) {
88 ScopeTimer _debugTimer("update: /proc/pid/status for UID/Name mapping");
89 _debugTimer.setEnabled(sOptDebug);
90 if (forceAll) {
91 mPrevPids.clear();
92 } else {
93 mPrevPids = mCurrPids;
94 }
95 // Get current pid list
96 mCurrPids.clear();
97 DIR *dir;
98 struct dirent *ent;
99 if ((dir = opendir("/proc/")) == NULL) {
100 LOG(ERROR) << "failed on opendir '/proc/'";
101 return;
102 }
103 while ((ent = readdir(dir)) != NULL) {
104 if (ent->d_type == DT_DIR) {
105 uint32_t pid;
106 if (android::base::ParseUint(ent->d_name, &pid)) {
107 mCurrPids.push_back(pid);
108 }
109 }
110 }
111 std::vector<uint32_t> newpids = getNewPids();
112 // update mUidNameMapping only for new pids
113 for (int i = 0, len = newpids.size(); i < len; i++) {
114 uint32_t pid = newpids[i];
115 if (sOptDebug > 1)
116 LOG(INFO) << i << ".";
117 std::string buffer;
118 if (!android::base::ReadFileToString("/proc/" + std::to_string(pid) + "/status", &buffer)) {
119 if (sOptDebug)
120 LOG(INFO) << "/proc/" << std::to_string(pid) << "/status"
121 << ": ReadFileToString failed (process died?)";
122 continue;
123 }
124 // --- Find Name ---
125 size_t s = buffer.find("Name:");
126 if (s == std::string::npos) {
127 continue;
128 }
129 s += std::strlen("Name:");
130 // find the pos of next word
131 while (buffer[s] && isspace(buffer[s])) s++;
132 if (buffer[s] == 0) {
133 continue;
134 }
135 size_t e = s;
136 // find the end pos of the word
137 while (buffer[e] && !std::isspace(buffer[e])) e++;
138 std::string pname(buffer, s, e - s);
139
140 // --- Find Uid ---
141 s = buffer.find("\nUid:", e);
142 if (s == std::string::npos) {
143 continue;
144 }
145 s += std::strlen("\nUid:");
146 // find the pos of next word
147 while (buffer[s] && isspace(buffer[s])) s++;
148 if (buffer[s] == 0) {
149 continue;
150 }
151 e = s;
152 // find the end pos of the word
153 while (buffer[e] && !std::isspace(buffer[e])) e++;
154 std::string strUid(buffer, s, e - s);
155
156 if (sOptDebug > 1)
157 LOG(INFO) << "(pid, name, uid)=(" << pid << ", " << pname << ", " << strUid << ")"
158 << std::endl;
159 uint32_t uid = (uint32_t)std::stoi(strUid);
160 mUidNameMapping[uid] = pname;
161 }
162 }
163
getNameForUid(uint32_t uid,std::string * name)164 bool ProcPidIoStats::getNameForUid(uint32_t uid, std::string *name) {
165 if (mUidNameMapping.find(uid) != mUidNameMapping.end()) {
166 *name = mUidNameMapping[uid];
167 return true;
168 }
169 return false;
170 }
171
updateTopRead(UserIo usage)172 void IoStats::updateTopRead(UserIo usage) {
173 UserIo tmp;
174 for (int i = 0, len = IO_TOP_MAX; i < len; i++) {
175 if (usage.sumRead() > mReadTop[i].sumRead()) {
176 // if new read > old read, then swap values
177 tmp = mReadTop[i];
178 mReadTop[i] = usage;
179 usage = tmp;
180 }
181 }
182 }
183
updateTopWrite(UserIo usage)184 void IoStats::updateTopWrite(UserIo usage) {
185 UserIo tmp;
186 for (int i = 0, len = IO_TOP_MAX; i < len; i++) {
187 if (usage.sumWrite() > mWriteTop[i].sumWrite()) {
188 // if new write > old write, then swap values
189 tmp = mWriteTop[i];
190 mWriteTop[i] = usage;
191 usage = tmp;
192 }
193 }
194 }
195
updateUnknownUidList()196 void IoStats::updateUnknownUidList() {
197 if (!mUnknownUidList.size()) {
198 return;
199 }
200 ScopeTimer _debugTimer("update overall UID/Name");
201 _debugTimer.setEnabled(sOptDebug);
202 mProcIoStats.update(false);
203 for (uint32_t i = 0, len = mUnknownUidList.size(); i < len; i++) {
204 uint32_t uid = mUnknownUidList[i];
205 if (isAppUid(uid)) {
206 // Get IO throughput for App processes
207 std::string pname;
208 if (!mProcIoStats.getNameForUid(uid, &pname)) {
209 if (sOptDebug)
210 LOG(WARNING) << "unable to find App uid:" << uid;
211 continue;
212 }
213 mUidNameMap[uid] = pname;
214 } else {
215 // Get IO throughput for system/native processes
216 passwd *usrpwd = getpwuid(uid);
217 if (!usrpwd) {
218 if (sOptDebug)
219 LOG(WARNING) << "unable to find uid:" << uid << " by getpwuid";
220 continue;
221 }
222 mUidNameMap[uid] = std::string(usrpwd->pw_name);
223 if (std::find(mUnknownUidList.begin(), mUnknownUidList.end(), uid) !=
224 mUnknownUidList.end()) {
225 }
226 }
227 mUnknownUidList.erase(std::remove(mUnknownUidList.begin(), mUnknownUidList.end(), uid),
228 mUnknownUidList.end());
229 }
230
231 if (sOptDebug && mUnknownUidList.size() > 0) {
232 std::stringstream msg;
233 msg << "Some UID/Name can't be retrieved: ";
234 for (const auto &i : mUnknownUidList) {
235 msg << i << ", ";
236 }
237 LOG(WARNING) << msg.str();
238 }
239 mUnknownUidList.clear();
240 }
241
calcIncrement(const std::unordered_map<uint32_t,UserIo> & data)242 std::unordered_map<uint32_t, UserIo> IoStats::calcIncrement(
243 const std::unordered_map<uint32_t, UserIo> &data) {
244 std::unordered_map<uint32_t, UserIo> diffs;
245 for (const auto &it : data) {
246 const UserIo &d = it.second;
247 // If data not existed, copy one, else calculate the increment.
248 if (mPrevious.find(d.uid) == mPrevious.end()) {
249 diffs[d.uid] = d;
250 } else {
251 diffs[d.uid] = d - mPrevious[d.uid];
252 }
253 // If uid not existed in UidNameMap, then add into unknown list
254 if ((diffs[d.uid].sumRead() || diffs[d.uid].sumWrite()) &&
255 mUidNameMap.find(d.uid) == mUidNameMap.end()) {
256 mUnknownUidList.push_back(d.uid);
257 }
258 }
259 // update Uid/Name mapping for dump()
260 updateUnknownUidList();
261 return diffs;
262 }
263
calcAll(std::unordered_map<uint32_t,UserIo> && data)264 void IoStats::calcAll(std::unordered_map<uint32_t, UserIo> &&data) {
265 // if mList == mNow, it's in init state.
266 if (mLast == mNow) {
267 mPrevious = std::move(data);
268 mLast = mNow;
269 mNow = std::chrono::system_clock::now();
270 mProcIoStats.update(true);
271 for (const auto &d : data) {
272 mUnknownUidList.push_back(d.first);
273 }
274 updateUnknownUidList();
275 return;
276 }
277 mLast = mNow;
278 mNow = std::chrono::system_clock::now();
279
280 // calculate incremental IO throughput
281 std::unordered_map<uint32_t, UserIo> amounts = calcIncrement(data);
282 // assign current data to Previous for next calculating
283 mPrevious = std::move(data);
284 // Reset Total and Tops
285 mTotal.reset();
286 for (int i = 0, len = IO_TOP_MAX; i < len; i++) {
287 mReadTop[i].reset();
288 mWriteTop[i].reset();
289 }
290 for (const auto &it : amounts) {
291 const UserIo &d = it.second;
292 // Add into total
293 mTotal = mTotal + d;
294 // Check if it's top
295 updateTopRead(d);
296 updateTopWrite(d);
297 }
298 }
299
300 /* Dump IO usage (Sample Log)
301 *
302 * [IO_TOTAL: 10.160s] RD:371,703,808 WR:15,929,344 fsync:567
303 * [TOP Usage ] fg bytes, bg bytes,fgsyn,bgsyn : UID NAME
304 * [R1: 33.99%] 0, 73240576, 0, 240 : 10016 .android.gms.ui
305 * [R2: 28.34%] 16039936, 45027328, 1, 21 : 10082 -
306 * [R3: 16.54%] 11243520, 24395776, 0, 25 : 10055 -
307 * [R4: 10.93%] 22241280, 1318912, 0, 1 : 10123 oid.apps.photos
308 * [R5: 10.19%] 21528576, 421888, 23, 20 : 10061 android.vending
309 * [W1: 58.19%] 0, 7655424, 0, 240 : 10016 .android.gms.ui
310 * [W2: 17.03%] 1265664, 974848, 38, 45 : 10069 -
311 * [W3: 11.30%] 1486848, 0, 58, 0 : 1000 system
312 * [W4: 8.13%] 667648, 401408, 23, 20 : 10061 android.vending
313 * [W5: 5.35%] 0, 704512, 0, 25 : 10055 -
314 *
315 */
dump(std::stringstream * output)316 bool IoStats::dump(std::stringstream *output) {
317 std::stringstream &out = (*output);
318
319 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(mNow - mLast);
320 char readTotal[32];
321 char writeTotal[32];
322 if (!formatNum(mTotal.sumRead(), readTotal, 32)) {
323 LOG(ERROR) << "formatNum buffer size is too small for read: " << mTotal.sumRead();
324 }
325 if (!formatNum(mTotal.sumWrite(), writeTotal, 32)) {
326 LOG(ERROR) << "formatNum buffer size is too small for write: " << mTotal.sumWrite();
327 }
328
329 out << android::base::StringPrintf(FMT_STR_TOTAL_USAGE, ms.count() / 1000, ms.count() % 1000,
330 readTotal, writeTotal, mTotal.fgFsync + mTotal.bgFsync);
331
332 if (mTotal.sumRead() >= mMinSizeOfTotalRead || mTotal.sumWrite() >= mMinSizeOfTotalWrite) {
333 out << STR_TOP_HEADER;
334 }
335 // Dump READ TOP
336 if (mTotal.sumRead() < mMinSizeOfTotalRead) {
337 out << android::base::StringPrintf(FMT_STR_SKIP_TOP_READ, mMinSizeOfTotalRead / 1000000)
338 << std::endl;
339 } else {
340 for (int i = 0, len = IO_TOP_MAX; i < len; i++) {
341 UserIo &target = mReadTop[i];
342 if (target.sumRead() == 0) {
343 break;
344 }
345 float percent = 100.0f * target.sumRead() / mTotal.sumRead();
346 const char *package = mUidNameMap.find(target.uid) == mUidNameMap.end()
347 ? "-"
348 : mUidNameMap[target.uid].c_str();
349 out << android::base::StringPrintf(FMT_STR_TOP_READ_USAGE, i + 1, percent,
350 target.fgRead, target.bgRead, target.fgFsync,
351 target.bgFsync, target.uid, package);
352 }
353 }
354
355 // Dump WRITE TOP
356 if (mTotal.sumWrite() < mMinSizeOfTotalWrite) {
357 out << android::base::StringPrintf(FMT_STR_SKIP_TOP_WRITE, mMinSizeOfTotalWrite / 1000000)
358 << std::endl;
359 } else {
360 for (int i = 0, len = IO_TOP_MAX; i < len; i++) {
361 UserIo &target = mWriteTop[i];
362 if (target.sumWrite() == 0) {
363 break;
364 }
365 float percent = 100.0f * target.sumWrite() / mTotal.sumWrite();
366 const char *package = mUidNameMap.find(target.uid) == mUidNameMap.end()
367 ? "-"
368 : mUidNameMap[target.uid].c_str();
369 out << android::base::StringPrintf(FMT_STR_TOP_WRITE_USAGE, i + 1, percent,
370 target.fgWrite, target.bgWrite, target.fgFsync,
371 target.bgFsync, target.uid, package);
372 }
373 }
374 return true;
375 }
376
loadDataFromLine(std::string && line,UserIo & data)377 static bool loadDataFromLine(std::string &&line, UserIo &data) {
378 std::vector<std::string> fields = android::base::Split(line, " ");
379 if (fields.size() < 11 || !android::base::ParseUint(fields[0], &data.uid) ||
380 !android::base::ParseUint(fields[3], &data.fgRead) ||
381 !android::base::ParseUint(fields[4], &data.fgWrite) ||
382 !android::base::ParseUint(fields[7], &data.bgRead) ||
383 !android::base::ParseUint(fields[8], &data.bgWrite) ||
384 !android::base::ParseUint(fields[9], &data.fgFsync) ||
385 !android::base::ParseUint(fields[10], &data.bgFsync)) {
386 LOG(WARNING) << "Invalid uid I/O stats: \"" << line << "\"";
387 return false;
388 }
389 return true;
390 }
391
dump(std::string * outAppend)392 void ScopeTimer::dump(std::string *outAppend) {
393 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
394 std::chrono::system_clock::now() - mStart);
395 outAppend->append("duration (");
396 outAppend->append(mName);
397 outAppend->append("): ");
398 outAppend->append(std::to_string(ms.count()));
399 outAppend->append("ms");
400 }
401
402 /*
403 * setOptions - IoUsage supports following options
404 * iostats.min : skip dump when R/W amount is lower than the value
405 * iostats.read.min : skip dump when READ amount is lower than the value
406 * iostats.write.min : skip dump when WRITE amount is lower than the value
407 * iostats.debug : 1 - to enable debug log; 0 - disabled
408 */
setOptions(const std::string & key,const std::string & value)409 void IoUsage::setOptions(const std::string &key, const std::string &value) {
410 std::stringstream out;
411 out << "set IO options: " << key << " , " << value;
412 if (key == "iostats.min" || key == "iostats.read.min" || key == "iostats.write.min" ||
413 key == "iostats.debug") {
414 uint64_t val = 0;
415 if (!android::base::ParseUint(value, &val)) {
416 out << "!!!! unable to parse value to uint64";
417 LOG(ERROR) << out.str();
418 return;
419 }
420 if (key == "iostats.min") {
421 mStats.setDumpThresholdSizeForRead(val);
422 mStats.setDumpThresholdSizeForWrite(val);
423 } else if (key == "iostats.disabled") {
424 mDisabled = (val != 0);
425 } else if (key == "iostats.read.min") {
426 mStats.setDumpThresholdSizeForRead(val);
427 } else if (key == "iostats.write.min") {
428 mStats.setDumpThresholdSizeForWrite(val);
429 } else if (key == "iostats.debug") {
430 sOptDebug = (val != 0);
431 }
432 LOG(INFO) << out.str() << ": Success";
433 }
434 }
435
refresh(void)436 void IoUsage::refresh(void) {
437 if (mDisabled)
438 return;
439 ScopeTimer _debugTimer("refresh");
440 _debugTimer.setEnabled(sOptDebug);
441 std::string buffer;
442 if (!android::base::ReadFileToString(UID_IO_STATS_PATH, &buffer)) {
443 LOG(ERROR) << UID_IO_STATS_PATH << ": ReadFileToString failed";
444 }
445 if (sOptDebug)
446 LOG(INFO) << "read " << UID_IO_STATS_PATH << " OK.";
447 std::vector<std::string> lines = android::base::Split(std::move(buffer), "\n");
448 std::unordered_map<uint32_t, UserIo> datas;
449 for (uint32_t i = 0; i < lines.size(); i++) {
450 if (lines[i].empty()) {
451 continue;
452 }
453 UserIo data;
454 if (!loadDataFromLine(std::move(lines[i]), data))
455 continue;
456 datas[data.uid] = data;
457 }
458 mStats.calcAll(std::move(datas));
459 std::stringstream out;
460 mStats.dump(&out);
461 const std::string &str = out.str();
462 if (sOptDebug) {
463 LOG(INFO) << str;
464 LOG(INFO) << "output append length:" << str.length();
465 }
466 append((std::string &)str);
467 }
468