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 
PopulateCpu(CpuData & cpu)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 
ClearPreviousResults(std::string path)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 
ReadIo(char * filename,AppSample * sample)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 
ReadStatForName(char * filename,AppData * app)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 
ReadStat(char * filename,AppSample * sample)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 
ReadCmdline(char * filename,AppData * app)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 
ReadProcData(std::unordered_map<int,AppData * > & pidDataMap,DataContainer & dataContainer,time_t currentTimeUtc,time_t currentUptime)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 
SumCpuValues(CpuData & cpu)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 
GetUptime()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 
PrintPids(DataContainer & data,std::unordered_map<int,uint64_t> & cpuDataMap)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 #define NUMBER "%-13" PRId64
279             printf("%5" PRId64 " - %-5" PRId64 "  " NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER "%-9.2f\n",
280 #undef NUMBER
281                    olderSample->uptime(),
282                    newerSample->uptime(),
283                    newerSample->rchar() - olderSample->rchar(),
284                    newerSample->wchar() - olderSample->wchar(),
285                    newerSample->syscr() - olderSample->syscr(),
286                    newerSample->syscw() - olderSample->syscw(),
287                    newerSample->readbytes() - olderSample->readbytes(),
288                    newerSample->writebytes() - olderSample->writebytes(),
289                    cpuLoad);
290             isFirstSample = false;
291         }
292         printf("-----------------------------------------------------------------------------\n");
293 #define NUMBER "%-13" PRId64
294         printf("%-15s" NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER "\n",
295 #undef NUMBER
296                "Total",
297                newerSample->rchar(),
298                newerSample->wchar(),
299                newerSample->syscr(),
300                newerSample->syscw(),
301                newerSample->readbytes(),
302                newerSample->writebytes());
303     }
304     printf("\nAggregations\n%-10s%-13s%-13s%-13s\n",
305            "Total",
306            "rbytes",
307            "wbytes",
308            "cpu%");
309 
310     for (auto it = statsMap.begin(); it != statsMap.end(); it++) {
311         printf("%-10u%-13" PRIu64 "%-13" PRIu64 "%-9.2f\n",
312                it->second.uptime,
313                it->second.rbytes,
314                it->second.wbytes,
315                it->second.cpu);
316     }
317 }
318 
319 }
320 
BootioCollector(std::string path)321 BootioCollector::BootioCollector(std::string path) {
322     DCHECK_EQ('/', path.back());
323     path_ = path;
324 }
325 
StartDataCollection(int timeout,int samples)326 void BootioCollector::StartDataCollection(int timeout, int samples) {
327     android::ClearPreviousResults(getStoragePath());
328     int remaining = samples + 1;
329     int delayS = timeout / samples;
330 
331     std::unordered_map < int, AppData * > pidDataMap;
332     std::unique_ptr <DataContainer> data(new DataContainer());
333     while (remaining > 0) {
334         time_t currentTimeUtc = time(nullptr);
335         time_t currentUptime = android::GetUptime();
336         CpuData *cpu = data->add_cpu();
337         cpu->set_timestamp(currentTimeUtc);
338         cpu->set_uptime(currentUptime);
339         android::PopulateCpu(*cpu);
340         android::ReadProcData(pidDataMap, *data.get(), currentTimeUtc, currentUptime);
341         remaining--;
342         if (remaining == 0) {
343             continue;
344         }
345         sleep(delayS);
346     }
347     std::string file_data;
348     if (!data->SerializeToString(&file_data)) {
349         LOG(ERROR) << "Failed to serialize";
350         return;
351     }
352     if (!android::base::WriteStringToFile(file_data, getStoragePath())) {
353         LOG(ERROR) << "Failed to write samples";
354     }
355 }
356 
Print()357 void BootioCollector::Print() {
358     std::string file_data;
359     if (!android::base::ReadFileToString(getStoragePath(), &file_data)) {
360         printf("Failed to read data from file.\n");
361         return;
362     }
363     std::unique_ptr <DataContainer> data(new DataContainer());
364     if (!data->ParsePartialFromString(file_data)) {
365         printf("Failed to parse data.\n");
366         return;
367     }
368     std::unordered_map<int, uint64_t> cpuDataMap;
369     for (int i = 0; i < data->cpu_size(); i++) {
370         CpuData cpu_data = data->cpu(i);
371         cpuDataMap[cpu_data.timestamp()] = android::SumCpuValues(cpu_data);
372     }
373     android::PrintPids(*data.get(), cpuDataMap);
374 }
375 
376 
getStoragePath()377 std::string BootioCollector::getStoragePath() {
378     return path_ + SAMPLES_FILE;
379 }
380