• 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_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_TO(SYSTEM, 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_TO(SYSTEM, INFO) << i << ".";
117         std::string buffer;
118         if (!android::base::ReadFileToString("/proc/" + std::to_string(pid) + "/status", &buffer)) {
119             if (sOptDebug)
120                 LOG_TO(SYSTEM, 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_TO(SYSTEM, 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_TO(SYSTEM, 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_TO(SYSTEM, 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_TO(SYSTEM, 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_TO(SYSTEM, ERROR) << "formatNum buffer size is too small for read: "
324                               << mTotal.sumRead();
325     }
326     if (!formatNum(mTotal.sumWrite(), writeTotal, 32)) {
327         LOG_TO(SYSTEM, ERROR) << "formatNum buffer size is too small for write: "
328                               << mTotal.sumWrite();
329     }
330 
331     out << android::base::StringPrintf(FMT_STR_TOTAL_USAGE, ms.count() / 1000, ms.count() % 1000,
332                                        readTotal, writeTotal, mTotal.fgFsync + mTotal.bgFsync);
333 
334     if (mTotal.sumRead() >= mMinSizeOfTotalRead || mTotal.sumWrite() >= mMinSizeOfTotalWrite) {
335         out << STR_TOP_HEADER;
336     }
337     // Dump READ TOP
338     if (mTotal.sumRead() < mMinSizeOfTotalRead) {
339         out << android::base::StringPrintf(FMT_STR_SKIP_TOP_READ, mMinSizeOfTotalRead / 1000000)
340             << std::endl;
341     } else {
342         for (int i = 0, len = IO_TOP_MAX; i < len; i++) {
343             UserIo &target = mReadTop[i];
344             if (target.sumRead() == 0) {
345                 break;
346             }
347             float percent = 100.0f * target.sumRead() / mTotal.sumRead();
348             const char *package = mUidNameMap.find(target.uid) == mUidNameMap.end()
349                                       ? "-"
350                                       : mUidNameMap[target.uid].c_str();
351             out << android::base::StringPrintf(FMT_STR_TOP_READ_USAGE, i + 1, percent,
352                                                target.fgRead, target.bgRead, target.fgFsync,
353                                                target.bgFsync, target.uid, package);
354         }
355     }
356 
357     // Dump WRITE TOP
358     if (mTotal.sumWrite() < mMinSizeOfTotalWrite) {
359         out << android::base::StringPrintf(FMT_STR_SKIP_TOP_WRITE, mMinSizeOfTotalWrite / 1000000)
360             << std::endl;
361     } else {
362         for (int i = 0, len = IO_TOP_MAX; i < len; i++) {
363             UserIo &target = mWriteTop[i];
364             if (target.sumWrite() == 0) {
365                 break;
366             }
367             float percent = 100.0f * target.sumWrite() / mTotal.sumWrite();
368             const char *package = mUidNameMap.find(target.uid) == mUidNameMap.end()
369                                       ? "-"
370                                       : mUidNameMap[target.uid].c_str();
371             out << android::base::StringPrintf(FMT_STR_TOP_WRITE_USAGE, i + 1, percent,
372                                                target.fgWrite, target.bgWrite, target.fgFsync,
373                                                target.bgFsync, target.uid, package);
374         }
375     }
376     return true;
377 }
378 
loadDataFromLine(std::string && line,UserIo & data)379 static bool loadDataFromLine(std::string &&line, UserIo &data) {
380     std::vector<std::string> fields = android::base::Split(line, " ");
381     if (fields.size() < 11 || !android::base::ParseUint(fields[0], &data.uid) ||
382         !android::base::ParseUint(fields[3], &data.fgRead) ||
383         !android::base::ParseUint(fields[4], &data.fgWrite) ||
384         !android::base::ParseUint(fields[7], &data.bgRead) ||
385         !android::base::ParseUint(fields[8], &data.bgWrite) ||
386         !android::base::ParseUint(fields[9], &data.fgFsync) ||
387         !android::base::ParseUint(fields[10], &data.bgFsync)) {
388         LOG_TO(SYSTEM, WARNING) << "Invalid uid I/O stats: \"" << line << "\"";
389         return false;
390     }
391     return true;
392 }
393 
dump(std::string * outAppend)394 void ScopeTimer::dump(std::string *outAppend) {
395     auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
396         std::chrono::system_clock::now() - mStart);
397     outAppend->append("duration (");
398     outAppend->append(mName);
399     outAppend->append("): ");
400     outAppend->append(std::to_string(ms.count()));
401     outAppend->append("ms");
402 }
403 
404 /*
405  * setOptions - IoUsage supports following options
406  *     iostats.min : skip dump when R/W amount is lower than the value
407  *     iostats.read.min : skip dump when READ amount is lower than the value
408  *     iostats.write.min : skip dump when WRITE amount is lower than the value
409  *     iostats.debug : 1 - to enable debug log; 0 - disabled
410  */
setOptions(const std::string & key,const std::string & value)411 void IoUsage::setOptions(const std::string &key, const std::string &value) {
412     std::stringstream out;
413     out << "set IO options: " << key << " , " << value;
414     if (key == "iostats.min" || key == "iostats.read.min" || key == "iostats.write.min" ||
415         key == "iostats.debug") {
416         uint64_t val = 0;
417         if (!android::base::ParseUint(value, &val)) {
418             out << "!!!! unable to parse value to uint64";
419             LOG_TO(SYSTEM, ERROR) << out.str();
420             return;
421         }
422         if (key == "iostats.min") {
423             mStats.setDumpThresholdSizeForRead(val);
424             mStats.setDumpThresholdSizeForWrite(val);
425         } else if (key == "iostats.disabled") {
426             mDisabled = (val != 0);
427         } else if (key == "iostats.read.min") {
428             mStats.setDumpThresholdSizeForRead(val);
429         } else if (key == "iostats.write.min") {
430             mStats.setDumpThresholdSizeForWrite(val);
431         } else if (key == "iostats.debug") {
432             sOptDebug = (val != 0);
433         }
434         LOG_TO(SYSTEM, INFO) << out.str() << ": Success";
435     }
436 }
437 
refresh(void)438 void IoUsage::refresh(void) {
439     if (mDisabled)
440         return;
441     ScopeTimer _debugTimer("refresh");
442     _debugTimer.setEnabled(sOptDebug);
443     std::string buffer;
444     if (!android::base::ReadFileToString(UID_IO_STATS_PATH, &buffer)) {
445         LOG_TO(SYSTEM, ERROR) << UID_IO_STATS_PATH << ": ReadFileToString failed";
446     }
447     if (sOptDebug)
448         LOG_TO(SYSTEM, INFO) << "read " << UID_IO_STATS_PATH << " OK.";
449     std::vector<std::string> lines = android::base::Split(std::move(buffer), "\n");
450     std::unordered_map<uint32_t, UserIo> datas;
451     for (uint32_t i = 0; i < lines.size(); i++) {
452         if (lines[i].empty()) {
453             continue;
454         }
455         UserIo data;
456         if (!loadDataFromLine(std::move(lines[i]), data))
457             continue;
458         datas[data.uid] = data;
459     }
460     mStats.calcAll(std::move(datas));
461     std::stringstream out;
462     mStats.dump(&out);
463     const std::string &str = out.str();
464     if (sOptDebug) {
465         LOG_TO(SYSTEM, INFO) << str;
466         LOG_TO(SYSTEM, INFO) << "output append length:" << str.length();
467     }
468     append((std::string &)str);
469 }
470