1 /* 2 * Copyright (C) 2016 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 #include "bootio_collector.h" 18 #include <android-base/logging.h> 19 #include <android-base/file.h> 20 #include <log/log.h> 21 #include "protos.pb.h" 22 #include "time.h" 23 #include <unordered_map> 24 #include <inttypes.h> 25 #include <dirent.h> 26 27 namespace android { 28 29 #define CPU_STAT_FILE "/proc/stat" 30 #define SAMPLES_FILE "/samples" 31 #define PID_STAT_FILE "/proc/%d/stat" 32 #define PID_CMDLINE_FILE "/proc/%d/cmdline" 33 #define PID_IO_FILE "/proc/%d/io" 34 #define PROC_DIR "/proc" 35 36 static const int MAX_LINE = 256; 37 38 #define die(...) { LOG(ERROR) << (__VA_ARGS__); exit(EXIT_FAILURE); } 39 40 void PopulateCpu(CpuData& cpu) { 41 long unsigned utime, ntime, stime, itime; 42 long unsigned iowtime, irqtime, sirqtime; 43 FILE *file; 44 file = fopen(CPU_STAT_FILE, "r"); 45 if (!file) die("Could not open /proc/stat.\n"); 46 fscanf(file, "cpu %lu %lu %lu %lu %lu %lu %lu", &utime, &ntime, &stime, 47 &itime, &iowtime, &irqtime, &sirqtime); 48 fclose(file); 49 cpu.set_utime(utime); 50 cpu.set_ntime(ntime); 51 cpu.set_stime(stime); 52 cpu.set_itime(itime); 53 cpu.set_iowtime(iowtime); 54 cpu.set_irqtime(irqtime); 55 cpu.set_sirqtime(sirqtime); 56 } 57 58 void ClearPreviousResults(std::string path) { 59 std::string err; 60 if (!android::base::RemoveFileIfExists(path, &err)) { 61 LOG(ERROR) << "failed to remove the file " << path << " " << err; 62 return; 63 } 64 } 65 66 int ReadIo(char *filename, AppSample *sample) { 67 FILE *file; 68 char line[MAX_LINE]; 69 unsigned int rchar, wchar, syscr, syscw, readbytes, writebytes; 70 71 file = fopen(filename, "r"); 72 if (!file) return 1; 73 while (fgets(line, MAX_LINE, file)) { 74 sscanf(line, "rchar: %u", &rchar); 75 sscanf(line, "wchar: %u", &wchar); 76 sscanf(line, "syscr: %u", &syscr); 77 sscanf(line, "syscw: %u", &syscw); 78 sscanf(line, "read_bytes: %u", &readbytes); 79 sscanf(line, "write_bytes: %u", &writebytes); 80 } 81 fclose(file); 82 sample->set_rchar(rchar); 83 sample->set_wchar(wchar); 84 sample->set_syscr(syscr); 85 sample->set_syscw(syscw); 86 sample->set_readbytes(readbytes); 87 sample->set_writebytes(writebytes); 88 return 0; 89 } 90 91 int ReadStatForName(char *filename, AppData *app) { 92 FILE *file; 93 char buf[MAX_LINE], *open_paren, *close_paren; 94 95 file = fopen(filename, "r"); 96 if (!file) return 1; 97 fgets(buf, MAX_LINE, file); 98 fclose(file); 99 100 /* Split at first '(' and last ')' to get process name. */ 101 open_paren = strchr(buf, '('); 102 close_paren = strrchr(buf, ')'); 103 if (!open_paren || !close_paren) return 1; 104 105 *open_paren = *close_paren = '\0'; 106 if (!app->has_tname()) { 107 app->set_tname(open_paren + 1, close_paren - open_paren - 1); 108 } 109 return 0; 110 } 111 112 int ReadStat(char *filename, AppSample *sample) { 113 FILE *file; 114 char buf[MAX_LINE], *open_paren, *close_paren; 115 116 file = fopen(filename, "r"); 117 if (!file) return 1; 118 fgets(buf, MAX_LINE, file); 119 fclose(file); 120 121 /* Split at first '(' and last ')' to get process name. */ 122 open_paren = strchr(buf, '('); 123 close_paren = strrchr(buf, ')'); 124 if (!open_paren || !close_paren) return 1; 125 126 uint64_t utime; 127 uint64_t stime; 128 uint64_t rss; 129 130 /* Scan rest of string. */ 131 sscanf(close_paren + 1, 132 " %*c " "%*d %*d %*d %*d %*d %*d %*d %*d %*d %*d " 133 "%" PRIu64 /*SCNu64*/ 134 "%" PRIu64 /*SCNu64*/ "%*d %*d %*d %*d %*d %*d %*d %*d " 135 "%" PRIu64 /*SCNu64*/ "%*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d", 136 &utime, 137 &stime, 138 &rss); 139 sample->set_utime(utime); 140 sample->set_stime(stime); 141 sample->set_rss(rss); 142 143 return 0; 144 } 145 146 int ReadCmdline(char *filename, AppData *app) { 147 FILE *file; 148 char line[MAX_LINE]; 149 150 line[0] = '\0'; 151 file = fopen(filename, "r"); 152 if (!file) return 1; 153 fgets(line, MAX_LINE, file); 154 fclose(file); 155 if (strlen(line) > 0) { 156 app->set_name(line, strlen(line)); 157 } else { 158 app->set_name("N/A"); 159 } 160 return 0; 161 }; 162 163 void ReadProcData(std::unordered_map<int, AppData*>& pidDataMap, DataContainer& dataContainer, 164 time_t currentTimeUtc, time_t currentUptime) { 165 DIR *procDir; 166 struct dirent *pidDir; 167 pid_t pid; 168 char filename[64]; 169 procDir = opendir(PROC_DIR); 170 if (!procDir) die("Could not open /proc.\n"); 171 while ((pidDir = readdir(procDir))) { 172 if (!isdigit(pidDir->d_name[0])) { 173 continue; 174 } 175 pid = atoi(pidDir->d_name); 176 AppData *data; 177 178 // TODO: in theory same pid can be shared for multiple processes, 179 // might need add extra check. 180 if (pidDataMap.count(pid) == 0) { 181 data = dataContainer.add_app(); 182 data->set_pid(pid); 183 sprintf(filename, PID_STAT_FILE, pid); 184 ReadStatForName(filename, data); 185 sprintf(filename, PID_CMDLINE_FILE, pid); 186 ReadCmdline(filename, data); 187 pidDataMap[pid] = data; 188 } else { 189 data = pidDataMap[pid]; 190 } 191 AppSample *sample = data->add_samples(); 192 sample->set_timestamp(currentTimeUtc); 193 sample->set_uptime(currentUptime); 194 195 sprintf(filename, PID_STAT_FILE, pid); 196 ReadStat(filename, sample); 197 198 sprintf(filename, PID_IO_FILE, pid); 199 ReadIo(filename, sample); 200 } 201 } 202 203 uint64_t SumCpuValues(CpuData& cpu) { 204 return cpu.utime() + cpu.ntime() + cpu.stime() + cpu.itime() + cpu.iowtime() + 205 cpu.irqtime() + cpu.sirqtime(); 206 } 207 208 time_t GetUptime() { 209 std::string uptime_str; 210 if (!android::base::ReadFileToString("/proc/uptime", &uptime_str)) { 211 LOG(ERROR) << "Failed to read /proc/uptime"; 212 return -1; 213 } 214 215 // Cast intentionally rounds down. 216 return static_cast<time_t>(strtod(uptime_str.c_str(), NULL)); 217 } 218 219 struct Stats { 220 int uptime; 221 float cpu; 222 uint64_t rbytes; 223 uint64_t wbytes; 224 }; 225 226 void PrintPids(DataContainer& data, std::unordered_map<int, uint64_t>& cpuDataMap) { 227 printf("rchar: number of bytes the process read, using any read-like system call " 228 "(from files, pipes, tty...).\n"); 229 printf("wchar: number of bytes the process wrote using any write-like system call.\n"); 230 printf("wchar: number of bytes the process wrote using any write-like system call.\n"); 231 printf("syscr: number of write-like system call invocations that the process performed.\n"); 232 printf("rbytes: number of bytes the process directly read from disk.\n"); 233 printf("wbytes: number of bytes the process originally dirtied in the page-cache " 234 "(assuming they will go to disk later).\n\n"); 235 236 std::unique_ptr<AppSample> bootZeroSample(new AppSample()); 237 std::map<int, Stats> statsMap; 238 // Init stats map 239 Stats emptyStat {0, 0., 0, 0}; 240 for (auto it = cpuDataMap.begin(); it != cpuDataMap.end(); it++) { 241 statsMap[it->first] = emptyStat; 242 } 243 for (int i = 0; i < data.app_size(); i++) { 244 const AppData appData = data.app(i); 245 printf("\n-----------------------------------------------------------------------------\n"); 246 printf("PID:\t%u\n", appData.pid()); 247 printf("Name:\t%s\n", appData.name().c_str()); 248 printf("ThName:\t%s\n", appData.tname().c_str()); 249 printf("%-15s%-13s%-13s%-13s%-13s%-13s%-13s%-13s\n", "Uptime inter.", "rchar", "wchar", 250 "syscr", "syscw", "rbytes", "wbytes", "cpu%"); 251 const AppSample *olderSample = NULL; 252 const AppSample *newerSample = NULL; 253 bool isFirstSample = true; 254 for (int j = 0; j < appData.samples_size(); j++) { 255 olderSample = newerSample; 256 newerSample = &(appData.samples(j)); 257 if (olderSample == NULL) { 258 olderSample = bootZeroSample.get(); 259 } 260 float cpuLoad = 0.; 261 uint64_t cpuDelta; 262 if (isFirstSample) { 263 cpuDelta = cpuDataMap[newerSample->timestamp()]; 264 } else { 265 cpuDelta = cpuDataMap[newerSample->timestamp()] - 266 cpuDataMap[olderSample->timestamp()]; 267 } 268 if (cpuDelta != 0) { 269 cpuLoad = (newerSample->utime() - olderSample->utime() + 270 newerSample->stime() - olderSample->stime()) * 100. / cpuDelta; 271 } 272 Stats& stats = statsMap[newerSample->timestamp()]; 273 stats.uptime = newerSample->uptime(); 274 stats.cpu += cpuLoad; 275 stats.rbytes += (newerSample->readbytes() - olderSample->readbytes()); 276 stats.wbytes += (newerSample->writebytes() - olderSample->writebytes()); 277 278 // Note that all of these are explicitly `long long`s, not int64_t, 279 // so we can't use PRId64 here. 280 #define NUMBER "%-13lld" 281 printf("%5lld - %-5lld " NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER "%-9.2f\n", 282 #undef NUMBER 283 olderSample->uptime(), 284 newerSample->uptime(), 285 newerSample->rchar() - olderSample->rchar(), 286 newerSample->wchar() - olderSample->wchar(), 287 newerSample->syscr() - olderSample->syscr(), 288 newerSample->syscw() - olderSample->syscw(), 289 newerSample->readbytes() - olderSample->readbytes(), 290 newerSample->writebytes() - olderSample->writebytes(), 291 cpuLoad); 292 isFirstSample = false; 293 } 294 printf("-----------------------------------------------------------------------------\n"); 295 #define NUMBER "%-13lld" 296 printf("%-15s" NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER "\n", 297 #undef NUMBER 298 "Total", 299 newerSample->rchar(), 300 newerSample->wchar(), 301 newerSample->syscr(), 302 newerSample->syscw(), 303 newerSample->readbytes(), 304 newerSample->writebytes()); 305 } 306 printf("\nAggregations\n%-10s%-13s%-13s%-13s\n", 307 "Total", 308 "rbytes", 309 "wbytes", 310 "cpu%"); 311 312 for (auto it = statsMap.begin(); it != statsMap.end(); it++) { 313 printf("%-10u%-13" PRIu64 "%-13" PRIu64 "%-9.2f\n", 314 it->second.uptime, 315 it->second.rbytes, 316 it->second.wbytes, 317 it->second.cpu); 318 } 319 } 320 321 } 322 323 BootioCollector::BootioCollector(std::string path) { 324 DCHECK_EQ('/', path.back()); 325 path_ = path; 326 } 327 328 void BootioCollector::StartDataCollection(int timeout, int samples) { 329 android::ClearPreviousResults(getStoragePath()); 330 int remaining = samples + 1; 331 int delayS = timeout / samples; 332 333 std::unordered_map < int, AppData * > pidDataMap; 334 std::unique_ptr <DataContainer> data(new DataContainer()); 335 while (remaining > 0) { 336 time_t currentTimeUtc = time(nullptr); 337 time_t currentUptime = android::GetUptime(); 338 CpuData *cpu = data->add_cpu(); 339 cpu->set_timestamp(currentTimeUtc); 340 cpu->set_uptime(currentUptime); 341 android::PopulateCpu(*cpu); 342 android::ReadProcData(pidDataMap, *data.get(), currentTimeUtc, currentUptime); 343 remaining--; 344 if (remaining == 0) { 345 continue; 346 } 347 sleep(delayS); 348 } 349 std::string file_data; 350 if (!data->SerializeToString(&file_data)) { 351 LOG(ERROR) << "Failed to serialize"; 352 return; 353 } 354 if (!android::base::WriteStringToFile(file_data, getStoragePath())) { 355 LOG(ERROR) << "Failed to write samples"; 356 } 357 } 358 359 void BootioCollector::Print() { 360 std::string file_data; 361 if (!android::base::ReadFileToString(getStoragePath(), &file_data)) { 362 printf("Failed to read data from file.\n"); 363 return; 364 } 365 std::unique_ptr <DataContainer> data(new DataContainer()); 366 if (!data->ParsePartialFromString(file_data)) { 367 printf("Failed to parse data.\n"); 368 return; 369 } 370 std::unordered_map<int, uint64_t> cpuDataMap; 371 for (int i = 0; i < data->cpu_size(); i++) { 372 CpuData cpu_data = data->cpu(i); 373 cpuDataMap[cpu_data.timestamp()] = android::SumCpuValues(cpu_data); 374 } 375 android::PrintPids(*data.get(), cpuDataMap); 376 } 377 378 379 std::string BootioCollector::getStoragePath() { 380 return path_ + SAMPLES_FILE; 381 } 382