* 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.
*/
* 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
# define USEC_FMT "d"
#else
# define USEC_FMT "ld"
#endif
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/resource.h>
static int verbose;
static int memstats;
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;
static pid_t child;
static vmstats_t vmstats;
static int limit;
struct timeval start, end;
static int silent;
static int kill_group;
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);
}
#define STATUS_LINE_LENGTH 4096
#define STATUS_LINE_FORMAT "%s %d"
void
get_mem_stats(pid_t pid)
{
char proc_pid_status[64];
FILE *status;
char line[STATUS_LINE_LENGTH];
int n;
if (!memstats)
return;
n = snprintf(proc_pid_status, sizeof(proc_pid_status), "/proc/%d/status", pid);
if (n < 0 || n == sizeof(proc_pid_status))
assert(0);
status = fopen(proc_pid_status, "r");
if (status == NULL)
return;
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);
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);
if (end.tv_sec - start.tv_sec > limit) {
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);
}
}
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);
#else
rc = sigemptyset(&act.sa_mask);
#endif
assert(rc == 0);
act.sa_flags = SA_ONSTACK;
rc = sigaction(sig, &act, NULL);
assert(rc == 0);
}
#define TV_MSEC tv_usec / 1000
void
print_stats(struct timeval *start, struct timeval *end, struct rusage *ru, int status)
{
unsigned long r;
unsigned long v;
end->tv_sec -= start->tv_sec;
if (end->tv_usec < start->tv_usec) {
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));
}
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;
if (end->tv_sec >= 3600) {
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 ",
end->tv_sec / 60, end->tv_sec % 60, end->tv_usec / 10000);
}
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,
ru->ru_minflt,
ru->ru_nswap);
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;
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 );
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;
rc = setitimer(ITIMER_REAL, &t, NULL);
assert(rc == 0);
info("parent waiting for child\n");
do {
result = wait4(-1 , &status, 0, &ru);
} while (result != child);
gettimeofday(&end, (struct timezone *)0);
info("child has exited\n");
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) {
* This assumes that no child will change the process group or
* invoke sub-commands via bash.
*/
result = setpgid(0 , 0 );
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;
}