OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / packages / experimental / procstatlog / procstatlog.c
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 '_'.
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.
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
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).
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.
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.
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: >%d 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.
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.
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
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 }