Home | History | Annotate | Download | only in bootio
      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