/*static char *SCCSID = "@(#)pinfo.c	6.1 90/11/17";*/
/* SCCSID = @(#)pinfo.c 13.5 89/07/18 */

/*  pinfo.c - dump info from profile file
*
*   AUTHOR:	Benjamin W. Slivka
*
*   USAGE:	See Usage()
*
*   HISTORY:	05-May-1987 bws Initial version
*		11-May-1987 bws Added verbose output options
*		12-May-1987 bws Works for missing map segments
*		13-May-1987 bws Fixed crc code
*		25-Mar-1988 mts Changes to support new kernel tic scalar
*/

#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <stdlib.h>

#include "casts.h"
#include "profile.h"

#ifndef SEEK_SET
#define SEEK_SET    0
#endif

#define     OUR_DUMP_FILE   "pinfo.pro" /* dump file for ourselves */
#define     MAX_SEGS	300	    /* maximum segments in dump file */
#define     LINE_LEN	250	    /* max line length in map file */


typedef struct ADDR {
    USHORT  seg;
    ULONG   off;
} ADDR;
typedef ADDR *PADDR;


/*  Check return code */
#define     crc(a)  doCrc((a),__FILE__,__LINE__)

doCrc (rc,msg,file,line)
int	rc;
char   *msg;
char   *file;
int	line;
{
    if (rc != 0) {
	printf ("%s:(%d) call failed; rc=%d\n", file, line, rc);
	exit (rc);
    }
}

#define     iIDLE	0
#define     iV86	1
#define	    iFLAT	2
#define     iGDT	3
#define     iLDT	4
#define	    iKERNEL	5

#define     N_SCALARS	6

// 16:32 pointer

char*	pss[N_SCALARS] = {  /* Profile Scalar Strings */
    "idle",
    "v86",
    "misc flat",
    "misc gdt",
    "misc ldt",
    "kernel",
};

typedef struct {
    word    ss_Verbose;
    long    ss_pst[N_SCALARS];
} seg_s;

seg_s	ps_seg;  /* Profile Scalar segment */

typedef struct {
    char    s_name[MOD_NAME_SIZE];  /* module name */
    word    s_segNum;	/* file segment number */
    ULONG   s_size;	/* size of tic data, in bytes */
    ULONG   s_pos;	/* position, via ftell, of start of tic counts */
    ULONG   s_tics;	/* total tics */
#ifdef DEBUG
    int     s_fMark;	// traversal flag
#endif
} seg_t;

seg_t	ctl[MAX_SEGS];	    /* segment data */
#define I_SCALAR    0	    /* index in ctl of scalar segment */

FILE   *fhDump=NULL;	    /* profile data file */
FILE   *fhCtl=NULL;	    /* map file or control file */
int	argc;		    /* argc from main */
char   **argv;		    /* argv from main */
#ifdef DEBUG
int	fDebug=FALSE;	    /* TRUE => debug output */
#endif
int	fEvery=FALSE;	    /* TRUE => print every tic count */
int	fFiles=FALSE;	    /* TRUE => "map file" is list of map files */
#ifdef PROFILE
int	fProf=FALSE;	    /* TRUE => profile ourselves */
#endif
int	fTotals=FALSE;	    /* TRUE => print totals */
int	fZero=TRUE;	    /* TRUE => don't print zero counts */
ULONG	totalTics=0;	    /* Total ticks count */
int	nSegs=0;	    /* Number of segments in dump file */


/***	cmpAddr - compare two ADDRs
 *
 *	ENTRY	addr1, addr2 - ADDRs to compare
 *
 *	EXIT	returns
 *		   -1 if addr1 <  addr2
 *		    0 if addr1 == addr2
 *		    1 if addr1 >  addr2
 */
int cmpAddr(ADDR addr1,ADDR addr2)
{
    if (addr1.seg > addr2.seg)
	return +1;
    else if (addr1.seg < addr2.seg)
	return -1;
    else if (addr1.off > addr2.off)
	return +1;
    else if (addr1.off < addr2.off)
	return -1;
    else
	return 0;
}


