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