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