static char rcsid[] = "$Header:prof.c 12.0$";
#ifndef lint
static	char *sccsid = "@(#)prof.c	4.3 (Berkeley) 7/2/83";
#endif
/*
 * prof
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <a.out.h>
#include <debug.h>

/* #ifdef ibm032 */
#define	PCFUDGE		22		/* how far away the return address */
					/*is from entry point. see asgncntrs*/
#define HZ	64
#define TICS	(1.0/HZ)
/* #endif */

#ifdef vax
#define	PCFUDGE		11		/* how far away the return address is */
#define HZ	100
#define TICS	(1.0/HZ)
#endif

typedef	short UNIT;		/* unit of profiling */
#define	A_OUTNAME	"a.out"
#define	MON_OUTNAME	"mon.out"
#define	MON_SUMNAME	"mon.sum"

/*
 * The symbol table;
 * for each external in the specified file we gather
 * its address, the number of calls and compute its share of cpu time.
 */
struct nl {
	char	*name;
	unsigned value;
	float	time;
	long	ncall;
} *nl;
int	nname;
struct	nl *np;
struct	nl *npe;

/*
 * The header on the mon.out file.
 * Mon.out consists of one of these headers, an array of ncount
 * cnt structures (as below) and then an array of samples
 * representing the discretized program counter values.
 */
struct hdr {
	UNIT	*lowpc, *highpc;
	int	ncount;
} h;

/*
 * Each counter has an address and a number of calls.
 */
struct cnt {
	unsigned cvalue;
	long	cncall;
} *cbuf;

/*
 * Each discretized pc sample has
 * a count of the number of samples in its range
 */
UNIT	*samples;

FILE	*pfile, *nfile;

unsigned lowpc, highpc;		/* range profiled */
double	ransca, ranoff;		/* scaling for blowing up plots */
unsigned sampbytes;		/* number of bytes of samples */
int	nsamples;		/* number of samples */
double	totime;			/* total time for all routines */
double	maxtime;		/* maximum time of any routine (for plot) */
double	scale;			/* scale factor converting samples to pc
				   values: each sample covers scale bytes */
char	*strtab;		/* string table in core */
off_t	ssiz;			/* size of the string table */
struct	exec xbuf;		/* exec header of a.out */

int	aflg;
int	nflg;
int	vflg;
int	lflg;
int	zflg;
int	sflag;

char	*namfil;

int	timcmp(), valcmp(), cntcmp();

main(argc, argv)
	char **argv;
{
	int lowpct, highpct;

	/*
	 * Use highpct and lowpc as percentages, temporarily
	 * for graphing options involving blow-up
	 */
	lowpct = -1;
	highpct = -1;
	argv++;
	while ( *argv != 0 && **argv == '-' ) {
		*argv += 1;
		if (**argv == 'l')
			lflg++;
		else if (**argv == 'a')
			aflg++;
		else if (**argv == 'n')
			nflg++;
		else if (**argv == 'z')
			zflg++;
		else if (**argv == 'v')
			vflg++;
		else if ( **argv == 's' )
			sflag++;
		else if ( **argv == 'd' )
			debug++;
		else if (**argv >= '0' && **argv <= '9') {
			int i = atoi(*argv);
			if (lowpct == -1)
				lowpct = i;
			else
				highpct = i;
		}
		argv++;
	}
	if ( *argv != 0 ) {
		namfil = *argv;
		argv++;
	} else {
		namfil = A_OUTNAME;
	}
	if (lowpct >= 100)
		lowpct = 0;
	if (highpct <= lowpct || highpct > 100)
		highpct = 100;
	ransca = 100./(highpct-lowpct);
	ranoff = 2040. + 40.8*lowpc*ransca;
		/*
		 *	get information about a.out file.
		 */
	getnfile();
		/*
		 *	get information about mon.out file(s).
		 */
	if ( *argv == 0 ) {
		getpfile( MON_OUTNAME );
	} else {
		do {
			getpfile( *argv );
			argv++;
		} while ( *argv != 0 );
	}
	asgnsamples();		/* assign samples to procedures */
#ifdef plot
	if (vflg)
		plotprof();	/* a plotted or ... */
	else
#endif
		printprof();	/* a printed profile */
	if ( sflag != 0 ) {
		putprof();
	}
	done();
}