main (c,v)
int	c;
char   *v[];
{
    argc=c;
    argv=v;
    parseCommandLine();
#ifdef DEBUG
    setvbuf(stderr,0,_IONBF,0); // don't buffer stderr
#endif
#ifdef PROFILE
    if (fProf) {
	crc( PROFINIT(PT_USEKP | PT_USER | PT_VERBOSE, (FPC)NULL) );
	crc( PROFCLEAR(PT_USER) );
	crc( PROFON(PT_USER) );
    }
#endif
    parseDumpFile();
    if (fTotals)
	printTotals();
    if (fhCtl != NULL) {
	doMaps();
	fclose(fhCtl);
    }
#ifdef PROFILE
    if (fProf) {
	fprintf(stderr,"doing ProfOff\n");
	crc( PROFOFF(PT_USER) );

	fprintf(stderr,"doing ProfDump\n");
	crc( PROFDUMP(PT_USER, (FPC)OUR_DUMP_FILE) );

	fprintf(stderr,"doing ProfFree\n");
	crc( PROFFREE(PT_USER) );
    }
    fprintf(stderr,"doing fclose\n");
#endif
    fclose(fhDump);
}

IssueMessage(fExit,msg,parm)
char *msg;
char *parm;
{
    if (strlen(parm) > 0)
	printf ("%s: %s -> %s\n", argv[0], msg, parm);
    else
	printf ("%s: %s\n", argv[0], msg);
    if (fExit)
	exit(2);
}


/*  Print error message and exit */
Fatal (msg, parm)
char *msg;
char *parm;
{
    IssueMessage (TRUE,msg,parm);
}


/*  Display usage, then issue message and exit */
Usage (msg, parm)
char *msg;
char *parm;
{
    IssueMessage (FALSE,msg,parm);
#ifdef PROFILE
    printf ("USAGE: %s [-eptz] dump_file [ [-f] file]\n",argv[0]);
#else
    printf ("USAGE: %s [-etz] dump_file [ [-f] file]\n",argv[0]);
#endif
    printf ("         -e : print every tic count (subject to -z)\n");
#ifdef PROFILE
    printf ("         -p : profile this program\n");
#endif
    printf ("         -t : print totals for each segment (subject to -z)\n");
    printf ("         -z : print 0 counts\n");
    printf ("   dump_file: output of PROFDUMP() API;\n");
    printf ("        file: map file to correlate with dump file\n");
    printf ("     -f file: file is list of map files to correlate with dump file\n");
    exit(1);
}


parseCommandLine ()
{
    char   *p;
    int     i;
    int     nFiles=0;	    /* number of files on command line */

    for (i=1; i<argc; i++) {
	if (argv[i][0] == '-') {    /* process options */
	    p = &(argv[i][1]);
	    while (*p) {
		switch (*p) {
#ifdef DEBUG
		    case 'd':	fDebug	= TRUE;  break;
#endif
		    case 'e':	fEvery	= TRUE;  break;
		    case 'f':	fFiles	= TRUE;  break;
#ifdef PROFILE
		    case 'p':	fProf	= TRUE;  break;
#endif
		    case 't':	fTotals = TRUE;  break;
		    case 'z':	fZero	= FALSE; break;
		    default:	Usage("unknown flag", argv[i]);
		}
		p++;
	    }
	}
	else {
	    switch (++nFiles) {
		case 1: /* dump file */
		    if ((fhDump = fopen (argv[i], "rb")) == NULL)
			Usage ("couldn't open dump file", argv[i]);
		    break;
		case 2: /* map file */
		    if ((fhCtl = fopen (argv[i], "r")) == NULL)
			Usage ("couldn't open map file", argv[i]);
		    break;
		default:
		    Usage("too many parameters", argv[i]);
	    }
	}
    }
    if (nFiles == 0)
	Usage("dump file missing","");
    if (fhCtl == NULL && !fTotals) /* If no output requested */
	fTotals = TRUE;     /* default output is totals */
}


#define     N_LONGS	4096	    /* number of longs in static tic buffer */
#define     LEN_BUF	(N_LONGS*sizeof(long)) /* size of tic buffer in bytes */

ULONG	cbCnt;		/* Count of bytes still to be read for counts */
word	iBuf;		/* index of next long in buffer */
long	lBuf[N_LONGS];	/* Buffer for tic counts */

initGetTics(length)
    ULONG  length;
{
    cbCnt = length;
    iBuf  = N_LONGS;	/* No more entries in buffer */
}


