2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
26 #include <sys/types.h>
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.
35 // The output format is a repeating series of observed differences:
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>
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"
58 char *name; // filename, plus ":var" for many-valued files
59 char *value; // text to be reported when it changes
62 // Like memcpy, but replaces spaces and unprintables with '_'.
63 static void unspace(char *dest, const char *src, int len) {
66 *dest++ = isgraph(ch) ? ch : '_';
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) {
74 data->name = strdup(filename);
75 int fd = open(filename, O_RDONLY);
81 int len = read(fd, buf, sizeof(buf));
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';
96 // Read a name/value file and write data entries for each line.
97 // Returns the number of entries written (always <= stats_count).
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) {
107 int fd = open(filename, O_RDONLY);
108 if (fd < 0) return 0;
110 int len = read(fd, buf, sizeof(buf) - 1);
119 if (terminator != NULL) {
120 char *end = strstr(buf, terminator);
121 if (end != NULL) *end = '\0';
124 int filename_len = strlen(filename);
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
133 while (isspace(*line)) ++line;
134 for (i = 0; i < skip_words; ++i) {
135 while (isgraph(*line)) ++line;
136 while (isspace(*line)) ++line;
139 char *name_end = strchr(line, delimiter);
140 if (name_end == NULL) continue;
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';
150 char *value = name_end + 1;
151 while (isspace(*value)) ++value;
152 data->value = strdup(value);
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) {
162 int fd = open("/proc/yaffs", O_RDONLY);
163 if (fd < 0) return 0;
165 int len = read(fd, buf, sizeof(buf) - 1);
167 perror("/proc/yaffs");
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);
188 if (device == NULL) continue;
190 char *name_end = line + strcspn(line, " .");
191 if (name_end == line || *name_end == '\0') continue;
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';
201 char *value = name_end;
202 while (*value == '.' || isspace(*value)) ++value;
203 data->value = strdup(value);
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);
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;
223 size_t pid_count = 0;
225 DIR *proc_dir = opendir("/proc");
226 if (proc_dir == NULL) {
227 perror("Can't scan /proc");
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;
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;
243 sprintf(filename, "/proc/%d/cmdline", pid);
244 int fd = open(filename, O_RDONLY);
250 int len = read(fd, cmdline, sizeof(cmdline) - 1);
260 for (n = 0; n < name_count && !strstr(cmdline, names[n]); ++n);
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];
276 if (pid_count >= sizeof(pids) / sizeof(pids[0])) {
277 fprintf(stderr, "warning: >%d processes\n", pid_count);
279 pids[pid_count++] = pid;
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++) {
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);
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);
302 "/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state",
303 ' ', NULL, 0, next, end - next);
305 assert(next < stats + stats_count);
308 qsort(stats, next - stats, sizeof(struct data), compare_data);
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) {
316 if (old_stats->name == NULL) {
318 } else if (new_stats->name == NULL) {
321 compare = compare_data(old_stats, new_stats);
325 // old_stats no longer present
326 if (old_stats->value != NULL) {
327 printf("%s -\n", old_stats->name);
330 } else if (compare > 0) {
332 if (new_stats->value != NULL) {
333 printf("%s + %s\n", new_stats->name, new_stats->value);
338 if (new_stats->value == NULL) {
339 if (old_stats->value != NULL) {
340 printf("%s -\n", old_stats->name);
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);
353 // Free a "struct data" array and all the strings within it.
354 static void free_stats(struct data *stats) {
356 for (i = 0; stats[i].name != NULL; ++i) {
358 free(stats[i].value);
363 int main(int argc, char *argv[]) {
366 "usage: procstatlog poll_interval [procname ...] > procstat.log\n\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"
372 "Scans all processes by default. Listing some process name\n"
373 "substrings will limit scanning and reduce overhead.\n"
375 "Data is logged continuously until the program is killed.\n");
379 long poll_usec = (long) (atof(argv[1]) * 1000000l);
380 if (poll_usec <= 0) {
381 fprintf(stderr, "illegal poll interval: %s\n", argv[1]);
385 struct data *old_stats = malloc(sizeof(struct data));
386 old_stats->name = NULL;
387 old_stats->value = NULL;
389 struct timeval before, after;
390 gettimeofday(&before, NULL);
391 printf("T + %ld.%06ld\n", before.tv_sec, before.tv_usec);
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);
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);