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 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