/*  Get next tic count */
long getTics()
{
    ULONG  n;

    if (iBuf >= N_LONGS) {  /* Have to fill buffer */
	if (cbCnt > 0) {
	    n = (cbCnt > LEN_BUF ? LEN_BUF : cbCnt);/* amount to read */
	    cbCnt -= n; 		    /* amount left to read */
	    fread ((NPC)lBuf, n, 1, fhDump);/* fill buffer */
	    iBuf = 0;			    /* start with first count */
	}
	else
	    Fatal ("request for count past end of count block","");
    }
    return lBuf[iBuf++];    /* return count, advance index */
}


/*  Compute total tics for a segment */
ULONG countTics(length)
    ULONG  length;
{
    ULONG   sum=0;
    ULONG   n=length/sizeof(long);  /* number of long counts */

    initGetTics(length);
    while (n--)
	sum += getTics();

    return sum;
}


parseDumpFile()
{
    register ULONG i;

    do {    /* read a segment */
	if (nSegs >= MAX_SEGS) {    /* too many segments */
	    char parm[20];
	    itoa(MAX_SEGS,parm,10);
	    Fatal("too many segments in dump file", parm);
	}
	fread (ctl[nSegs].s_name, sizeof(char), MOD_NAME_SIZE, fhDump);
							/* module name */
	strupr(ctl[nSegs].s_name);
	fread ((NPC)&ctl[nSegs].s_segNum, sizeof(word), 1, fhDump);
							/* segment number */
	fread ((NPC)&ctl[nSegs].s_size, sizeof(ULONG), 1, fhDump);
							/* size of data */
	ctl[nSegs].s_pos = ftell(fhDump);     /* save file position */

	if (!feof(fhDump)) {
#ifdef DEBUG
	    if (fDebug) {
		printf ("mod:%8s   seg: %04x   pos:%8ld\n",
			ctl[nSegs].s_name,ctl[nSegs].s_segNum,ftell(fhDump));
	    }
#endif
	    if (nSegs == 0) {		// Scalar segment
		fread ((NPC)&ps_seg, sizeof(ps_seg),1,fhDump);// read scalars

		ctl[nSegs].s_tics = 0;	// Sum all scalar tics
		for (i=0; i<N_SCALARS-1; i++) {
		    ctl[nSegs].s_tics += ps_seg.ss_pst[i];
		}

		// Handle kernel tic sum
		if (ps_seg.ss_Verbose) // verbose kernel tics
		    ps_seg.ss_pst[iKERNEL] = 0; // toss this total
		else	// count it for scalars
		    ctl[nSegs].s_tics += ps_seg.ss_pst[iKERNEL];

	    } else
		ctl[nSegs].s_tics = countTics(ctl[nSegs].s_size);

	    totalTics += ctl[nSegs].s_tics;
	    nSegs++;
	}
    } while (!feof(fhDump));
}


printTotals()
{
    int 	i;

    printf     ("Module   Segment Length   Tics     Percent\n");
    printf     ("-------- ------- -------- -------- -------\n");
    for (i=0; i<N_SCALARS; i++) {
	if (ps_seg.ss_pst[i] !=0 || !fZero)
	    printf (ps_seg.ss_Verbose && (i == N_SCALARS-1)?
		    "%-9s                 %8lu %6.2f\n":
		    "%-9s                 %8lu %6.2f\n",
		    pss[i],
		    ps_seg.ss_pst[i],
		    (ps_seg.ss_pst[i]*100.0)/totalTics);
    }
    for (i=1; i<nSegs; i++) {	/* skip scalar segment */
	if (ctl[i].s_tics !=0 || !fZero)
	    printf ("%-8s  %04x   %08lx %8lu %6.2f\n",
		    ctl[i].s_name,
		    ctl[i].s_segNum,
		    ctl[i].s_size,
		    ctl[i].s_tics,
		    (ctl[i].s_tics*100.0)/totalTics);
    }
    printf     ("                          --------\n");
    printf     ("                   Total  %8lu\n",totalTics);
}