printprof()
{
	double time, actime;

	actime = 0;
	printf(" %%time  cumsecs  #call  ms/call  name\n");
	if (!lflg)
		qsort(nl, nname, sizeof(struct nl), timcmp);
	for (np = nl; np<npe-1; np++) {
		if (zflg == 0 && np->time == 0 && np->ncall == 0)
			continue;
		time = np->time/totime;
		actime += np->time;
		printf("%6.1f%9.2f", 100*time, actime/HZ);
		if (np->ncall != 0)
			printf("%7ld %8.2f",
			    np->ncall, np->time/(np->ncall / TICS));
		else
			printf("%7.7s %8.8s", "", "");
		printf("  %s\n", np->name);
	}
}

/*
 * Set up string and symbol tables from a.out.
 * On return symbol table is sorted by value.
 */
getnfile()
{

	nfile = fopen(namfil,"r");
	if (nfile == NULL) {
		perror(namfil);
		done();
	}
	fread(&xbuf, 1, sizeof(xbuf), nfile);
	if (N_BADMAG(xbuf)) {
		fprintf(stderr, "%s: bad format\n", namfil);
		done();
	}
	getstrtab();
	getsymtab();
	qsort(nl, nname, sizeof(struct nl), valcmp);
}

getstrtab()
{

	fseek(nfile, N_SYMOFF(xbuf) + xbuf.a_syms, 0);
	if (fread(&ssiz, sizeof (ssiz), 1, nfile) == 0) {
		fprintf(stderr, "%s: no string table (old format?)\n", namfil);
		done();
	}
	strtab = (char *)calloc(ssiz, 1);
	if (strtab == NULL) {
		fprintf(stderr, "%s: no room for %d bytes of string table",
		    namfil, ssiz);
		done();
	}
	if (fread(strtab+sizeof(ssiz), ssiz-sizeof(ssiz), 1, nfile) != 1) {
		fprintf(stderr, "%s: error reading string table\n", namfil);
		done();
	}
}

/*
 * Read in symbol table
 */
getsymtab()
{
	register int i;

	/* pass1 - count symbols */
	fseek(nfile, N_SYMOFF(xbuf), 0);
	nname = 0;
	for (i = xbuf.a_syms; i > 0; i -= sizeof(struct nlist)) {
		struct nlist nbuf;
		fread(&nbuf, sizeof(nbuf), 1, nfile);
		if (nbuf.n_type!=N_TEXT && nbuf.n_type!=N_TEXT+N_EXT)
			continue;
		if (aflg==0 && nbuf.n_type!=N_TEXT+N_EXT)
			continue;
		nname++;
	}
	if (nname == 0) {
		fprintf(stderr, "%s: no symbols\n", namfil);
		done();
	}
	nl = (struct nl *)calloc((nname+1), sizeof (struct nl));
	if (nl == 0) {
		fprintf(stderr, "prof: No room for %d bytes of symbol table\n",
		    (nname+1) * sizeof (struct nlist));
		done();
	}

	/* pass2 - read symbols */
	fseek(nfile, N_SYMOFF(xbuf), 0);
	npe = nl;
	nname = 0;
	for (i = xbuf.a_syms; i > 0; i -= sizeof(struct nlist)) {
		struct nlist nbuf;
		fread(&nbuf, sizeof(nbuf), 1, nfile);
		if (nbuf.n_type!=N_TEXT && nbuf.n_type!=N_TEXT+N_EXT)
			continue;
		if (aflg==0 && nbuf.n_type!=N_TEXT+N_EXT)
			continue;
		npe->value = nbuf.n_value/sizeof(UNIT);
		npe->name = strtab+nbuf.n_un.n_strx;
		npe++;
		nname++;
	}
	npe->value = -1;
	npe++;
}

/*
 * information from a mon.out file is in two parts:
 * the counters of how many times each procedure was called,
 * if it was called at all;
 * and an array of sampling hits within pc ranges.
 * the counters must be dealt with on a file-by-file basis,
 * since which procedures are represented may vary.
 * the samples ranges are fixed, but must be summed across
 * files, and then distributed among procedures, because
 * of the wierd way the plotting is done.
 */
getpfile(filename)
	char *filename;
{

	openpfile(filename);
	readcntrs();
	asgncntrs();		/* assign counts to procedures */
	readsamples();
	closepfile();
}

