

/*
	Synthetic workload generator
*/

#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>


#define TOK_SEQ 0
#define TOK_PAR 1
#define TOK_RAND 2
#define TOK_LBRACE 3
#define TOK_RBRACE 4
#define TOK_CMD 5
#define TOK_STRING 6
#define TOK_NUM 7
#define TOK_EOF 8
#define TOK_PID 9

#define MAX_COMMAND 256

typedef struct _tok {
	int	token;   /* One of the #defined types */
	char	string[MAX_COMMAND+1];
	int	number;
} tok;

#define JOB_PAR 0
#define JOB_SEQ 1
#define JOB_RAND 2
#define JOB_CMD 3

#define MAX_JOBLIST 64

typedef struct _job {
	int	type;
	union {
		struct { int count; } par_params;
		struct { int count; } seq_params;
		struct { int count; int seed; int low; int high; } rand_params;
		struct { char *cmd } cmd_params;
	} params;
	int jobcount;
	struct _job *joblist[MAX_JOBLIST];
} job;

#define PAR params.par_params
#define SEQ params.seq_params
#define RAND params.rand_params
#define CMD params.cmd_params

#define SEED_PID -1

void next_token();

job *parse_job();
job *parse_seq();
job *parse_par();
job *parse_rand();
job *parse_cmd();
void parse_joblist();

void do_job();
void do_seq_job();
void do_par_job();
void do_rand_job();
void do_command();

void dump_job();

char errbuff[MAX_COMMAND + 32];
int lineno;

void
main(ac, av)
int	ac;
char	*av[];
{
	job	*jobtree;
	tok	t;
	int	debug;

	lineno = 1;
	debug = 0;

	switch(ac) {
	case 1:
		break;
	case 3:
		debug = 1;
		/* Fall through */
	case 2:
		if(freopen(av[1], "r", stdin) == NULL) {
			fprintf(stderr, "Could not open file %s\n", av[1]);
			exit(-1);
		}
		break;
	default:
		fprintf(stderr, "Usage: %s [<workload file>]\n", av[0]);
		exit(-1);
		break;
	}

	/* Suck up the first token */

	next_token(&t);
	jobtree = parse_job(&t);

	if(debug)
		dump_job(jobtree, 0);

	do_job(jobtree);
	exit(0);
}

/*
	Generic error handler. Mostly used by the parser, since
	it does report a line number.
*/

error(e)
char	*e;
{
	fprintf(stderr, "ERROR at line %d: %s\n", lineno, e);
	exit(-1);
}

/*
	Do the job tree. Recursive descent, more or less, creating
	processes on the way down, terminating at leaf nodes.
*/

void
do_job(j)
job	*j;
{
	switch(j->type) {
	case JOB_PAR:
		do_par_job(j);
		break;
	case JOB_SEQ:
		do_seq_job(j);
		break;
	case JOB_RAND:
		do_rand_job(j);
		break;
	case JOB_CMD:
		/* This just does it, and does not return */
		do_command(j->CMD.cmd);
		break;
	}
	/* Bail out here */
	exit(0);
}

void
do_par_job(j)
job	*j;
{
	int	k, l;
	int	count;
	int	status;
	int	pid;

	/* Launch all the jobs as fast as we can */

	for(k = 0; k < j->PAR.count; k++) {
		for(l = 0; l < j->jobcount; l++) {
			pid = fork();
			if(pid < 0) {
				perror("fork");
				error("fork() failed");
			}

			if(pid == 0) {
				/* Child */
				do_job(j->joblist[l]);
			}
		}
	}

	/* Now wait for the children to all terminate */

	count = j->PAR.count * j->jobcount;

	while(count > 0) {
		wait(&status);
		if((status & 0xff) != 0xff)
			count--;
	}
}

