File: [Development] / projects / ltp / pan / pan.c (download)
Revision 1.1, Thu Sep 14 21:54:44 2000 UTC (17 years, 1 month ago) by nstraz
Branch: MAIN
Add pan and associated files. This is a lightweight test harness. It works a
lot like runtests.py did, but it is more powerful. See the man page for
details.
|
/*
* Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* Further, this software is distributed without any warranty that it is
* free of the rightful claim of any third person regarding infringement
* or the like. Any license provided herein, whether implied or
* otherwise, applies only to this software file. Patent licenses, if
* any, provided herein do not apply to combinations of this program with
* other software, or any other product whatsoever.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write the Free Software Foundation, Inc., 59
* Temple Place - Suite 330, Boston MA 02111-1307, USA.
*
* Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
* Mountain View, CA 94043, or:
*
* http://www.sgi.com
*
* For further information regarding this notice, see:
*
* http://oss.sgi.com/projects/GenInfo/NoticeExplan/
*
*/
/* $Id: pan.c,v 1.1 2000/09/14 21:54:44 nstraz Exp $ */
#include <errno.h>
#include <string.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <string.h>
#include <time.h>
#include <math.h> /* log10() for subst_pcnt_f */
#include <sys/times.h>
#define NANNY
#include "zoolib.h"
struct coll_entry
{
char *name;
char **argv;
int argc;
char *pcnt_f;
struct coll_entry *next;
};
struct collection
{
int cnt;
struct coll_entry **ary;
};
struct active
{
int pgrp;
int stopping;
time_t stime;
struct coll_entry *cmd;
};
struct orphan_pgrp
{
int pgrp;
struct orphan_pgrp *next;
};
static pid_t run_child (struct coll_entry *colle, FILE * fp,
struct active *active);
static char *slurp (char *file);
static struct collection *get_collection (char *file, int optind, int argc,
char **argv);
static void pids_running (struct active *running, int keep_active);
static int check_pids (struct active *running, int *num_active,
int keep_active, FILE * fp, FILE * logfile,
struct orphan_pgrp *orphans);
static void propagate_signal (struct active *running, int keep_active,
struct orphan_pgrp *orphans, FILE * fp);
static void dump_coll (struct collection *coll);
static char **subst_pcnt_f (struct coll_entry *colle);
static void mark_orphan (struct orphan_pgrp *orphans, pid_t cpid);
static void orphans_running (struct orphan_pgrp *orphans);
static void check_orphans (struct orphan_pgrp *orphans, FILE * fp, int sig);
char **splitstr (char *str, int *argc, char *sep);
static char *panname = NULL;
static char *errmsg;
/* Debug Bits */
int Debug = 0;
#define Dsetup 0x000200 /* one-time set-up */
#define Dshutdown 0x000100 /* killed by signal */
#define Dexit 0x000020 /* exit status */
#define Drunning 0x000010 /* current pids running */
#define Dstartup 0x000004 /* started command */
#define Dstart 0x000002 /* started command */
#define Dwait 0x000001 /* wait interrupted */
main (int argc, char **argv)
{
extern char *optarg;
extern int optind;
int c;
char *active = NULL;
char *filename = "/dev/null";
char *logfilename = NULL;
FILE *logfile = NULL;
struct collection *coll = NULL;
FILE *fp;
pid_t cpid;
struct active *running;
struct orphan_pgrp *orphans, *orph;
int keep_active = 1;
int num_active = 0;
int err, i;
int starts = -1;
int stop;
int go_idle;
int has_brakes = 0;
int sequential = 0;
int fork_in_road = 0;
time_t t;
int exit_stat;
int track_exit_stats = 0;
while ((c = getopt (argc, argv, "s:x:n:a:f:Ad:hSl:ye")) != -1) {
switch (c) {
case 'A':
has_brakes = 1;
track_exit_stats = 1;
break;
case 'x':
keep_active = atoi (optarg);
break;
case 's':
starts = atoi (optarg);
break;
case 'n':
panname = (char *) malloc (strlen (optarg) + 1);
strcpy (panname, optarg);
break;
case 'a':
active = (char *) malloc (strlen (optarg) + 1);
strcpy (active, optarg);
break;
case 'f':
filename = (char *) malloc (strlen (optarg) + 1);
strcpy (filename, optarg);
break;
case 'd':
sscanf (optarg, "%i", &Debug);
break;
case 'S':
sequential = 1;
break;
case 'l':
logfilename = optarg;
break;
case 'y':
fork_in_road = 1;
break;
case 'e':
track_exit_stats = 1;
break;
case 'h':
printf
("Usage: pan -n name [ -SyAeh ] [ -s starts ] [ -x nactive ] [ -l logfile ]\n\t[ -a active-file ] [ -f command-file ] [ -d debug-level ] [cmd]\n");
exit (0);
}
}
if (panname == NULL) {
fprintf (stderr, "pan: Must supply -n\n");
exit (1);
}
if (active == NULL) {
active = zoo_active ();
if (active == NULL) {
fprintf (stderr,
"pan(%s): Must supply -a or set ZOO env variable\n",
panname);
exit (1);
}
}
if (logfilename != NULL) {
time_t startup;
char *s;
if (!strcmp (logfilename, "-")) {
logfile = stdout;
} else {
if ((logfile = fopen (logfilename, "a+")) == NULL) {
fprintf (stderr,
"pan(%s): Error %s (%d) opening log file '%s'\n",
panname, strerror(errno), errno, logfilename);
exit (1);
}
}
time (&startup);
s = ctime (&startup);
*(s + strlen (s) - 1) = '\0';
fprintf (logfile, "startup='%s'\n", s);
}
coll = get_collection (filename, optind, argc, argv);
if (coll->cnt == 0) {
fprintf (stderr,
"pan(%s): Must supply a file collection or a command\n",
panname);
exit (1);
}
if (Debug & Dsetup)
dump_coll (coll);
/* a place to store the pgrps we're watching */
running =
(struct active *) malloc ((keep_active + 1) * sizeof (struct active));
memset (running, 0, keep_active * sizeof (struct active));
running[keep_active].pgrp = -1; /* end sentinel */
/* a head to the orphaned pgrp list */
orphans = (struct orphan_pgrp *) malloc (sizeof (struct orphan_pgrp));
memset (orphans, 0, sizeof (struct orphan_pgrp));
srand48 (time (NULL) ^ (getpid () + (getpid () << 15)));
/* Supply a default for starts. If we are in sequential mode, use
* the number of commands available; otherwise 1.
*/
if (starts == -1)
if (sequential)
starts = coll->cnt;
else
starts = 1;
/* If starts is not infinite, but is less than keep_active,
* then bump it up to keep_active.
*/
if ((starts > 0) && (starts < keep_active))
starts = keep_active;
if (starts == 0) /* The user's view of infinite starts. */
starts = -1; /* Our view of infinite starts. */
if ((fp = open_file (active, "r+", &errmsg)) == NULL) {
fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
exit (1);
}
if (write_active_args (fp, getpid (), panname, argc, argv, &errmsg) == -1) {
fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
exit (1);
}
/* Allocate N spaces for max-arg commands.
* this is an "active file cleanliness" thing
*/
{
char *av[2], bigarg[82];
int t;
t = 1;
memset (bigarg, '.', 81);
bigarg[81] = '\0';
av[0] = bigarg;
av[1] = NULL;
for (c = 0; c < keep_active; c++) {
if (write_active_args (fp, t, panname, 1, av, &errmsg) == -1) {
fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
exit (1);
}
}
for (c = 0; c < keep_active; c++) {
if (clear_active (fp, t, &errmsg) != 1) {
fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
exit (1);
}
}
}
rec_signal = send_signal = 0;
signal (SIGINT, wait_handler);
signal (SIGTERM, wait_handler);
signal (SIGHUP, wait_handler);
signal (SIGUSR1, wait_handler); /* ignore fork_in_road */
signal (SIGUSR2, wait_handler); /* stop the scheduler */
c = 0; /* in this loop, c is the command index */
stop = 0;
exit_stat = 0;
go_idle = 0;
while (1) {
while ((num_active < keep_active) && (starts != 0)) {
if (stop || rec_signal || go_idle)
break;
if (!sequential)
c = lrand48 () % coll->cnt;
/* find a slot for the child */
for (i = 0; i < keep_active; ++i) {
if (running[i].pgrp == 0)
break;
}
if (i == keep_active) {
fprintf (stderr, "pan(%s): Aborting: i == keep_active = %d\n",
panname, i);
wait_handler (SIGINT);
exit_stat++;
break;
}
cpid = run_child (coll->ary[c], fp, running + i);
if (cpid != -1) {
++num_active;
if (starts > 0)
--starts;
}
if (sequential)
if (++c >= coll->cnt)
c = 0;
} /* while( (num_active < keep_active) && (starts != 0) ) */
if (starts == 0)
++stop;
if (rec_signal) {
/* propagate everything except sigusr2 */
if (rec_signal == SIGUSR2) {
if (fork_in_road)
++go_idle;
else
++stop;
signal (rec_signal, wait_handler);
rec_signal = send_signal = 0;
} else {
if (rec_signal == SIGUSR1)
fork_in_road = 0;
propagate_signal (running, keep_active, orphans, fp);
if (fork_in_road)
++go_idle;
else
++stop;
}
}
err = check_pids (running, &num_active, keep_active, fp,
logfile, orphans);
if (Debug & Drunning) {
pids_running (running, keep_active);
orphans_running (orphans);
}
if (err) {
if (fork_in_road)
++go_idle;
if (track_exit_stats)
exit_stat++;
if (has_brakes) {
printf ("pan(%s): All stop!%s\n", panname,
go_idle ? " (idling)" : "");
wait_handler (SIGINT);
}
}
if (stop && (num_active == 0))
break;
if (go_idle && (num_active == 0)) {
go_idle = 0; /* It is idle, now resume scheduling. */
wait_handler (0); /* Reset the signal ratchet. */
}
}
/* Wait for orphaned pgrps */
while (1) {
for (orph = orphans; orph != NULL; orph = orph->next) {
if (orph->pgrp == 0)
continue;
/* Yes, we have orphaned pgrps */
sleep (5);
if (!rec_signal) {
/* force an artificial signal, move us
* through the signal ratchet.
*/
wait_handler (SIGINT);
}
propagate_signal (running, keep_active, orphans, fp);
if (Debug & Drunning)
orphans_running (orphans);
break;
}
if (orph == NULL)
break;
}
signal (SIGINT, SIG_DFL);
if (clear_active (fp, getpid (), &errmsg) != 1) {
fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
++exit_stat;
}
fclose (fp);
exit (exit_stat);
}
static void
pids_running (struct active *running, int keep_active)
{
int i;
printf ("pids still running: ");
for (i = 0; i < keep_active; ++i) {
if (running[i].pgrp != 0)
printf ("%d ", running[i].pgrp);
}
printf ("\n");
}
static void
propagate_signal (struct active *running, int keep_active,
struct orphan_pgrp *orphans, FILE * fp)
{
int i;
if (Debug & Dshutdown)
printf ("pan was signaled with sig %d...\n", rec_signal);
for (i = 0; i < keep_active; ++i) {
if (running[i].pgrp == 0)
continue;
if (Debug & Dshutdown)
printf (" propagating sig %d to %d\n",
send_signal, -running[i].pgrp);
if (kill (-running[i].pgrp, send_signal) != 0) {
fprintf (stderr,
"pan(%s): kill(%d,%d) failed on tag (%s). errno:%d %s\n",
panname, -running[i].pgrp, send_signal,
running[i].cmd->name, errno, SYSERR);
}
running[i].stopping = 1;
}
check_orphans (orphans, fp, send_signal);
signal (rec_signal, wait_handler);
rec_signal = send_signal = 0;
}
static int
check_pids (struct active *running, int *num_active, int keep_active,
FILE * fp, FILE * logfile, struct orphan_pgrp *orphans)
{
int w;
pid_t cpid;
int stat_loc;
int ret = 0;
int i;
time_t t;
char *status;
int signaled = 0;
struct tms tms1, tms2;
clock_t tck;
check_orphans (orphans, fp, 0);
tck = times (&tms1);
if (tck == -1) {
fprintf (stderr, "pan(%s): times(&tms1) failed. errno:%d %s\n",
panname, errno, SYSERR);
}
cpid = wait (&stat_loc);
tck = times (&tms2);
if (tck == -1) {
fprintf (stderr, "pan(%s): times(&tms2) failed. errno:%d %s\n",
panname, errno, SYSERR);
}
if (cpid < 0) {
if (errno == EINTR) {
if (Debug)
fprintf (stderr, "pan(%s): wait() interrupted\n", panname);
} else if (errno != ECHILD) {
fprintf (stderr, "pan(%s): wait() failed. errno:%d %s\n",
panname, errno, SYSERR);
}
} else if (cpid > 0) {
if (WIFSIGNALED (stat_loc)) {
w = WTERMSIG (stat_loc);
status = "signaled";
if (Debug & Dexit)
printf ("child %d terminated with signal %d\n", cpid, w);
--*num_active;
signaled = 1;
} else if (WIFEXITED (stat_loc)) {
w = WEXITSTATUS (stat_loc);
status = "exited";
if (Debug & Dexit)
printf ("child %d exited with status %d\n", cpid, w);
--*num_active;
if (w != 0)
ret++;
} else if (WIFSTOPPED (stat_loc)) { /* should never happen */
w = WSTOPSIG (stat_loc);
status = "stopped";
ret++;
} else { /* should never happen */
w = 0;
status = "unknown";
ret++;
}
for (i = 0; i < keep_active; ++i) {
if (running[i].pgrp == cpid) {
if ((w == 130) && running[i].stopping &&
(strcmp (status, "exited") == 0)) {
/* The child received sigint, but
* did not trap for it? Compensate
* for it here.
*/
w = 0;
ret--; /* undo */
if (Debug & Drunning)
printf
("pan(%s): tag=%s exited 130, known to be signaled; will give it an exit 0.\n",
panname, running[i].cmd->name);
}
if (logfile != NULL) {
time (&t);
fprintf (logfile,
"tag=%s stime=%d dur=%d exit=%s stat=%d core=%s cu=%d cs=%d\n",
running[i].cmd->name, running[i].stime,
t - running[i].stime, status, w,
(stat_loc & 0200) ? "yes" : "no",
tms2.tms_cutime - tms1.tms_cutime,
tms2.tms_cstime - tms1.tms_cstime);
fflush (logfile);
}
/* If signaled and we weren't expecting
* this to be stopped then the proc
* had a problem.
*/
if (signaled && !running[i].stopping)
ret++;
running[i].pgrp = 0;
if (clear_active (fp, cpid, &errmsg) == -1) {
fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
exit (1);
}
/* Check for orphaned pgrps */
if ((kill (-cpid, 0) == 0) || (errno == EPERM)) {
if (write_active_args (fp, cpid, "panorphan",
running[i].cmd->argc,
running[i].cmd->argv,
&errmsg) == -1) {
fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
exit (1);
}
mark_orphan (orphans, cpid);
/* status of kill doesn't matter */
kill (-cpid, SIGTERM);
}
break;
}
}
}
return ret;
}
static pid_t
run_child (struct coll_entry *colle, FILE * fp, struct active *active)
{
int cpid;
if ((cpid = fork ()) < 0) {
fprintf (stderr, "pan(%s): fork failed. errno:%d %s\n",
panname, errno, SYSERR);
return -1;
} else if (cpid == 0) {
/* child */
char **eargv;
fclose (fp);
setpgrp ();
if (colle->pcnt_f != NULL) {
eargv = subst_pcnt_f (colle);
} else {
eargv = colle->argv;
}
/* execute command */
execvp (eargv[0], eargv);
fprintf (stderr,
"pan(%s): execvp of '%s' (tag %s) failed. errno:%d %s\n",
panname, eargv[0], colle->name, errno, SYSERR);
exit (errno);
}
/* parent */
time (&active->stime);
active->pgrp = cpid;
active->stopping = 0;
active->cmd = colle;
if (write_active_args
(fp, cpid, colle->name, colle->argc, colle->argv, &errmsg) == -1) {
fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
exit (1);
}
if (Debug & Dstartup)
printf ("started %s cpid=%d at %s",
colle->name, cpid, ctime (&active->stime));
if (Debug & Dstart) {
int ac;
printf ("Executing test = %s as ", colle->name);
for (ac = 0; ac < colle->argc; ac++) {
printf ("%s ", colle->argv[ac]);
}
printf ("\n");
}
return cpid;
}
static char **
subst_pcnt_f (struct coll_entry *colle)
{
char **eargv;
char *p;
int i;
eargv = (char **) malloc ((colle->argc + 1) * sizeof (char *));
for (i = 0; i < colle->argc; ++i) {
if ((p = strstr (colle->argv[i], "%f")) != NULL) {
/* simple, for now */
static int counter = 1;
char *b, *p2;
int pidlen, counterlen;
*p = '\0'; /* cut off at % */
p2 = p + 2; /* stuff that follows %f */
pidlen = 1 + (int) log10 ((double) getpid ());
counterlen = 1 + (int) log10 ((double) counter);
b = (char *) malloc (strlen (colle->argv[i]) +
pidlen + 1 + counterlen + strlen (p2) + 1);
sprintf (b, "%s%d_%d%s",
colle->argv[i], getpid (), counter++, p2);
*p = '%'; /* restore % */
eargv[i] = b;
} else {
eargv[i] = colle->argv[i];
}
}
eargv[i] = NULL;
return eargv;
}
static struct collection *
get_collection (char *file, int optind, int argc, char **argv)
{
char *buf, *a, *b;
struct coll_entry *head, *p, *n;
struct collection *coll;
int i;
buf = slurp (file);
coll = (struct collection *) malloc (sizeof (struct collection));
coll->cnt = 0;
head = p = n = NULL;
a = b = buf;
while (*b != '\0') {
if ((b = strchr (a, '\n')) != NULL)
*b = '\0';
if ((*a != '#') && (*a != '\0') && (*a != ' ')) {
if (head == NULL) {
head =
(struct coll_entry *) malloc (sizeof (struct coll_entry));
head->pcnt_f = strstr (a, "%f");
head->argv = splitstr (a, &head->argc, 0);
head->name = head->argv[0];
head->argv++; /* remove name from command */
head->argc--;
head->next = NULL;
p = head;
} else {
n = (struct coll_entry *) malloc (sizeof (struct coll_entry));
p->next = n;
n->pcnt_f = strstr (a, "%f");
n->argv = splitstr (a, &n->argc, 0);
n->name = n->argv[0];
n->argv++; /* remove name from command */
n->argc--;
n->next = NULL;
p = n;
}
coll->cnt++;
}
a += strlen (a) + 1;
b = a;
}
free (buf);
/* is there something on the commandline to be counted? */
if (optind < argc) {
char **args;
char *pcnt_f = NULL;
args = (char **) malloc ((argc - optind + 1) * sizeof (char *));
/* fill arg list */
for (i = 0; optind < argc; ++optind, ++i) {
args[i] = argv[optind];
if ((pcnt_f == NULL) && ((strstr (args[i], "%f")) != NULL)) {
pcnt_f = args[i];
}
}
args[i] = NULL;
if (head == NULL) {
head = (struct coll_entry *) malloc (sizeof (struct coll_entry));
head->pcnt_f = pcnt_f;
head->argv = args;
head->name = "cmdln";
head->argc = i;
head->next = NULL;
} else {
n = (struct coll_entry *) malloc (sizeof (struct coll_entry));
p->next = n;
n->pcnt_f = pcnt_f;
n->argv = args;
n->name = "cmdln";
n->argc = i;
n->next = NULL;
}
coll->cnt++;
}
/* get an array */
coll->ary = (struct coll_entry **) malloc (coll->cnt *
sizeof (struct coll_entry *));
/* fill the array */
i = 0;
n = head;
while (n != NULL) {
coll->ary[i] = n;
n = n->next;
++i;
}
if (i != coll->cnt)
fprintf (stderr, "pan(%s): i doesn't match cnt\n", panname);
return coll;
}
static char *
slurp (char *file)
{
char *buf;
int fd;
struct stat sbuf;
if ((fd = open (file, O_RDONLY)) < 0) {
fprintf (stderr, "pan(%s): open(%s,O_RDONLY) failed. errno:%d %s\n",
panname, file, errno, SYSERR);
exit (1);
}
if (fstat (fd, &sbuf) < 0) {
fprintf (stderr, "pan(%s): fstat(%s) failed. errno:%d %s\n",
panname, file, errno, SYSERR);
exit (1);
}
buf = (char *) malloc (sbuf.st_size + 1);
if (read (fd, buf, sbuf.st_size) != sbuf.st_size) {
fprintf (stderr, "pan(%s): slurp failed. errno:%d %s\n",
panname, errno, SYSERR);
exit (1);
}
buf[sbuf.st_size] = '\0';
close (fd);
return buf;
}
static void
check_orphans (struct orphan_pgrp *orphans, FILE * fp, int sig)
{
struct orphan_pgrp *orph;
for (orph = orphans; orph != NULL; orph = orph->next) {
if (orph->pgrp == 0)
continue;
if (Debug & Dshutdown)
printf (" propagating sig %d to orphaned pgrp %d\n",
sig, -(orph->pgrp));
if (kill (-(orph->pgrp), sig) != 0) {
if (errno == ESRCH) {
/* This pgrp is now empty */
if (clear_active (fp, orph->pgrp, &errmsg) == -1) {
fprintf (stderr, "pan(%s): %s\n", panname, errmsg);
}
orph->pgrp = 0;
} else {
fprintf (stderr,
"pan(%s): kill(%d,%d) on orphaned pgrp failed. errno:%d %s\n",
panname, -(orph->pgrp), sig, errno, SYSERR);
}
}
}
}
static void
mark_orphan (struct orphan_pgrp *orphans, pid_t cpid)
{
struct orphan_pgrp *orph;
for (orph = orphans; orph != NULL; orph = orph->next) {
if (orph->pgrp == 0)
break;
}
if (orph == NULL) {
/* make a new struct */
orph = (struct orphan_pgrp *) malloc (sizeof (struct orphan_pgrp));
/* plug in the new struct just after the head */
orph->next = orphans->next;
orphans->next = orph;
}
orph->pgrp = cpid;
}
static void
orphans_running (struct orphan_pgrp *orphans)
{
struct orphan_pgrp *orph;
printf ("orphans still running: ");
for (orph = orphans; orph != NULL; orph = orph->next) {
if (orph->pgrp != 0)
printf ("%d ", -(orph->pgrp));
}
printf ("\n");
}
static void
dump_coll (struct collection *coll)
{
int x, i;
for (i = 0; i < coll->cnt; ++i) {
printf ("coll %d\n", i);
printf (" name=%s #args=%d\n", coll->ary[i]->name,
coll->ary[i]->argc);
for (x = 0; coll->ary[i]->argv[x]; ++x) {
printf (" argv[%d] = (%s)\n", x, coll->ary[i]->argv[x]);
}
}
}