/*  Process map file(s) */
doMaps()
{
    FILE    *fhMap;		/* map file */
    char    fnMap[LINE_LEN];	/* buffer for map file name */

    if (!fFiles)
	doOneMap(fhCtl);
    else {		    /* map file is list of map files */
	while (!feof(fhCtl)) {
	    fgets (fnMap, sizeof(fnMap), fhCtl);    /* get next map file */
	    if (!feof(fhCtl)) {  /* not EOF */
		fnMap[strlen(fnMap)-1] = '\0';	/* Get rid of new line */
		if ((fhMap = fopen (fnMap, "r")) == NULL)
		    Fatal ("couldn't open map file", fnMap);
		doOneMap(fhMap);
		fclose(fhMap);	/* close map file */
	    }
	}
    }
}


/*  Fill table with indicies of ctl structures, such that
*
*   a.	for all i, ctl[sInd[i]].s_name = name	-AND-
*   b.	for all i, ctl[sInd[i]].s_segNum < ctl[sInd[i+1]].s_segNum
*
*   Approach:
*	1.  Fill sInd with indices that match "name"
*	2.  Sort indicies based on s_segNum
*
*   Assumption:
*	Common case is segments are in ascending order.  So, we use
*	O(n**2) algorithm which verifies sorted order in O(n) time.
*/
initInd (sInd,pnInd,name)   /* Set up index table */
int	sInd[];     /* table of indicies */
int    *pnInd;	    /* number of indicies */
char   *name;	    /* module name to search for */
{
    int     i;
    int     j;
    int     n=0;
    int     fSwap;
    int     tmp;

    for (i=0; i<nSegs; i++) {
	if (strcmp(name,ctl[i].s_name) == 0)
	    sInd[n++] = i;
    }

    do {
	fSwap = FALSE;
	for (i=0; i<(n-1); i++) {
	    j = i+1;
	    if (ctl[sInd[i]].s_segNum > ctl[sInd[j]].s_segNum) {
		tmp = i;
		i = j;
		j = tmp;
		fSwap = TRUE;
	    }
	}
    } while (fSwap);
    *pnInd = n;
}

/*  Get rid of trailing newline in str, if it exists */
clean (str)
char *str;
{
    int     i;

    i = strlen(str)-1;
    if (str[i] == '\n')
	str[i] = '\0';
}

#define ROUND(o)    (((o)>>PROF_SHIFT)<<PROF_SHIFT)
#define ItoO(i)     ((i)<<PROF_SHIFT)
#define Next(i)     (1-(i))	/* next history record */
#define Last(i)     (1-(i))	/* last history record */

int	fPrinted;   /* TRUE => symbol line printed (only for fEvery case) */

