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