blob: e6d93ac94f7e5b15e4a6af8f592602b833cd862a [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2011 Google, Inc. All rights reserved.
* Copyright (c) 2003-2008 VMware, Inc. All rights reserved.
* **********************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
/* runstats.c
* by Derek Bruening
*
* similar to /usr/bin/time, but adds maximum memory
* usage statistics, implemented via sampling every 500ms
*
* also has texec functionality: will kill child after specified
* time limit expires
*/
#include "configure.h"
#ifdef MACOS
/* struct timeval.tv_usec is int, not long int */
# define USEC_FMT "d"
#else
# define USEC_FMT "ld"
#endif
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h> /* for wait and mmap */
#include <sys/wait.h> /* for wait */
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/time.h> /* itimer */
#include <stdlib.h> /* getenv */
#include <string.h> /* strstr */
#include <signal.h> /* killpg */
#include <sys/resource.h> /* struct rusage */
static int verbose;
static int memstats;
/* just use single-arg handlers */
typedef void (*handler_t)(int);
typedef void (*handler_3_t)(int, siginfo_t *, void *);
typedef struct {
unsigned long VmSize;
unsigned long VmLck;
unsigned long VmRSS;
unsigned long VmData;
unsigned long VmStk;
unsigned long VmExe;
unsigned long VmLib;
} vmstats_t;
/* global vars for access in signal handler */
static pid_t child;
static vmstats_t vmstats;
static int limit; /* in seconds */
struct timeval start, end;
static int silent; /* whether to print anything */
static int kill_group; /* use killpg instead of kill */
static FILE *FP;
static void
info(const char *fmt, ...)
{
va_list ap;
if (silent || !verbose)
return;
va_start(ap, fmt);
vfprintf(FP, fmt, ap);
va_end(ap);
}
/***************************************************************************/
/* /proc/self/status reader */
/* these are defined in /usr/src/linux/fs/proc/array.c */
#define STATUS_LINE_LENGTH 4096
#define STATUS_LINE_FORMAT "%s %d"
void
get_mem_stats(pid_t pid)
{
char proc_pid_status[64]; /* file name */
FILE *status;
char line[STATUS_LINE_LENGTH];
int n;
if (!memstats)
return;
// open file's /proc/id/status file
n = snprintf(proc_pid_status, sizeof(proc_pid_status),
"/proc/%d/status", pid);
if (n<0 || n==sizeof(proc_pid_status))
assert(0); /* paranoia */
status = fopen(proc_pid_status,"r");
if (status == NULL)
return; /* child already exited */
while(!feof(status)){
unsigned val = 0;
char prefix[STATUS_LINE_LENGTH];
int len;
if (NULL==fgets(line, sizeof(line), status))
break;
if (strstr(line, "VmSize") != 0) {
len = sscanf(line, STATUS_LINE_FORMAT, prefix, &val);
assert(len == 2);
if (val > vmstats.VmSize)
vmstats.VmSize = val;
info("VmSize is %d kB\n", val);
} else if (strstr(line, "VmLck") != 0) {
len = sscanf(line, STATUS_LINE_FORMAT, prefix, &val);
assert(len == 2);
if (val > vmstats.VmLck)
vmstats.VmLck = val;
info("VmLck is %d kB\n", val);
} else if (strstr(line, "VmRSS") != 0) {
len = sscanf(line, STATUS_LINE_FORMAT, prefix, &val);
assert(len == 2);
if (val > vmstats.VmRSS)
vmstats.VmRSS = val;
info("VmRSS is %d kB\n", val);
} else if (strstr(line, "VmData") != 0) {
len = sscanf(line, STATUS_LINE_FORMAT, prefix, &val);
assert(len == 2);
if (val > vmstats.VmData)
vmstats.VmData = val;
info("VmData is %d kB\n", val);
} else if (strstr(line, "VmStk") != 0) {
len = sscanf(line, STATUS_LINE_FORMAT, prefix, &val);
assert(len == 2);
if (val > vmstats.VmStk)
vmstats.VmStk = val;
info("VmStk is %d kB\n", val);
} else if (strstr(line, "VmExe") != 0) {
len = sscanf(line, STATUS_LINE_FORMAT, prefix, &val);
assert(len == 2);
if (val > vmstats.VmExe)
vmstats.VmExe = val;
info("VmExe is %d kB\n", val);
} else if (strstr(line, "VmLib") != 0) {
len = sscanf(line, STATUS_LINE_FORMAT, prefix, &val);
assert(len == 2);
if (val > vmstats.VmLib)
vmstats.VmLib = val;
info("VmLib is %d kB\n", val);
/* final one, so break */
break;
}
}
fclose(status);
}
/***************************************************************************/
static void
signal_handler(int sig)
{
if (sig == SIGALRM) {
info("just got SIGALRM for %d =>\n", child);
if (limit > 0) {
gettimeofday(&end, (struct timezone *) 0);
info("SIGALRM: comparing limit %d s vs. elapsed %ld s\n",
limit, end.tv_sec - start.tv_sec);
/* for limit, ignore fractions of second */
if (end.tv_sec - start.tv_sec > limit) {
/* could use SIGTERM, but on win32 cygwin need SIGKILL */
if (kill_group) {
killpg(child, SIGKILL);
} else {
kill(child, SIGKILL);
}
fprintf(FP, "Timeout after %d seconds\n", limit);
exit(-1);
}
}
get_mem_stats(child);
} else if (sig == SIGCHLD) {
info("just got SIGCHLD for %d\n", child);
}
}
/* set up signal_handler as the handler for signal "sig" */
static void
intercept_signal(int sig, handler_t handler)
{
int rc;
struct sigaction act;
act.sa_sigaction = (handler_3_t) handler;
#if BLOCK_IN_HANDLER
rc = sigfillset(&act.sa_mask); /* block all signals within handler */
#else
rc = sigemptyset(&act.sa_mask); /* no signals are blocked within handler */
#endif
assert(rc == 0);
act.sa_flags = SA_ONSTACK;
/* arm the signal */
rc = sigaction(sig, &act, NULL);
assert(rc == 0);
}
#define TV_MSEC tv_usec / 1000
/* some of this code is based on /usr/bin/time source code */
void
print_stats(struct timeval *start, struct timeval *end,
struct rusage *ru, int status)
{
unsigned long r; /* Elapsed real milliseconds. */
unsigned long v; /* Elapsed virtual (CPU) milliseconds. */
end->tv_sec -= start->tv_sec;
if (end->tv_usec < start->tv_usec) {
/* Manually carry a one from the seconds field. */
end->tv_usec += 1000000;
--end->tv_sec;
}
end->tv_usec -= start->tv_usec;
if (WIFSTOPPED (status)) {
fprintf(FP, "Command stopped by signal %d\n",
WSTOPSIG (status));
} else if (WIFSIGNALED (status)) {
fprintf(FP, "Command terminated by signal %d\n",
WTERMSIG (status));
} else if (WIFEXITED (status) && WEXITSTATUS (status)) {
fprintf(FP, "Command exited with non-zero status %d\n",
WEXITSTATUS (status));
}
/* Convert all times to milliseconds. Occasionally, one of these values
comes out as zero. Dividing by zero causes problems, so we first
check the time value. If it is zero, then we take `evasive action'
instead of calculating a value. */
r = end->tv_sec * 1000 + end->tv_usec / 1000;
v = ru->ru_utime.tv_sec * 1000 + ru->ru_utime.TV_MSEC +
ru->ru_stime.tv_sec * 1000 + ru->ru_stime.TV_MSEC;
/* Elapsed real (wall clock) time. */
if (end->tv_sec >= 3600) { /* One hour -> h:m:s. */
fprintf(FP, "%ld:%02ld:%02ldelapsed ",
end->tv_sec / 3600,
(end->tv_sec % 3600) / 60,
end->tv_sec % 60);
} else {
fprintf(FP, "%ld:%02ld.%02"USEC_FMT"elapsed ", /* -> m:s. */
end->tv_sec / 60,
end->tv_sec % 60,
end->tv_usec / 10000);
}
/* % cpu is (total cpu time)/(elapsed time). */
if (r > 0)
fprintf(FP, "%lu%%CPU ", (v * 100 / r));
else
fprintf(FP, "?%%CPU ");
fprintf(FP, "%ld.%02"USEC_FMT"user ",
ru->ru_utime.tv_sec, ru->ru_utime.TV_MSEC / 10);
fprintf(FP, "%ld.%02"USEC_FMT"system ",
ru->ru_stime.tv_sec, ru->ru_stime.TV_MSEC / 10);
fprintf(FP, "(%ldmajor+%ldminor)pagefaults %ldswaps\n",
ru->ru_majflt,/* Major page faults. */
ru->ru_minflt,/* Minor page faults. */
ru->ru_nswap); /* times swapped out */
fprintf(FP, "(%lu tot, %lu RSS, %lu data, %lu stk, %lu exe, %lu lib)k\n",
vmstats.VmSize, vmstats.VmRSS, vmstats.VmData,
vmstats.VmStk, vmstats.VmExe, vmstats.VmLib);
if (memstats) {
fprintf(FP, "Memory usage:\n");
fprintf(FP, "\tVmSize: %ld kB\n", vmstats.VmSize);
fprintf(FP, "\tVmLck: %ld kB\n", vmstats.VmLck);
fprintf(FP, "\tVmRSS: %ld kB\n", vmstats.VmRSS);
fprintf(FP, "\tVmData: %ld kB\n", vmstats.VmData);
fprintf(FP, "\tVmStk: %ld kB\n", vmstats.VmStk);
fprintf(FP, "\tVmExe: %ld kB\n", vmstats.VmExe);
fprintf(FP, "\tVmLib: %ld kB\n", vmstats.VmLib);
}
}
int usage(char *us)
{
fprintf(FP, "Usage: %s [-s limit_sec | -m limit_min | -h limit_hr]\n"
" [-killpg] [-f] [-silent] [-env var value] <program> <args...>\n", us);
return 1;
}
int main(int argc, char *argv[])
{
int rc;
struct itimerval t;
struct rusage ru;
int arg_offs = 1;
FP = stderr; /* default */
if (argc < 2) {
return usage(argv[0]);
}
while (argv[arg_offs][0] == '-') {
if (strcmp(argv[arg_offs], "-s") == 0) {
if (argc <= arg_offs+1)
return usage(argv[0]);
limit = atoi(argv[arg_offs+1]);
arg_offs += 2;
} else if (strcmp(argv[arg_offs], "-m") == 0) {
if (argc <= arg_offs+1)
return usage(argv[0]);
limit = atoi(argv[arg_offs+1])*60;
arg_offs += 2;
} else if (strcmp(argv[arg_offs], "-h") == 0) {
if (argc <= arg_offs+1)
return usage(argv[0]);
limit = atoi(argv[arg_offs+1])*3600;
arg_offs += 2;
} else if (strcmp(argv[arg_offs], "-v") == 0) {
verbose = 1;
arg_offs += 1;
} else if (strcmp(argv[arg_offs], "-silent") == 0) {
silent = 1;
arg_offs += 1;
} else if (strcmp(argv[arg_offs], "-mem") == 0) {
memstats = 1;
arg_offs += 1;
} else if (strcmp(argv[arg_offs], "-killpg") == 0) {
kill_group = 1;
arg_offs += 1;
} else if (strcmp(argv[arg_offs], "-f") == 0) {
char fname[32];
int len = snprintf(fname, sizeof(fname)/sizeof(fname[0]),
"runstats-%d", getpid());
if (len <= 0)
return 1;
FP = fopen(fname, "w");
arg_offs += 1;
} else if (strcmp(argv[arg_offs], "-env") == 0) {
if (argc <= arg_offs+2)
return usage(argv[0]);
info("setting env var \"%s\" to \"%s\"\n",
argv[arg_offs+1], argv[arg_offs+2]);
rc = setenv(argv[arg_offs+1], argv[arg_offs+2], 1/*overwrite*/);
if (rc != 0 ||
strcmp(getenv(argv[arg_offs+1]), argv[arg_offs+2]) != 0) {
fprintf(FP, "error in setenv of \"%s\" to \"%s\"\n",
argv[arg_offs+1], argv[arg_offs+2]);
fprintf(FP, "setenv returned %d\n", rc);
fprintf(FP, "env var \"%s\" is now \"%s\"\n",
argv[arg_offs+1], getenv(argv[arg_offs+1]));
exit(-1);
}
arg_offs += 3;
} else {
return usage(argv[0]);
}
if (limit < 0) {
return usage(argv[0]);
}
if (argc - arg_offs < 1)
return usage(argv[0]);
}
gettimeofday(&start, (struct timezone *) 0);
child = fork();
if (child < 0) {
perror("ERROR on fork");
return 1;
} else if (child > 0) {
pid_t result;
int status;
get_mem_stats(child);
intercept_signal(SIGALRM, (handler_t) signal_handler);
intercept_signal(SIGCHLD, (handler_t) signal_handler);
t.it_interval.tv_sec = 0;
t.it_interval.tv_usec = 500000;
t.it_value.tv_sec = 0;
t.it_value.tv_usec = 500000;
/* must use real timer so timer decrements while process suspended */
rc = setitimer(ITIMER_REAL, &t, NULL);
assert(rc == 0);
info("parent waiting for child\n");
/* need loop since SIGALRM will interrupt us */
do {
result = wait3(&status, 0, &ru);
} while (result != child);
gettimeofday(&end, (struct timezone *) 0);
info("child has exited\n");
/* turn off timer */
t.it_interval.tv_usec = 0;
t.it_value.tv_usec = 0;
rc = setitimer(ITIMER_REAL, &t, NULL);
assert(rc == 0);
if (!silent)
print_stats(&start, &end, &ru, status);
return (status == 0 ? 0 : 1);
} else {
int result;
if (kill_group) {
/* Change the process group so we can relibably kill all children.
* This assumes that no child will change the process group or
* invoke sub-commands via bash.
*/
result = setpgid(0 /* my pid */, 0 /* set pgid to my pid */);
if (result < 0) {
perror("ERROR in setpgid");
fprintf(FP, " trying to run %s\n", argv[arg_offs]);
return 1;
}
}
result = execvp(argv[arg_offs], argv+arg_offs);
if (result < 0) {
perror("ERROR in execvp");
fprintf(FP, " trying to run %s\n", argv[arg_offs]);
return 1;
}
}
return 0;
}