void
do_seq_job(j)
job	*j;
{
	int	k, l;
	int	pid;
	int	status;

	/* Launch all the jobs in order, waiting for each one */
	/* to terminate before moving on to the next */

	for(k = 0; k < j->SEQ.count; k++) {
		for(l = 0; l < j->jobcount; l++) {
			pid = fork();
			if(pid < 0) {
				perror("fork");
				error("fork() failed");
			}

			if(pid == 0) {
				/* Child */
				do_job(j->joblist[l]);
			} else {
				/* Parent. Wait for child to stop */
				do {
					wait(&status);
				} while((status & 0xff) == 0xff);
			}
		}
	}
}

void
do_rand_job(j)
job	*j;
{
	int	k, l;
	int	count;
	int	status;
	int	pid;
	int	interval;

	/* Seed the RNG with the specified value */

	if(j->RAND.seed == SEED_PID) {
		srandom(getpid());
	} else {
		srandom(j->RAND.seed);
	}

	/* Now launch all the jobs, waiting a pseudo-random interval */
	/* between each job is launched, perparameters supplied.     */

	for(k = 0; k < j->RAND.count; k++) {
		for(l = 0; l < j->jobcount; l++) {

			interval = j->RAND.low +
				(random() % (j->RAND.high - j->RAND.low));
			sleep(interval);
			pid = fork();
			if(pid < 0) {
				perror("fork");
				error("fork() failed");
			}

			if(pid == 0) {
				/* Child */
				do_job(j->joblist[l]);
			}
		}
	}

	/* now wait for them all to end, and finally return */

	count = j->RAND.count * j->jobcount;

	while(count > 0) {
		wait(&status);
		if((status & 0xff) != 0xff)
			count--;
	}
}

/* Stupid tokenize-and-execute stolen from my archives */

#define ISSPACE(c) ((c) == ' ' || (c) == '\t')
#define ISTOKEN(c) ((c) != ' ' && (c) != '\t' && (c) != '\0')

void
do_command(c)
char	*c;
{
	int	i;
	char	*p, *token[MAX_COMMAND];

	/* Tokenise at whitespace */

	i = 0;
	p = c;

	/* Kill leading whitespace */
	while(ISSPACE(*p)) p++;
	if(*p == '\0') {
		exit(0); /* Null command? */
	}

	token[i++] = p;
	while(*p != '\0') {
		/* Walk over token */
		while(ISTOKEN(*p)) p++;
		if(*p == '\0') {
			token[i] = NULL;
			break;
		}

		/* Terminate token */
		*p++ = '\0';
		/* Walk over whitespace */
		while(ISSPACE(*p)) p++;
		if(*p == '\0') {
			token[i] = NULL;
		} else {
			token[i++] = p;
		}
	}

	/* Have a go at transmogrifying ourself into the command */

	execvp(token[0], token);

	perror("execvp");
	fprintf(stderr, "execvp() failed.\n");
	exit(0);
}

/*
	Trivial recursive descent parser for job specifications.
	Exits when it hits an error. Not as robust as it should be.

workload -> job
job -> seq | par | rand | cmd
seq -> "sequential" NUM # number of time to do it
	"{" joblist "}"
	|
	"sequential" # NOTE: no NUM
	"{" joblist "}"

par -> "parallel" NUM # number of parallel jobs to start
	"{" joblist "}"
	|
	"parallel"  # NOTE: no NUM
	"{" joblist "}"

rand -> "random" NUM # number of times to do it
		NUM # seed random number generator with this
		NUM NUM # low and high values to bound start gap
	"{" joblist "}"

cmd -> "command"  "\"" TEXT "\""  # text is \ escaped text



*/

job *
parse_job(t)
tok	*t;
{
	switch(t->token) {
	case TOK_PAR:
		next_token(t);
		return parse_par(t);
		break;
	case TOK_SEQ:
		next_token(t);
		return parse_seq(t);
		break;
	case TOK_RAND:
		next_token(t);
		return parse_rand(t);
		break;
	case TOK_CMD:
		next_token(t);
		return parse_cmd(t);
		break;
	default:
		return NULL;
	}
	return NULL;
}
/*
	Parallel job specifier
*/