doOneMap(fh)
FILE	*fh;
{
    long		cnt;
    ULONG		i;
    int 		iH;	    /* index of last <s,o,sym> pair */
    int 		iSeg;
    int 		n;
    char		name[MOD_NAME_SIZE];	/* name of module */
    ULONG		nCnts;
    int 		nInd;		    /* Number of segments */
    unsigned		seg;
    ULONG		total;		    /* Total tics for last label */
    unsigned		s[2];	    /* segment from map file */
    ULONG		o[2];	    /* offset from map file */
    char		sym[2][LINE_LEN];
    char		line[LINE_LEN];
    int 		sInd[MAX_SEGS];     /* indicies into ctl table */
    ADDR		addrMap;
    ADDR		addrTic;

    iH = 0;
    s[iH] = 0;	 // start at very beginning to meet loop criteria
    o[iH] = 0;
    addrMap.seg = s[iH];
    addrMap.off = o[iH];

    do {    /*	find module name */
	fgets (line, LINE_LEN, fh);
    } while (!feof (fh) &&
	     strncmp (line, " ", 1));

    /*	Locate segments for this module */
    strcpy (name, &line[1]);
    strupr (name);
    clean(name);    /* Get rid of newline */
    initInd (sInd,&nInd,name);	/* Set up index table */
    if (nInd == 0) {
	printf ("Ignored: No segments for module %s\n", name);
	return;
    }

    do {    /*	skip to symbol and value set */
	fgets (line, LINE_LEN, fh);
    } while (!feof (fh) &&
	     strcmp (line, "  Address         Publics by Value\n"));

#ifdef DEBUG
    // dump out control tables
    if (fDebug) {
	for (n=0; n<nSegs; n++) {
	    ctl[n].s_fMark = FALSE;  // reset flags
	}

	for (n=0; n<nInd; n++) {    // flags segments for this module
	    iSeg = sInd[n];
	    ctl[iSeg].s_fMark = TRUE;
	}

	// print control table

	printf("< Module   Seg# Size     FilePos  Tics     >\n");
	for (n=0; n<nSegs; n++) {
	    printf("<%c%8s %4d %8lx %8lx %9ld>\n",
		(ctl[n].s_fMark ? '*' : ' '),
		ctl[n].s_name,
		ctl[n].s_segNum,
		ctl[n].s_size,
		ctl[n].s_pos,
		ctl[n].s_tics);
	}
	printf("\n");
    }
#endif
    /* read the map file, mapping segments on the command line into
     * segments in the map file */
    printf ("Module %s\n", name);
    for (n=0; n<nInd; n++) {
	iSeg = sInd[n]; 	/* index of segment */

#ifdef DEBUG
	if (fDebug) {
	    printf("< Module   Seg# Size     FilePos  Tics     >\n");
	    printf("<%c%8s %4d %8lx %8lx %9ld>\n",
		(ctl[iSeg].s_fMark ? '*' : ' '),
		ctl[iSeg].s_name,
		ctl[iSeg].s_segNum,
		ctl[iSeg].s_size,
		ctl[iSeg].s_pos,
		ctl[iSeg].s_tics);

	}
#endif
	if (ctl[iSeg].s_tics == 0)
	    continue;		/* No tics, don't bother processing */

	seg = ctl[iSeg].s_segNum;
	addrTic.seg = seg;
	printf ("\n");	/* separate segments */
	total = 0;
	crc( fseek (fhDump, ctl[iSeg].s_pos, SEEK_SET) ); /* seek to counts */
	initGetTics(ctl[iSeg].s_size);
	nCnts = ctl[iSeg].s_size/sizeof(long);
	fPrinted = FALSE;	    /* TRUE if symbol printed for fEvery */
	for (i=0; i<nCnts; i++) {   /* process each tic count */
	    addrTic.off = ItoO(i);
	    cnt = getTics();
	    if (cnt == 0)
		continue;   /* Skip zero count */
#ifdef DEBUG
	    if (fDebug)
		printf ("Looking for: %04x:%08lx\n",seg,ItoO(i));
#endif
	    /* skip in map file until s:o of symbol >= seg:i */
	    while (cmpAddr(addrMap,addrTic) <= 0) {
		if (s[iH] == seg)	/* in desired segment */
		    doTotal(&total,iH,s,o,sym);
		if (fgets(line, LINE_LEN, fh) == NULL)
		    Fatal("Unexpected EOF on map file", "");
		iH = Next(iH);	/* advance history index */
		if (3 == sscanf(line," %x:%lx %s ",&s[iH],&o[iH],sym[iH]) ) {
		    addrMap.seg = s[iH];
		    addrMap.off = o[iH];
#ifdef DEBUG
		    if (fDebug)
			printf ("<%04x:%08lx %s>\n",s[iH],o[iH],sym[iH]);
#endif
		    fPrinted = FALSE;	/* haven't printed this line, yet */
		}
#ifdef DEBUG
		else
		    if (fDebug)
			printf ("<%s",line);
#endif
	    }
	    if (fEvery) {
		if (!fPrinted) {
		    int iL = Last(iH); /* index of label */
		    printf ("%04x:%08lx  %s\n", s[iL], o[iL], sym[iL]);
		    fPrinted = TRUE;
		}
		printf ("%04x:%08lx  %9lu\n", s[iH], ItoO(i), cnt);
	    }
	    total += cnt;
	}
	doTotal(&total,iH,s,o,sym);
    }
}

doTotal(total,iH,s,o,sym)
ULONG  *total;
int	iH;
int	s[];
ULONG	o[];
char	sym[2][LINE_LEN];
{
    ULONG   t=*total;

    if (t != 0) {   /* accumulated total to print */
	int iL = Last(iH); /* index of label */
	if (!fPrinted)
	    printf ("%04x:%08lx  %9lu (%6.2f%%)   %s\n",
		    s[iL], o[iL], t, (100.0*t)/totalTics, sym[iL]);
	else
	    printf ("   Total:  %9lu (%6.2f%%)\n", t, (100.0*t)/totalTics);
	*total = 0;
    }
}