openpfile(filename)
	char *filename;
{
	struct stat stb;

	if((pfile = fopen(filename, "r")) == NULL) {
		perror(filename);
		done();
	}
	fstat(fileno(pfile), &stb);
	fread(&h, sizeof(struct hdr), 1, pfile);
	lowpc = (int)h.lowpc - 0;
	highpc = (int)h.highpc - 0;
	sampbytes =
	    stb.st_size - sizeof(struct hdr) - h.ncount*sizeof(struct cnt);
	nsamples = sampbytes/sizeof(UNIT);
}

closepfile()
{

	fclose(pfile);
	free(cbuf);
}

readcntrs()
{
	struct cnt *kp;

	PTRACE(1, ("%d counter entries",h.ncount));
	cbuf = (struct cnt *)calloc((h.ncount+1), sizeof (struct cnt));
	if (cbuf == 0) {
		fprintf(stderr, "prof: No room for %d bytes of count buffer\n",
		    (h.ncount+1) * sizeof (struct cnt));
		exit(1);
	}
	fread(cbuf, sizeof(struct cnt), h.ncount, pfile);
	/* eliminate zero counters and scale counter pc values */
	if (h.ncount) {
		kp = &cbuf[h.ncount - 1];
		for (;;) {
			if (kp->cvalue!=0) {
				++kp;
				h.ncount=kp-cbuf;
				break;
			}
			if (kp == cbuf) {
				h.ncount = 0;
				break;
			}
			--kp;
		}
		for (; --kp>=cbuf; ) {
			kp->cvalue /= sizeof(UNIT);
			PTRACE(2, ("counter: 0x%x = %d",kp->cvalue,kp->cncall));
		}
	}
	/* sort counters */
	qsort(cbuf, h.ncount, sizeof(struct cnt), cntcmp);
	PTRACE(1, ("%d counter entries",h.ncount));
}

/*
 * Assign counters to the procedures to which they belong
 */
asgncntrs()
{
	register int i;
	struct cnt *kp;

	kp = &cbuf[h.ncount-1];
	np = npe;
	while (--np>=nl) {
		if (kp<cbuf || np->value > kp->cvalue)
			continue;
			/* skip ``static'' functions */

/* kp->cvalue the address passed to mcount and read from mon.out is not the
	exact entry point of the function. It is some bytes after the entry
	point. We allow for this using PCFUDGE.
*/
		while (kp >= cbuf && kp->cvalue > np->value + PCFUDGE)
			--kp;
		if (kp->cvalue >= np->value) {
			np->ncall += kp->cncall;
			--kp;
		}
	}
}

readsamples()
{
	register i;
	UNIT	sample;
	int totalt;
	
	if (samples == 0) {
		samples = (UNIT *)
		    calloc(sampbytes, sizeof (UNIT));
		if (samples == 0) {
			printf("prof: No room for %d sample pc's\n", 
			    sampbytes / sizeof (UNIT));
			done();
		}
	}
	for (i = 0; ; i++) {
		fread(&sample, sizeof (UNIT), 1, pfile);
		PTRACE(3, ("sample %d = %d",i,sample));
		if (feof(pfile))
			break;
		samples[i] += sample;
		totalt += sample;
	}
	if (i != nsamples) {
		fprintf(stderr,
		    "prof: unexpected EOF after reading %d/%d samples\n",
			--i, nsamples);
		done();
	}
}

/*
 * Assign samples to the procedures to which they belong.
 */
asgnsamples()
{
	register j;
	UNIT	ccnt;
	double time;
	unsigned pcl, pch;
	register int i;
	register int base=0;
	int overlap;

	/* read samples and assign to namelist symbols */
	scale = highpc - lowpc;
	scale /= nsamples;
	for (i=0; i < nsamples; i++) {
		ccnt = samples[i];
		if (ccnt == 0)
			continue;
		pcl = lowpc + scale*i;
		pch = lowpc + scale*(i+1);
		PTRACE(2, ("%d: 0x%x ... 0x%x = %d tics",i, pcl,pch-1,ccnt));
		time = ccnt;
		totime += time;
		if(time > maxtime)
			maxtime = time;
		for (j=base; j<nname; j++) {
			if (pch < nl[j].value)
				break;
			if (pcl >= nl[j+1].value) {
				base=j;
				continue;
			}
			overlap=(min(pch,nl[j+1].value)-max(pcl,nl[j].value));
			if (overlap>0) {
				nl[j].time += overlap*time/scale;
				PTRACE(2, ("fn %s (0x%x) + %.1f",nl[j].name,nl[j].value,overlap*time/scale));
			}
		}
	}
	if (totime==0.0) {
		fprintf(stderr, "No time accumulated\n");
/*
		done();
 */
		totime=1.0;
	}
}

