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 */ 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 69 static bool isAppUid(uint32_t uid) { 70 if (uid >= AID_APP_START) { 71 return true; 72 } 73 return false; 74 } 75 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 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 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 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 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 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 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 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 */ 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 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 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 */ 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 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