/* monitor.c
 *
 * Implementation of monitor(3) for minix. monitor is an interface to profil(2)
 * and is used to create an execution profile for the current process. When
 * monitoring is stopped, the profiling data is written to the file "gmon.out"
 * and can be examined with the gprof(1) utility.
 *
 * written by Kai-Uwe Bloem (I5110401@DBSTU1.BITNET), 12/10/89, 04/20/90
 */

#include <lib.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <gmon.h>

#ifndef NULL
#define NULL	0		/* just to be safe */
#endif

#if (CHIP == INTEL)		/* %&!@ 64K segments. Max 8KB for profiling */
#define	MAX_PBSIZ	0x1000L		/* max size of profiling buffer */
#define	ARC_BYTES	128		/* #textbytes per arc */
#define	ARC_MIN		32		/* min # of arcs allocated */
#define	ARC_MAX		512		/* max # of arcs allocated */
#else
#define	MAX_PBSIZ	0x10000L
#define	ARC_BYTES	32
#define	ARC_MIN		32
#define	ARC_MAX		32768		/* MUST fit into an unsigned int ! */
#endif

#if (CHIP == M68000)		/* Add your favorite processor here */
#define	OPC_ALIGN	2		/* Op-codes are word-aligned */
#else
#define	OPC_ALIGN	1		/* No alignment for opcodes needed */
#endif

#define	MONFILE		"gmon.out"
#define SZ		sizeof(CHUNK)

typedef	unsigned long	unlong;
typedef	_PROTOTYPE( void vf_t, (void) );	/* type for pc representation */

/* variables describing buffer and edge points for the system profil()er */

PRIVATE CHUNK *buff;		/* The profiling buffer */
PRIVATE unlong size;		/* size of profiling buffer */
PRIVATE unsigned scale;		/* profile scaling factor */
PRIVATE vf_t *low;		/* low  address of sampled area */
PRIVATE vf_t *high;		/* high address of sampled area */


/* variables describing the call graph which is build by mcount() */

struct from {			/* caller data structure: */
	vf_t *fpc;		/*   pc of caller */
	unlong ncalls;		/*   call count */
	struct from *fnext;	/*   link to next structure */
};
struct to {			/* callee data structure: */
	vf_t *tpc;		/*   the pc of the called proc */
	struct to *tnext;	/*   pointer to next callee struct */
	struct from *fchain;	/*   chain of from-structures for this proc */
};

PRIVATE union fundesc {		/* function call array */
	struct to	clr;
	struct from	cle;
} *funcs;
PRIVATE unsigned funsize, funused; /* total size and used part of count array */
PRIVATE union fundesc *funnext;	/* next free descriptor, &funcs[funused] */
PRIVATE struct to *tohead;	/* head of to-structure chain */


/* moncontrol enables or disables the system histogramm profiler. Be aware
 * that the global buffer and pc variables must be set when calling moncontrol
 * to enable system profiling !
 */
PUBLIC void moncontrol (flag)
int flag;
{
  if (flag) {
	if (profil((char *)buff, (long)SZ * size, (long)low, scale) < 0)
		write(2, "monitor: profil() failed\n", 25);
  } else
	profil((char *) NULL, 0L, 0L, 0);
}


/* This is the main worker procedure as documented in "unix V7 prog. man #1"
 * It allocates a function call count array with nr_func entries, and calls
 * the system profiler profil() to get a histogramm. If low_pc is NULL
 * monitoring is stopped and a file "gmon.out" will be created in the current
 * working directory, containing the histogramm and the call count info. It is
 * suitable for use with gprof(1). After all the call count array is freed.
 */

PUBLIC void monitor (low_pc, high_pc, buf, bufsiz, nr_func)
vf_t *low_pc;
vf_t *high_pc;
CHUNK buf[];
unlong bufsiz;
unsigned nr_func;
{
  /* pc's in file should be virtual. On MMU systems _start is 0 */
  extern vf_t _start;	/* symbol for virtual address 0 (from crtso) */

  if (low_pc != (vf_t *)NULL) {		/* turn monitoring on */
	unlong	incr, length;

	/* Initialize the call count array */

	funcs = (union fundesc *) calloc( sizeof(union fundesc), nr_func );
	if (funcs == (union fundesc *) NULL) {
		write( 2, "monitor: can't allocate function table\n", 39 );
		funsize = funused = 0;
	} else {
		funsize = nr_func;
		funused = 0;
	}
	funnext = funcs;		/* first entry is next free descr */
	tohead = (struct to *) NULL;	/* to-structure chain is empty */

	/* prepare globals for profil() call in moncontrol */

	length = (unlong)high_pc - (unlong)low_pc;
	incr = length / bufsiz;
	if (incr < OPC_ALIGN)		/* Quantization too fine. Adjust it. */
		incr = OPC_ALIGN;
	scale = (0x10000L / incr) - 1;
	if (scale < 2) {		/* scale too small. Adjust to scale=2 */
		scale = 2;
		incr = 0x10000L / (scale + 1);
		length = bufsiz * incr;
	}
	buff = buf;
	size = length / incr;
	if (length % incr != 0) size++;
	low = low_pc;
	high = (vf_t *)((unlong)low + size*incr);

	/* start the profiler */

	moncontrol(1);
  } else {			/* turn monitoring off */
	int	fd;
	struct gm_header	ghead;
	char	*bp;
	long	len;
	struct gm_call		gcall;
	register struct from	*fp;
	register struct to	*tp;

	moncontrol(0);			/* disable histogramm profiler */

	fd = creat(MONFILE, 0666);	/* create output file */
	if (fd < 0) {
		write(2, "monitor: can't create gmon.out\n", 31);
		return;
	}

	/* write the profiling buffer. NOTE: error checking for write() ?? */

	ghead.low =  (unlong)low  - (unlong)_start;	/* low pc */
	ghead.high = (unlong)high - (unlong)_start;	/* high pc */
	ghead.nbytes = SZ * size + sizeof(ghead);	/* histo + hdr size */
	write(fd, (char *)&ghead, (int)sizeof(ghead));
	bp = (char *)buff;		/* write the histogramm buffer */
	len = SZ * size;
	while (len > 0) {
		int plen = (len > 16384 ? 16384 : len);
		write(fd, bp, plen);
		bp += plen;
		len -= plen;
	}

	/* write the call count info */

	for (tp = tohead; tp != (struct to *) NULL; tp = tp->tnext) {
	    for (fp = tp->fchain; fp != (struct from *) NULL; fp = fp->fnext) {
		gcall.from = (unlong)fp->fpc - (unlong)_start;	/* from */
		gcall.to =   (unlong)tp->tpc - (unlong)_start;	/* to */
		gcall.ncalls = fp->ncalls;			/* count */
		write(fd, (char *)&gcall, (int)sizeof(gcall));
	    }
	}

	close(fd);			/* finished writing */

	if (funcs != (union fundesc *) NULL) {
		free (funcs);		/* release func array */
		funcs = (union fundesc *) NULL;
		funsize = funused = 0;
	}
  }
}


