blob: 1e73c2ca1f2bbe7cda36eb7e2016083b8531c037 [file] [log] [blame]
/*
* Copyright 2015 Google Inc. All rights reserved.
*
* 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 <fcntl.h>
#include <getopt.h>
#include <glob.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <unistd.h>
#define DEFAULT_READ_INTERVAL 60
#define DEFAULT_PROCS_TO_SAMPLE 5
#define DEFAULT_WARMUP_SECONDS 600
#define DEFAULT_PROC_GLOB_PATH "/proc/[0-9]*/stat"
#define CMD_LEN 15
#define STR(X) #X
#define XSTR(X) STR(X)
struct proc {
pid_t pid;
char cmd[CMD_LEN + 1];
unsigned long msec;
};
struct proc_list {
struct proc *procs;
int count;
};
static long ticks_per_sec;
void die(const char *msg)
{
fprintf(stderr, "error: %s\n", msg);
exit(1);
}
unsigned long ticks_to_ms(unsigned long ticks)
{
return (1000.0 / ticks_per_sec) * ticks;
}
void read_stat(struct proc *proc, const char *stat_path)
{
char buf[256];
int pid_int;
unsigned long utime;
unsigned long stime;
int rc;
int fd;
fd = open(stat_path, O_RDONLY);
/* This isn't an error. We have a list of files which existed the moment the
glob was run. If a process exits before we get around to reading it, its
/proc files will go away. */
if (fd == -1)
return;
rc = read(fd, buf, sizeof(buf) - 1);
if (rc < 1)
die("read");
close(fd);
buf[rc] = '\0';
rc = sscanf(buf,
"%d (%" XSTR(CMD_LEN) "[^)]) %*c %*d %*d %*d %*d %*d %*u %*u "
"%*u %*u %*u %lu %lu", &pid_int, proc->cmd, &utime, &stime);
if (rc != 4)
die("sscanf");
proc->pid = pid_int;
proc->msec = ticks_to_ms(utime + stime);
}
int proc_pid_cmp(const void *a, const void *b)
{
const struct proc *_a = a;
const struct proc *_b = b;
return _a->pid - _b->pid;
}
int proc_msec_cmp(const void *a, const void *b)
{
const struct proc *_a = a;
const struct proc *_b = b;
return _a->msec - _b->msec;
}
void print_top(struct proc_list *new_procs, struct proc_list *old_procs,
int procs_to_sample, int interval)
{
struct proc top[new_procs->count];
int top_count;
int i, lim;
top_count = 0;
for (i = 0; i < new_procs->count; i++) {
struct proc *new_proc = &new_procs->procs[i];
struct proc *old_proc = bsearch(new_proc, old_procs->procs, old_procs->count,
sizeof(*old_procs->procs), proc_pid_cmp);
if (old_proc) {
top[top_count] = *new_proc;
top[top_count].msec = new_proc->msec - old_proc->msec;
top_count++;
}
}
qsort(top, top_count, sizeof(*top), proc_msec_cmp);
printf("%dsec:", interval);
lim = (top_count > procs_to_sample) ? (top_count - procs_to_sample) : 0;
for (i = top_count - 1; i >= lim; i--) {
printf(" %s(%.3f)", top[i].cmd, top[i].msec / 1000.0);
}
printf("\n");
}
void read_procs(struct proc_list *new_procs, const char *proc_glob_path)
{
glob_t pglob;
unsigned i;
int rc;
rc = glob(proc_glob_path, GLOB_NOSORT, NULL, &pglob);
if (rc != 0)
die("glob");
new_procs->count = pglob.gl_pathc;
new_procs->procs = malloc(new_procs->count * sizeof(*new_procs->procs));
if (!new_procs->procs)
die("out of memory");
for (i = 0; i < pglob.gl_pathc; i++) {
read_stat(&new_procs->procs[i], pglob.gl_pathv[i]);
}
globfree(&pglob);
/* glob sorts alphanumerically, so we would need to use an alphanumeric
comparison to find elements using bsearch. here we sort numerically by pid*/
qsort(new_procs->procs, new_procs->count, sizeof(*new_procs->procs), proc_pid_cmp);
}
void free_procs(struct proc_list *p)
{
free(p->procs);
}
void usage_and_die(const char *argv0)
{
fprintf(stderr, "Usage: %s [options]\n"
"\n"
" -i, --interval=<interval> sampling interval in seconds (%d)\n"
" -n, --num=<num> number of processes to sample (%d)\n"
" -o, --oneshot one-shot mode, do not loop\n"
" -p, --path=<path> path for process stat files (%s)\n"
" -w, --warmup=<warmup> seconds to wait before sampling begins (%d)\n",
argv0, DEFAULT_READ_INTERVAL, DEFAULT_PROCS_TO_SAMPLE,
DEFAULT_PROC_GLOB_PATH, DEFAULT_WARMUP_SECONDS);
exit(1);
}
int main(int argc, char **argv)
{
struct proc_list old_procs = {0, 0};
struct proc_list new_procs = {0, 0};
int read_interval = DEFAULT_READ_INTERVAL;
int procs_to_sample = DEFAULT_PROCS_TO_SAMPLE;
int warmup_seconds = DEFAULT_WARMUP_SECONDS;
int one_shot_mode = false;
const char *proc_glob_path = DEFAULT_PROC_GLOB_PATH;
struct option long_options[] = {
{"interval", required_argument, 0, 'i'},
{"num", required_argument, 0, 'n'},
{"oneshot", no_argument, 0, 'o'},
{"path", required_argument, 0, 'p'},
{"warmup", required_argument, 0, 'w'},
{0, 0, 0, 0},
};
int c;
while ((c = getopt_long(argc, argv, "i:n:w:p:o", long_options, NULL)) != -1) {
switch (c) {
case 'i':
read_interval = atoi(optarg);
if (read_interval < 1)
die("invalid argument");
break;
case 'n':
procs_to_sample = atoi(optarg);
if (procs_to_sample < 1)
die("invalid argument");
break;
case 'o':
one_shot_mode = true;
break;
case 'p':
proc_glob_path = optarg;
break;
case 'w':
warmup_seconds = atoi(optarg);
if (warmup_seconds < 0)
die("invalid argument");
break;
default:
usage_and_die(argv[0]);
}
}
if (optind < argc)
usage_and_die(argv[0]);
setlinebuf(stdout);
ticks_per_sec = sysconf(_SC_CLK_TCK);
sleep(warmup_seconds);
read_procs(&old_procs, proc_glob_path);
for (;;) {
sleep(read_interval);
read_procs(&new_procs, proc_glob_path);
print_top(&new_procs, &old_procs, procs_to_sample, read_interval);
free_procs(&old_procs);
old_procs = new_procs;
if (one_shot_mode)
exit(0);
}
}