procstatlog.c revision 9295b85ec3b4eb30e8fdd524a810033ae2aec633
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
57struct 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 '_'.
63static 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.
72static 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
102static 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).
160static 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.
210static 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.
218static 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.
313static 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.
354static 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
363int 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