1 /*
2 * Copyright (C) 2010 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 <assert.h>
18 #include <ctype.h>
19 #include <dirent.h>
20 #include <fcntl.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <sys/stat.h>
25 #include <sys/time.h>
26 #include <sys/types.h>
27 #include <time.h>
28 #include <unistd.h>
29
30 // This program is as dumb as possible -- it reads a whole bunch of data
31 // from /proc and reports when it changes. It's up to analysis tools to
32 // actually parse the data. This program only does enough parsing to split
33 // large files (/proc/stat, /proc/yaffs) into individual values.
34 //
35 // The output format is a repeating series of observed differences:
36 //
37 // T + <beforetime.stamp>
38 // /proc/<new_filename> + <contents of newly discovered file>
39 // /proc/<changed_filename> = <contents of changed file>
40 // /proc/<deleted_filename> -
41 // /proc/<filename>:<label> = <part of a multiline file>
42 // T - <aftertime.stamp>
43 //
44 //
45 // Files read:
46 //
47 // /proc/*/stat - for all running/selected processes
48 // /proc/*/wchan - for all running/selected processes
49 // /proc/binder/stats - per line: "/proc/binder/stats:BC_REPLY"
50 // /proc/diskstats - per device: "/proc/diskstats:mmcblk0"
51 // /proc/net/dev - per interface: "/proc/net/dev:rmnet0"
52 // /proc/stat - per line: "/proc/stat:intr"
53 // /proc/yaffs - per device/line: "/proc/yaffs:userdata:nBlockErasures"
54 // /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state
55 // - per line: "/sys/.../time_in_state:245000"
56
57 struct data {
58 char *name; // filename, plus ":var" for many-valued files
59 char *value; // text to be reported when it changes
60 };
61
62 // Like memcpy, but replaces spaces and unprintables with '_'.
unspace(char * dest,const char * src,int len)63 static void unspace(char *dest, const char *src, int len) {
64 while (len-- > 0) {
65 char ch = *src++;
66 *dest++ = isgraph(ch) ? ch : '_';
67 }
68 }
69
70 // Set data->name and data->value to malloc'd strings with the
71 // filename and contents of the file. Trims trailing whitespace.
read_data(struct data * data,const char * filename)72 static void read_data(struct data *data, const char *filename) {
73 char buf[4096];
74 data->name = strdup(filename);
75 int fd = open(filename, O_RDONLY);
76 if (fd < 0) {
77 data->value = NULL;
78 return;
79 }
80
81 int len = read(fd, buf, sizeof(buf));
82 if (len < 0) {
83 perror(filename);
84 close(fd);
85 data->value = NULL;
86 return;
87 }
88
89 close(fd);
90 while (len > 0 && isspace(buf[len - 1])) --len;
91 data->value = malloc(len + 1);
92 memcpy(data->value, buf, len);
93 data->value[len] = '\0';
94 }
95
96 // Read a name/value file and write data entries for each line.
97 // Returns the number of entries written (always <= stats_count).
98 //
99 // delimiter: used to split each line into name and value
100 // terminator: if non-NULL, processing stops after this string
101 // skip_words: skip this many words at the start of each line
read_lines(const char * filename,char delimiter,const char * terminator,int skip_words,struct data * stats,int stats_count)102 static int read_lines(
103 const char *filename,
104 char delimiter, const char *terminator, int skip_words,
105 struct data *stats, int stats_count) {
106 char buf[8192];
107 int fd = open(filename, O_RDONLY);
108 if (fd < 0) return 0;
109
110 int len = read(fd, buf, sizeof(buf) - 1);
111 if (len < 0) {
112 perror(filename);
113 close(fd);
114 return 0;
115 }
116 buf[len] = '\0';
117 close(fd);
118
119 if (terminator != NULL) {
120 char *end = strstr(buf, terminator);
121 if (end != NULL) *end = '\0';
122 }
123
124 int filename_len = strlen(filename);
125 int num = 0;
126 char *line;
127 for (line = strtok(buf, "\n");
128 line != NULL && num < stats_count;
129 line = strtok(NULL, "\n")) {
130 // Line format: <sp>name<delim><sp>value
131
132 int i;
133 while (isspace(*line)) ++line;
134 for (i = 0; i < skip_words; ++i) {
135 while (isgraph(*line)) ++line;
136 while (isspace(*line)) ++line;
137 }
138
139 char *name_end = strchr(line, delimiter);
140 if (name_end == NULL) continue;
141
142 // Key format: <filename>:<name>
143 struct data *data = &stats[num++];
144 data->name = malloc(filename_len + 1 + (name_end - line) + 1);
145 unspace(data->name, filename, filename_len);
146 data->name[filename_len] = ':';
147 unspace(data->name + filename_len + 1, line, name_end - line);
148 data->name[filename_len + 1 + (name_end - line)] = '\0';
149
150 char *value = name_end + 1;
151 while (isspace(*value)) ++value;
152 data->value = strdup(value);
153 }
154
155 return num;
156 }
157
158 // Read /proc/yaffs and write data entries for each line.
159 // Returns the number of entries written (always <= stats_count).
read_proc_yaffs(struct data * stats,int stats_count)160 static int read_proc_yaffs(struct data *stats, int stats_count) {
161 char buf[8192];
162 int fd = open("/proc/yaffs", O_RDONLY);
163 if (fd < 0) return 0;
164
165 int len = read(fd, buf, sizeof(buf) - 1);
166 if (len < 0) {
167 perror("/proc/yaffs");
168 close(fd);
169 return 0;
170 }
171 buf[len] = '\0';
172 close(fd);
173
174 int num = 0, device_len = 0;
175 char *line, *device = NULL;
176 for (line = strtok(buf, "\n");
177 line != NULL && num < stats_count;
178 line = strtok(NULL, "\n")) {
179 if (strncmp(line, "Device ", 7) == 0) {
180 device = strchr(line, '"');
181 if (device != NULL) {
182 char *end = strchr(++device, '"');
183 if (end != NULL) *end = '\0';
184 device_len = strlen(device);
185 }
186 continue;
187 }
188 if (device == NULL) continue;
189
190 char *name_end = line + strcspn(line, " .");
191 if (name_end == line || *name_end == '\0') continue;
192
193 struct data *data = &stats[num++];
194 data->name = malloc(12 + device_len + 1 + (name_end - line) + 1);
195 memcpy(data->name, "/proc/yaffs:", 12);
196 unspace(data->name + 12, device, device_len);
197 data->name[12 + device_len] = ':';
198 unspace(data->name + 12 + device_len + 1, line, name_end - line);
199 data->name[12 + device_len + 1 + (name_end - line)] = '\0';
200
201 char *value = name_end;
202 while (*value == '.' || isspace(*value)) ++value;
203 data->value = strdup(value);
204 }
205
206 return num;
207 }
208
209 // Compare two "struct data" records by their name.
compare_data(const void * a,const void * b)210 static int compare_data(const void *a, const void *b) {
211 const struct data *data_a = (const struct data *) a;
212 const struct data *data_b = (const struct data *) b;
213 return strcmp(data_a->name, data_b->name);
214 }
215
216 // Return a malloc'd array of "struct data" read from all over /proc.
217 // The array is sorted by name and terminated by a record with name == NULL.
read_stats(char * names[],int name_count)218 static struct data *read_stats(char *names[], int name_count) {
219 static int bad[4096]; // Cache pids known not to match patterns
220 static size_t bad_count = 0;
221
222 int pids[4096];
223 size_t pid_count = 0;
224
225 DIR *proc_dir = opendir("/proc");
226 if (proc_dir == NULL) {
227 perror("Can't scan /proc");
228 exit(1);
229 }
230
231 size_t bad_pos = 0;
232 char filename[1024];
233 struct dirent *proc_entry;
234 while ((proc_entry = readdir(proc_dir))) {
235 int pid = atoi(proc_entry->d_name);
236 if (pid <= 0) continue;
237
238 if (name_count > 0) {
239 while (bad_pos < bad_count && bad[bad_pos] < pid) ++bad_pos;
240 if (bad_pos < bad_count && bad[bad_pos] == pid) continue;
241
242 char cmdline[4096];
243 sprintf(filename, "/proc/%d/cmdline", pid);
244 int fd = open(filename, O_RDONLY);
245 if (fd < 0) {
246 perror(filename);
247 continue;
248 }
249
250 int len = read(fd, cmdline, sizeof(cmdline) - 1);
251 if (len < 0) {
252 perror(filename);
253 close(fd);
254 continue;
255 }
256
257 close(fd);
258 cmdline[len] = '\0';
259 int n;
260 for (n = 0; n < name_count && !strstr(cmdline, names[n]); ++n);
261
262 if (n == name_count) {
263 // Insertion sort -- pids mostly increase so this makes sense
264 if (bad_count < sizeof(bad) / sizeof(bad[0])) {
265 int pos = bad_count++;
266 while (pos > 0 && bad[pos - 1] > pid) {
267 bad[pos] = bad[pos - 1];
268 --pos;
269 }
270 bad[pos] = pid;
271 }
272 continue;
273 }
274 }
275
276 if (pid_count >= sizeof(pids) / sizeof(pids[0])) {
277 fprintf(stderr, "warning: >%zu processes\n", pid_count);
278 } else {
279 pids[pid_count++] = pid;
280 }
281 }
282 closedir(proc_dir);
283
284 size_t i, stats_count = pid_count * 2 + 200; // 200 for stat, yaffs, etc.
285 struct data *stats = malloc((stats_count + 1) * sizeof(struct data));
286 struct data *next = stats;
287 for (i = 0; i < pid_count; i++) {
288 assert(pids[i] > 0);
289 sprintf(filename, "/proc/%d/stat", pids[i]);
290 read_data(next++, filename);
291 sprintf(filename, "/proc/%d/wchan", pids[i]);
292 read_data(next++, filename);
293 }
294
295 struct data *end = stats + stats_count;
296 next += read_proc_yaffs(next, stats + stats_count - next);
297 next += read_lines("/proc/net/dev", ':', NULL, 0, next, end - next);
298 next += read_lines("/proc/stat", ' ', NULL, 0, next, end - next);
299 next += read_lines("/proc/binder/stats", ':', "\nproc ", 0, next, end - next);
300 next += read_lines("/proc/diskstats", ' ', NULL, 2, next, end - next);
301 next += read_lines(
302 "/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state",
303 ' ', NULL, 0, next, end - next);
304
305 assert(next < stats + stats_count);
306 next->name = NULL;
307 next->value = NULL;
308 qsort(stats, next - stats, sizeof(struct data), compare_data);
309 return stats;
310 }
311
312 // Print stats which have changed from one sorted array to the next.
diff_stats(struct data * old_stats,struct data * new_stats)313 static void diff_stats(struct data *old_stats, struct data *new_stats) {
314 while (old_stats->name != NULL || new_stats->name != NULL) {
315 int compare;
316 if (old_stats->name == NULL) {
317 compare = 1;
318 } else if (new_stats->name == NULL) {
319 compare = -1;
320 } else {
321 compare = compare_data(old_stats, new_stats);
322 }
323
324 if (compare < 0) {
325 // old_stats no longer present
326 if (old_stats->value != NULL) {
327 printf("%s -\n", old_stats->name);
328 }
329 ++old_stats;
330 } else if (compare > 0) {
331 // new_stats is new
332 if (new_stats->value != NULL) {
333 printf("%s + %s\n", new_stats->name, new_stats->value);
334 }
335 ++new_stats;
336 } else {
337 // changed
338 if (new_stats->value == NULL) {
339 if (old_stats->value != NULL) {
340 printf("%s -\n", old_stats->name);
341 }
342 } else if (old_stats->value == NULL) {
343 printf("%s + %s\n", new_stats->name, new_stats->value);
344 } else if (strcmp(old_stats->value, new_stats->value)) {
345 printf("%s = %s\n", new_stats->name, new_stats->value);
346 }
347 ++old_stats;
348 ++new_stats;
349 }
350 }
351 }
352
353 // Free a "struct data" array and all the strings within it.
free_stats(struct data * stats)354 static void free_stats(struct data *stats) {
355 int i;
356 for (i = 0; stats[i].name != NULL; ++i) {
357 free(stats[i].name);
358 free(stats[i].value);
359 }
360 free(stats);
361 }
362
main(int argc,char * argv[])363 int main(int argc, char *argv[]) {
364 if (argc < 2) {
365 fprintf(stderr,
366 "usage: procstatlog poll_interval [procname ...] > procstat.log\n\n"
367 "\n"
368 "Scans process status every poll_interval seconds (e.g. 0.1)\n"
369 "and writes data from /proc/stat, /proc/*/stat files, and\n"
370 "other /proc status files every time something changes.\n"
371 "\n"
372 "Scans all processes by default. Listing some process name\n"
373 "substrings will limit scanning and reduce overhead.\n"
374 "\n"
375 "Data is logged continuously until the program is killed.\n");
376 return 2;
377 }
378
379 long poll_usec = (long) (atof(argv[1]) * 1000000l);
380 if (poll_usec <= 0) {
381 fprintf(stderr, "illegal poll interval: %s\n", argv[1]);
382 return 2;
383 }
384
385 struct data *old_stats = malloc(sizeof(struct data));
386 old_stats->name = NULL;
387 old_stats->value = NULL;
388 while (1) {
389 struct timeval before, after;
390 gettimeofday(&before, NULL);
391 printf("T + %ld.%06ld\n", before.tv_sec, before.tv_usec);
392
393 struct data *new_stats = read_stats(argv + 2, argc - 2);
394 diff_stats(old_stats, new_stats);
395 free_stats(old_stats);
396 old_stats = new_stats;
397 gettimeofday(&after, NULL);
398 printf("T - %ld.%06ld\n", after.tv_sec, after.tv_usec);
399
400 long elapsed_usec = (long) after.tv_usec - before.tv_usec;
401 elapsed_usec += 1000000l * (after.tv_sec - before.tv_sec);
402 if (poll_usec > elapsed_usec) usleep(poll_usec - elapsed_usec);
403 }
404
405 return 0;
406 }
407