Home | History | Annotate | Download | only in procstatlog
      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: >%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.
    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 }
    407