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