job *
parse_par(t)
tok	*t;
{
	job	*newjob;

	newjob = (job *) malloc(sizeof(job));
	if(newjob == NULL)
		error("Out of memory");

	newjob->type = JOB_PAR;
	if(t->token == TOK_NUM) {
		newjob->PAR.count = t->number;
		next_token(t);
	} else {
		newjob->PAR.count = 1;
	}

	if(t->token != TOK_LBRACE) {
		error("Expected {");
	}
	next_token(t);

	parse_joblist(t, newjob);

	if(t->token != TOK_RBRACE) {
		error("Expected }");
	}
	return newjob;
}
/*
	Sequential job specifier. Should be rolled in with parse_par()
	since it's pretty much the same. I am lazy, and it would not be
	so lucid.
*/

job *
parse_seq(t)
tok	*t;
{
	job	*newjob;

	newjob = (job *) malloc(sizeof(job));
	if(newjob == NULL)
		error("Out of memory");

	newjob->type = JOB_SEQ;
	if(t->token == TOK_NUM) {
		newjob->SEQ.count = t->number;
		next_token(t);
	} else {
		newjob->SEQ.count = 1;
	}

	if(t->token != TOK_LBRACE) {
		error("Expected {");
	}
	next_token(t);

	parse_joblist(t, newjob);

	if(t->token != TOK_RBRACE) {
		error("Expected }");
	}
	return newjob;
}
/*
	Parse a random job specifier. Note that the numeric args are
	NON-optional.
*/

job *
parse_rand(t)
tok	*t;
{
	job	*newjob;

	newjob = (job *) malloc(sizeof(job));
	if(newjob == NULL)
		error("Out of memory");

	newjob->type = JOB_RAND;
	if(t->token != TOK_NUM) {
		error("rand requires 4 numeric arguments");
	}
	newjob->RAND.count = t->number;
	next_token(t);

	if(t->token  == TOK_PID) {
		newjob->RAND.seed = SEED_PID;
	} else if(t->token != TOK_NUM) {
		error("rand requires 4 numeric arguments");
	} else {
		newjob->RAND.seed = t->number;
	}
	next_token(t);

	if(t->token != TOK_NUM) {
		error("rand requires 4 numeric arguments");
	}
	newjob->RAND.low = t->number;
	next_token(t);

	if(t->token != TOK_NUM) {
		error("rand requires 4 numeric arguments");
	}
	newjob->RAND.high = t->number;
	next_token(t);

	if(t->token != TOK_LBRACE) {
		error("Expected {");
	}
	next_token(t);

	parse_joblist(t, newjob);

	if(t->token != TOK_RBRACE) {
		error("Expected }");
	}
	return newjob;
}
/*
	Parse a command job spec.
*/

job *
parse_cmd(t)
tok	*t;
{
	job	*newjob;

	if(t->token != TOK_STRING) {
		error("command expects quoted string");
	}

	newjob = (job *) malloc(sizeof(job));
	if(newjob == NULL)
		error("Out of memory");

	newjob->type = JOB_CMD;
	newjob->jobcount = 0;
	newjob->CMD.cmd = (char *) malloc(strlen(t->string) + 1);

	if(newjob->CMD.cmd == NULL)
		error("Out of memory");

	strcpy(newjob->CMD.cmd, t->string);

	return newjob;
}
/*
	parse a sequence on job specifiers as children of the passed-in
	job.
*/

void
parse_joblist(t, j)
tok	*t;
job	*j;
{
	int	i;
	job	*newjob;

	for(i = 0; i < MAX_JOBLIST; i++) {
		newjob = parse_job(t);
		if(newjob == NULL)
			break;

		next_token(t);
		j->joblist[i] = newjob;
	}

	if(i == MAX_JOBLIST) {
		error("Too many jobs specified");
	}
	j->jobcount = i;
}
/*
	Dump out the job tree, neatly indented etc. Debugging purposes.
*/

