406 lines
13 KiB
C
406 lines
13 KiB
C
/*
|
|
* Copyright (C) 2010 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
// This program is as dumb as possible -- it reads a whole bunch of data
|
|
// from /proc and reports when it changes. It's up to analysis tools to
|
|
// actually parse the data. This program only does enough parsing to split
|
|
// large files (/proc/stat, /proc/yaffs) into individual values.
|
|
//
|
|
// The output format is a repeating series of observed differences:
|
|
//
|
|
// T + <beforetime.stamp>
|
|
// /proc/<new_filename> + <contents of newly discovered file>
|
|
// /proc/<changed_filename> = <contents of changed file>
|
|
// /proc/<deleted_filename> -
|
|
// /proc/<filename>:<label> = <part of a multiline file>
|
|
// T - <aftertime.stamp>
|
|
//
|
|
//
|
|
// Files read:
|
|
//
|
|
// /proc/*/stat - for all running/selected processes
|
|
// /proc/*/wchan - for all running/selected processes
|
|
// /proc/binder/stats - per line: "/proc/binder/stats:BC_REPLY"
|
|
// /proc/diskstats - per device: "/proc/diskstats:mmcblk0"
|
|
// /proc/net/dev - per interface: "/proc/net/dev:rmnet0"
|
|
// /proc/stat - per line: "/proc/stat:intr"
|
|
// /proc/yaffs - per device/line: "/proc/yaffs:userdata:nBlockErasures"
|
|
// /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state
|
|
// - per line: "/sys/.../time_in_state:245000"
|
|
|
|
struct data {
|
|
char *name; // filename, plus ":var" for many-valued files
|
|
char *value; // text to be reported when it changes
|
|
};
|
|
|
|
// Like memcpy, but replaces spaces and unprintables with '_'.
|
|
static void unspace(char *dest, const char *src, int len) {
|
|
while (len-- > 0) {
|
|
char ch = *src++;
|
|
*dest++ = isgraph(ch) ? ch : '_';
|
|
}
|
|
}
|
|
|
|
// Set data->name and data->value to malloc'd strings with the
|
|
// filename and contents of the file. Trims trailing whitespace.
|
|
static void read_data(struct data *data, const char *filename) {
|
|
char buf[4096];
|
|
data->name = strdup(filename);
|
|
int fd = open(filename, O_RDONLY);
|
|
if (fd < 0) {
|
|
data->value = NULL;
|
|
return;
|
|
}
|
|
|
|
int len = read(fd, buf, sizeof(buf));
|
|
if (len < 0) {
|
|
perror(filename);
|
|
close(fd);
|
|
data->value = NULL;
|
|
return;
|
|
}
|
|
|
|
close(fd);
|
|
while (len > 0 && isspace(buf[len - 1])) --len;
|
|
data->value = malloc(len + 1);
|
|
memcpy(data->value, buf, len);
|
|
data->value[len] = '\0';
|
|
}
|
|
|
|
// Read a name/value file and write data entries for each line.
|
|
// Returns the number of entries written (always <= stats_count).
|
|
//
|
|
// delimiter: used to split each line into name and value
|
|
// terminator: if non-NULL, processing stops after this string
|
|
// skip_words: skip this many words at the start of each line
|
|
static int read_lines(
|
|
const char *filename,
|
|
char delimiter, const char *terminator, int skip_words,
|
|
struct data *stats, int stats_count) {
|
|
char buf[8192];
|
|
int fd = open(filename, O_RDONLY);
|
|
if (fd < 0) return 0;
|
|
|
|
int len = read(fd, buf, sizeof(buf) - 1);
|
|
if (len < 0) {
|
|
perror(filename);
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
buf[len] = '\0';
|
|
close(fd);
|
|
|
|
if (terminator != NULL) {
|
|
char *end = strstr(buf, terminator);
|
|
if (end != NULL) *end = '\0';
|
|
}
|
|
|
|
int filename_len = strlen(filename);
|
|
int num = 0;
|
|
char *line;
|
|
for (line = strtok(buf, "\n");
|
|
line != NULL && num < stats_count;
|
|
line = strtok(NULL, "\n")) {
|
|
// Line format: <sp>name<delim><sp>value
|
|
|
|
int i;
|
|
while (isspace(*line)) ++line;
|
|
for (i = 0; i < skip_words; ++i) {
|
|
while (isgraph(*line)) ++line;
|
|
while (isspace(*line)) ++line;
|
|
}
|
|
|
|
char *name_end = strchr(line, delimiter);
|
|
if (name_end == NULL) continue;
|
|
|
|
// Key format: <filename>:<name>
|
|
struct data *data = &stats[num++];
|
|
data->name = malloc(filename_len + 1 + (name_end - line) + 1);
|
|
unspace(data->name, filename, filename_len);
|
|
data->name[filename_len] = ':';
|
|
unspace(data->name + filename_len + 1, line, name_end - line);
|
|
data->name[filename_len + 1 + (name_end - line)] = '\0';
|
|
|
|
char *value = name_end + 1;
|
|
while (isspace(*value)) ++value;
|
|
data->value = strdup(value);
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
// Read /proc/yaffs and write data entries for each line.
|
|
// Returns the number of entries written (always <= stats_count).
|
|
static int read_proc_yaffs(struct data *stats, int stats_count) {
|
|
char buf[8192];
|
|
int fd = open("/proc/yaffs", O_RDONLY);
|
|
if (fd < 0) return 0;
|
|
|
|
int len = read(fd, buf, sizeof(buf) - 1);
|
|
if (len < 0) {
|
|
perror("/proc/yaffs");
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
buf[len] = '\0';
|
|
close(fd);
|
|
|
|
int num = 0, device_len = 0;
|
|
char *line, *device = NULL;
|
|
for (line = strtok(buf, "\n");
|
|
line != NULL && num < stats_count;
|
|
line = strtok(NULL, "\n")) {
|
|
if (strncmp(line, "Device ", 7) == 0) {
|
|
device = strchr(line, '"');
|
|
if (device != NULL) {
|
|
char *end = strchr(++device, '"');
|
|
if (end != NULL) *end = '\0';
|
|
device_len = strlen(device);
|
|
}
|
|
continue;
|
|
}
|
|
if (device == NULL) continue;
|
|
|
|
char *name_end = line + strcspn(line, " .");
|
|
if (name_end == line || *name_end == '\0') continue;
|
|
|
|
struct data *data = &stats[num++];
|
|
data->name = malloc(12 + device_len + 1 + (name_end - line) + 1);
|
|
memcpy(data->name, "/proc/yaffs:", 12);
|
|
unspace(data->name + 12, device, device_len);
|
|
data->name[12 + device_len] = ':';
|
|
unspace(data->name + 12 + device_len + 1, line, name_end - line);
|
|
data->name[12 + device_len + 1 + (name_end - line)] = '\0';
|
|
|
|
char *value = name_end;
|
|
while (*value == '.' || isspace(*value)) ++value;
|
|
data->value = strdup(value);
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
// Compare two "struct data" records by their name.
|
|
static int compare_data(const void *a, const void *b) {
|
|
const struct data *data_a = (const struct data *) a;
|
|
const struct data *data_b = (const struct data *) b;
|
|
return strcmp(data_a->name, data_b->name);
|
|
}
|
|
|
|
// Return a malloc'd array of "struct data" read from all over /proc.
|
|
// The array is sorted by name and terminated by a record with name == NULL.
|
|
static struct data *read_stats(char *names[], int name_count) {
|
|
static int bad[4096]; // Cache pids known not to match patterns
|
|
static size_t bad_count = 0;
|
|
|
|
int pids[4096];
|
|
size_t pid_count = 0;
|
|
|
|
DIR *proc_dir = opendir("/proc");
|
|
if (proc_dir == NULL) {
|
|
perror("Can't scan /proc");
|
|
exit(1);
|
|
}
|
|
|
|
size_t bad_pos = 0;
|
|
char filename[1024];
|
|
struct dirent *proc_entry;
|
|
while ((proc_entry = readdir(proc_dir))) {
|
|
int pid = atoi(proc_entry->d_name);
|
|
if (pid <= 0) continue;
|
|
|
|
if (name_count > 0) {
|
|
while (bad_pos < bad_count && bad[bad_pos] < pid) ++bad_pos;
|
|
if (bad_pos < bad_count && bad[bad_pos] == pid) continue;
|
|
|
|
char cmdline[4096];
|
|
sprintf(filename, "/proc/%d/cmdline", pid);
|
|
int fd = open(filename, O_RDONLY);
|
|
if (fd < 0) {
|
|
perror(filename);
|
|
continue;
|
|
}
|
|
|
|
int len = read(fd, cmdline, sizeof(cmdline) - 1);
|
|
if (len < 0) {
|
|
perror(filename);
|
|
close(fd);
|
|
continue;
|
|
}
|
|
|
|
close(fd);
|
|
cmdline[len] = '\0';
|
|
int n;
|
|
for (n = 0; n < name_count && !strstr(cmdline, names[n]); ++n);
|
|
|
|
if (n == name_count) {
|
|
// Insertion sort -- pids mostly increase so this makes sense
|
|
if (bad_count < sizeof(bad) / sizeof(bad[0])) {
|
|
int pos = bad_count++;
|
|
while (pos > 0 && bad[pos - 1] > pid) {
|
|
bad[pos] = bad[pos - 1];
|
|
--pos;
|
|
}
|
|
bad[pos] = pid;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (pid_count >= sizeof(pids) / sizeof(pids[0])) {
|
|
fprintf(stderr, "warning: >%zu processes\n", pid_count);
|
|
} else {
|
|
pids[pid_count++] = pid;
|
|
}
|
|
}
|
|
closedir(proc_dir);
|
|
|
|
size_t i, stats_count = pid_count * 2 + 200; // 200 for stat, yaffs, etc.
|
|
struct data *stats = malloc((stats_count + 1) * sizeof(struct data));
|
|
struct data *next = stats;
|
|
for (i = 0; i < pid_count; i++) {
|
|
assert(pids[i] > 0);
|
|
sprintf(filename, "/proc/%d/stat", pids[i]);
|
|
read_data(next++, filename);
|
|
sprintf(filename, "/proc/%d/wchan", pids[i]);
|
|
read_data(next++, filename);
|
|
}
|
|
|
|
struct data *end = stats + stats_count;
|
|
next += read_proc_yaffs(next, stats + stats_count - next);
|
|
next += read_lines("/proc/net/dev", ':', NULL, 0, next, end - next);
|
|
next += read_lines("/proc/stat", ' ', NULL, 0, next, end - next);
|
|
next += read_lines("/proc/binder/stats", ':', "\nproc ", 0, next, end - next);
|
|
next += read_lines("/proc/diskstats", ' ', NULL, 2, next, end - next);
|
|
next += read_lines(
|
|
"/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state",
|
|
' ', NULL, 0, next, end - next);
|
|
|
|
assert(next < stats + stats_count);
|
|
next->name = NULL;
|
|
next->value = NULL;
|
|
qsort(stats, next - stats, sizeof(struct data), compare_data);
|
|
return stats;
|
|
}
|
|
|
|
// Print stats which have changed from one sorted array to the next.
|
|
static void diff_stats(struct data *old_stats, struct data *new_stats) {
|
|
while (old_stats->name != NULL || new_stats->name != NULL) {
|
|
int compare;
|
|
if (old_stats->name == NULL) {
|
|
compare = 1;
|
|
} else if (new_stats->name == NULL) {
|
|
compare = -1;
|
|
} else {
|
|
compare = compare_data(old_stats, new_stats);
|
|
}
|
|
|
|
if (compare < 0) {
|
|
// old_stats no longer present
|
|
if (old_stats->value != NULL) {
|
|
printf("%s -\n", old_stats->name);
|
|
}
|
|
++old_stats;
|
|
} else if (compare > 0) {
|
|
// new_stats is new
|
|
if (new_stats->value != NULL) {
|
|
printf("%s + %s\n", new_stats->name, new_stats->value);
|
|
}
|
|
++new_stats;
|
|
} else {
|
|
// changed
|
|
if (new_stats->value == NULL) {
|
|
if (old_stats->value != NULL) {
|
|
printf("%s -\n", old_stats->name);
|
|
}
|
|
} else if (old_stats->value == NULL) {
|
|
printf("%s + %s\n", new_stats->name, new_stats->value);
|
|
} else if (strcmp(old_stats->value, new_stats->value)) {
|
|
printf("%s = %s\n", new_stats->name, new_stats->value);
|
|
}
|
|
++old_stats;
|
|
++new_stats;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Free a "struct data" array and all the strings within it.
|
|
static void free_stats(struct data *stats) {
|
|
int i;
|
|
for (i = 0; stats[i].name != NULL; ++i) {
|
|
free(stats[i].name);
|
|
free(stats[i].value);
|
|
}
|
|
free(stats);
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
if (argc < 2) {
|
|
fprintf(stderr,
|
|
"usage: procstatlog poll_interval [procname ...] > procstat.log\n\n"
|
|
"\n"
|
|
"Scans process status every poll_interval seconds (e.g. 0.1)\n"
|
|
"and writes data from /proc/stat, /proc/*/stat files, and\n"
|
|
"other /proc status files every time something changes.\n"
|
|
"\n"
|
|
"Scans all processes by default. Listing some process name\n"
|
|
"substrings will limit scanning and reduce overhead.\n"
|
|
"\n"
|
|
"Data is logged continuously until the program is killed.\n");
|
|
return 2;
|
|
}
|
|
|
|
long poll_usec = (long) (atof(argv[1]) * 1000000l);
|
|
if (poll_usec <= 0) {
|
|
fprintf(stderr, "illegal poll interval: %s\n", argv[1]);
|
|
return 2;
|
|
}
|
|
|
|
struct data *old_stats = malloc(sizeof(struct data));
|
|
old_stats->name = NULL;
|
|
old_stats->value = NULL;
|
|
while (1) {
|
|
struct timeval before, after;
|
|
gettimeofday(&before, NULL);
|
|
printf("T + %ld.%06ld\n", before.tv_sec, before.tv_usec);
|
|
|
|
struct data *new_stats = read_stats(argv + 2, argc - 2);
|
|
diff_stats(old_stats, new_stats);
|
|
free_stats(old_stats);
|
|
old_stats = new_stats;
|
|
gettimeofday(&after, NULL);
|
|
printf("T - %ld.%06ld\n", after.tv_sec, after.tv_usec);
|
|
|
|
long elapsed_usec = (long) after.tv_usec - before.tv_usec;
|
|
elapsed_usec += 1000000l * (after.tv_sec - before.tv_sec);
|
|
if (poll_usec > elapsed_usec) usleep(poll_usec - elapsed_usec);
|
|
}
|
|
|
|
return 0;
|
|
}
|