/* The pointer passed to mcount() by the compiler-generated profile stuff
 * has a compiler dependant meaning. For unix it points to an unsigned long,
 * which was used to record the call count for the old prof(1) stuff. For ACK
 * it points to the name of the called function.
 *
 * In this implementation that pointer is used to store an index into the
 * function call table. The advantage is there is no search necessary to locate
 * the entry for this procedure in the call count array. A validity test must
 * be done since ACK stores a name there (that is the letters might be a valid
 * table index). This can be done by simply comparing the pc in the table
 * entry with the parameter to_pc.
 */

/* It is necessary to get the pc for caller and callee, which is rather
 * machine dependant. Therefore this dirty work is done by an external
 * interface procedure in the mcrtso.s startup code.
 */
PUBLIC void mcount(from_pc, to_pc, id)
vf_t *from_pc;
vf_t *to_pc;
unsigned short *id;
{
  register struct to	*fn;
  register struct from	*p;

  /* check validity of index and funcs[index] */
  if (*id < funused &&
     (fn = (struct to *) &funcs[*id])->tpc == to_pc) {
	/* Function has already been recorded here. Search caller chain. */
	for (p = fn->fchain; p != (struct from *) NULL; p = p->fnext)
		if (p->fpc == from_pc) {
			/* arc found. increment call count. */
			p->ncalls++;
			return;
		}
  } else {
	/* function not yet recorded. allocate to-structure */
	if (funused < funsize) {
		*id = funused++;
		fn = (struct to *) (funnext++);
		fn->tpc = to_pc;
		fn->fchain = (struct from *) NULL;
		fn->tnext = tohead;
		tohead = fn;
	} else
		/* out of space */
		return;
  }

  /* allocate a new from-struct for this proc */
  if (funused < funsize) {
  	funused++;
	p = (struct from *) (funnext++);
	p->fpc = from_pc;
	p->ncalls = 1;
	p->fnext = fn->fchain;
	fn->fchain = p;
  }
}



/* The following 2 procedures, monstartup() and _mcleanup(), are called by
 * the c startup code in mcrtso.o and allow automatic profiling. The size
 * of the profiling buffer is calculated from the code size of the profiled
 * program. The buffer gets allocated, and a suitable number of function
 * entries is requested. If profiling has been stopped the buffer is released.
 */

PRIVATE CHUNK *_mbuff;

PUBLIC void monstartup (low_pc, high_pc)
vf_t *low_pc;
vf_t *high_pc;
{
  unlong tsize, msize, fsize;
  unsigned	scale;

  /* calculate size of profiling buffer */

  tsize = (unlong)high_pc - (unlong)low_pc; /* code area size */
  scale = tsize / MAX_PBSIZ;		/* scaling for msize < MAX_PBSIZ */
  if (tsize % MAX_PBSIZ != 0)
	scale++;
  msize = (tsize / scale + 1) / SZ;	/* profil buffer size in words */
  _mbuff = (CHUNK *) calloc( (unsigned) msize, SZ );
  if (_mbuff == (CHUNK *) NULL) {
	write(2, "monitor: can't allocate histogramm buffer\n", 42);
	return;
  }

  /* Calculate number of arcs sampled */

  fsize = tsize / ARC_BYTES;
  if (fsize > ARC_MAX)
	fsize = ARC_MAX;
  if (fsize < ARC_MIN)
	fsize = ARC_MIN;

  /* Start monitor */

  monitor(low_pc, high_pc, _mbuff, msize, (unsigned) fsize);
}


PUBLIC void _mcleanup ()
{
  monitor( (vf_t *)NULL, (vf_t *)NULL, buff, 0L, 0 );

  if (_mbuff != (CHUNK *) NULL)
	free(_mbuff);
}