void
dump_job(j, indent)
job	*j;
int	indent;
{
	int	i;

	for(i = 0; i < indent; i++) {
		fprintf(stderr, "  ");
	}

	switch(j->type) {
	case JOB_SEQ:
		fprintf(stderr, "seq %d\n", j->SEQ.count);
		break;
	case JOB_PAR:
		fprintf(stderr, "par %d\n", j->PAR.count);
		break;
	case JOB_RAND:
		fprintf(stderr, "rand %d %d %d-%d\n",
				j->RAND.count, j->RAND.seed,
				j->RAND.low, j->RAND.high);
		break;
	case JOB_CMD:
		fprintf(stderr, "cmd \'%s\'\n", j->CMD.cmd);
		break;
	}

	for(i = 0; i < j->jobcount; i++) {
		dump_job(j->joblist[i], indent+1);
	}
}
/*
	Stupid and simple lever. Groks numbers, quoted strings and
	the keywords and punctuation we recognise.
*/

void
next_token(t)
tok	*t;
{
	int	ch, next_ch, i;

	/* Skip whitespace and comments */
	do {
		ch = getchar();
		if(ch == '#') {
			while(ch != '\n' && ch != EOF)
				ch = getchar();
		}
		if(ch == '\n')
			lineno++;
	} while(isspace(ch) && ch != EOF);


	/* Look for single character tokens */

	switch(ch) {
	case EOF:
		t->token = TOK_EOF;
		return;
	case '}':
		t->token = TOK_RBRACE;
		return;
	case '{':
		t->token = TOK_LBRACE;
		return;

	/* Is it a quoted string? */

	case '"':
		t->token = TOK_STRING;
		for(i = 0; i < MAX_COMMAND; i++) {
			t->string[i] = getchar();
			switch(t->string[i]) {
			case '\\':
				t->string[i] = getchar();
				break;
			case EOF:
				error("Unexpected EOF");
				break;
			case '"':
				t->string[i] = '\0';
				return;
			default:
				break;
			}
		}
		return;

	default:
		if(ch <= '9' && ch >= '0') {
			/* Eat digits and return a number */
			t->token = TOK_NUM;
			next_ch = getchar();
			if(ch == '0') {
				if(next_ch == 'x') {
					/* Eat hex number */
					t->number = 0;
					next_ch = getchar();
					while(isxdigit(next_ch)) {
						t->number *= 16;
						if(isdigit(next_ch)) {
							t->number +=
								next_ch - '0';
						} else if(islower(next_ch)) {
							t->number += 10 +
								(next_ch - 'a');
						} else {
							t->number += 10 +
								(next_ch - 'A');
						}
						next_ch = getchar();
					}
					ungetc(next_ch, stdin);
					return;
				}
			}
			/* Consume a decimal number */

			t->number = ch - '0';
			while(isdigit(next_ch)) {
				t->number *= 10;
				t->number += next_ch - '0';
				next_ch = getchar();
			}
			ungetc(next_ch, stdin);
			return;
		} else {
			/* Eat non-whitespace */
			i = 0;
			do {
				t->string[i++] = ch;
				ch = getchar();
				if(i >= MAX_COMMAND) {
					error("Token too long");
				}
			} while(!isspace(ch) && ch != EOF &&
				(!ispunct(ch) || ch == '_'));

			if(ch != EOF)
				ungetc(ch, stdin);

			t->string[i] = '\0';

			/* See if it's a keyword */

			if(strcmp(t->string, "sequential") == 0) {
				t->token = TOK_SEQ;
			} else if(strcmp(t->string, "parallel") == 0) {
				t->token = TOK_PAR;
			} else if(strcmp(t->string, "random") == 0) {
				t->token = TOK_RAND;
			} else if(strcmp(t->string, "command") == 0) {
				t->token = TOK_CMD;
			} else if(strcmp(t->string, "pid") == 0) {
				t->token = TOK_PID;
			} else {
				sprintf(errbuff, "Unrecognised keyword :%s:",
					t->string);
				error(errbuff);
			}
		}
	}
}