/*
 * dump what you have out to a mon.out style file.
 */
putprof()
{
	FILE *sfile;
	struct nl *np;
	struct cnt kp;
	int i;

	sfile = fopen(MON_SUMNAME, "w");
	if (sfile == NULL) {
		perror(MON_SUMNAME);
		done();
	}
	/*
	 * build a new header.
	 * h.lowpc and h.highpc are already fine.
	 * fix h.ncount to count non-zero calls,
	 * and the one zero call which marks the end.
	 */
	h.ncount = 0;
	for (np = nl; np < npe-1 ; np++)
		if (np->ncall > 0)
			h.ncount++;
	h.ncount++;
	fwrite(&h, sizeof (struct hdr), 1, sfile);
	for (np = nl; np < npe-1; np++) {
		if (np->ncall > 0) {
			kp.cvalue = np->value * sizeof (UNIT);
			kp.cncall = np->ncall;
			fwrite(&kp, sizeof (struct cnt), 1, sfile);
		}
	}
	kp.cvalue = 0;
	kp.cncall = 0;
	fwrite(&kp, sizeof (struct cnt), 1, sfile);
	fwrite(samples, sizeof (UNIT), nsamples, sfile);
	fclose(sfile);
}

min(a, b)
{
	if (a<b)
		return(a);
	return(b);
}

max(a, b)
{
	if (a>b)
		return(a);
	return(b);
}

valcmp(p1, p2)
	struct nl *p1, *p2;
{

	return(p1->value - p2->value);
}

timcmp(p1, p2)
	struct nl *p1, *p2;
{
	float d;

	if (nflg && p2->ncall != p1->ncall)
		return (p2->ncall - p1->ncall);
	d = p2->time - p1->time;
	if (d > 0.0)
		return(1);
	if (d < 0.0)
		return(-1);
	return(strcmp(p1->name,p2->name));
}

cntcmp(p1, p2)
	struct cnt *p1, *p2;
{

	return(p1->cvalue - p2->cvalue);
}

done()
{

#ifdef plot
	if(vflg) {
		point(0, -2040);
		closepl();
	}
#endif
	exit(0);
}

#ifdef plot
plotprof()
{
	double time, lastx, lasty, lastsx;
	register i;

	openpl();
	erase();
	space(-2048, -2048, 2048, 2048);
	line(-2040, -2040, -2040, 2040);
	line(0, 2040, 0, -2040);
	for(i=0; i<11; i++)
		line(-2040, 2040-i*408, 0, 2040-i*408);
	lastx = 0.;
	lasty = ranoff;
	scale = (4080.*ransca)/(sampbytes/sizeof(UNIT));
	lastsx = 0.0;
	for(i = 0; i < nsamples; i++) {
		UNIT ccnt;
		double tx, ty;
		ccnt = samples[i];
		time = ccnt;
		tx = lastsx;
		ty = lasty;
		lastsx -= 2000.*time/totime;
		lasty -= scale;
		if(lasty >= -2040. && ty <= 2040.) {
			line((int)tx, (int)ty, (int)lastsx, (int)lasty);
			if (ccnt!=0 || lastx!=0.0) {
				tx = lastx;
				lastx = -time*2000./maxtime;
				ty += scale/2;
				line(0, (int)ty, (int)tx, (int)ty);
			}
		}
	}
	scale = (4080.*ransca)/(highpc-lowpc);
	lastx = 50.;
	for(np = nl; np<npe;  np++) {
		if(np->value < lowpc)
			continue;
		if(np->value >= highpc)
			continue;
		if(zflg == 0 && np->time == 0 && np->ncall == 0)
			continue;
		time = np->time/totime;
		lasty = ranoff - (np->value - lowpc)*scale;
		if(lasty >= -2040. && lasty <= 2040.) {
			char bufl[BUFSIZ], *namp;
			register j;
			line(0, (int)lasty, 50, (int)lasty);
			line((int)(lastx-50),(int)lasty,(int)lastx,(int)lasty);
			move((int)(lastx+30), (int)(lasty+10));
			sprintf(bufl, "%s", np->name + (np->name[0] == '_'));
			label(bufl);
		}
		lastx += 500.;
		if(lastx > 2000.)
			lastx = 50.;
	}
}
#endif
