shithub: nstats

ref: 21ed259dcd8fcbbe93c78c5958a48176b6953e7a
dir: /nstats.c/

View raw version
#include <u.h>
#include <libc.h>
#include <ctype.h>

#define	MAXNUM	10	/* maximum number of numbers on data line */

typedef struct Graph	Graph;
typedef struct Machine	Machine;

struct Graph
{
	char		*label;
	void		(*newvalue)(Machine*, uvlong*, uvlong*, int);
	Machine		*mach;
};

enum
{
	/* /dev/swap */
	Mem		= 0,
	Maxmem,
	Swap,
	Maxswap,
	Reclaim,
	Maxreclaim,
	Kern,
	Maxkern,
	Draw,
	Maxdraw,

	/* /dev/sysstats */
	Procno	= 0,
	Context,
	Interrupt,
	Syscall,
	Fault,
	TLBfault,
	TLBpurge,
	Load,
	Idle,
	InIntr,

	/* /net/ether0/stats */
	In		= 0,
	Link,
	Out,
	Err0,
};

struct Machine
{
	char		*name;
	char		*shortname;
	int		remote;
	int		statsfd;
	int		swapfd;
	int		etherfd[10];
	int		batteryfd;
	int		bitsybatfd;
	int		tempfd;
	int		disable;

	uvlong		devswap[10];
	uvlong		devsysstat[10];
	uvlong		prevsysstat[10];
	int		nproc;
	int		lgproc;
	uvlong		netetherstats[8];
	uvlong		prevetherstats[8];
	uvlong		batterystats[2];
	uvlong		temp[10];

	/* big enough to hold /dev/sysstat even with many processors */
	char		buf[8*1024];
	char		*bufp;
	char		*ebufp;
};

enum Menu2
{
	Mbattery,
	Mcontext,
	Mether,
	Methererr,
	Metherin,
	Metherout,
	Mfault,
	Midle,
	Minintr,
	Mintr,
	Mload,
	Mmem,
	Mswap,
	Mreclaim,
	Mkern,
	Mdraw,
	Msyscall,
	Mtlbmiss,
	Mtlbpurge,
	Mtemp,
	Nmenu2,
};

char *labels[Nmenu2+1] = {
	"battery",
	"context",
	"ether",
	"ethererr",
	"etherin",
	"etherout",
	"fault",
	"idle",
	"inintr",
	"intr",
	"load",
	"mem",
	"swap",
	"reclaim",
	"kern",
	"draw",
	"syscall",
	"tlbmiss",
	"tlbpurge",
	"temp",
	nil,
};
int padlabels = 10;


void	contextval(Machine*, uvlong*, uvlong*, int),
	etherval(Machine*, uvlong*, uvlong*, int),
	ethererrval(Machine*, uvlong*, uvlong*, int),
	etherinval(Machine*, uvlong*, uvlong*, int),
	etheroutval(Machine*, uvlong*, uvlong*, int),
	faultval(Machine*, uvlong*, uvlong*, int),
	intrval(Machine*, uvlong*, uvlong*, int),
	inintrval(Machine*, uvlong*, uvlong*, int),
	loadval(Machine*, uvlong*, uvlong*, int),
	idleval(Machine*, uvlong*, uvlong*, int),
	memval(Machine*, uvlong*, uvlong*, int),
	swapval(Machine*, uvlong*, uvlong*, int),
	reclaimval(Machine*, uvlong*, uvlong*, int),
	kernval(Machine*, uvlong*, uvlong*, int),
	drawval(Machine*, uvlong*, uvlong*, int),
	syscallval(Machine*, uvlong*, uvlong*, int),
	tlbmissval(Machine*, uvlong*, uvlong*, int),
	tlbpurgeval(Machine*, uvlong*, uvlong*, int),
	batteryval(Machine*, uvlong*, uvlong*, int),
	tempval(Machine*, uvlong*, uvlong*, int);

int present[Nmenu2];
void	(*newvaluefn[Nmenu2])(Machine*, uvlong*, uvlong*, int init) = {
	batteryval,
	contextval,
	etherval,
	ethererrval,
	etherinval,
	etheroutval,
	faultval,
	idleval,
	inintrval,
	intrval,
	loadval,
	memval,
	swapval,
	reclaimval,
	kernval,
	drawval,
	syscallval,
	tlbmissval,
	tlbpurgeval,
	tempval,
};

Graph	*graph;
Machine	*mach;
char	*mysysname;
char	argchars[] = "8bcdeEfiIkmlnprstwz";
int	nmach;
int	ngraph;	/* totaly number is ngraph*nmach */
int	sleeptime = 1000;
int	batteryperiod = 1000;
int	tempperiod = 1000;

void
killall(char *s)
{
	exits(s);
}

void*
emalloc(ulong sz)
{
	void *v;
	v = malloc(sz);
	if(v == nil) {
		fprint(2, "stats: out of memory allocating %ld: %r\n", sz);
		killall("mem");
	}
	memset(v, 0, sz);
	return v;
}

void*
erealloc(void *v, ulong sz)
{
	v = realloc(v, sz);
	if(v == nil) {
		fprint(2, "stats: out of memory reallocating %ld: %r\n", sz);
		killall("mem");
	}
	return v;
}

char*
estrdup(char *s)
{
	char *t;
	if((t = strdup(s)) == nil) {
		fprint(2, "stats: out of memory in strdup(%.10s): %r\n", s);
		killall("mem");
	}
	return t;
}

int
loadbuf(Machine *m, int *fd)
{
	int n;


	if(*fd < 0)
		return 0;
	seek(*fd, 0, 0);
	n = read(*fd, m->buf, sizeof m->buf-1);
	if(n <= 0){
		close(*fd);
		*fd = -1;
		return 0;
	}
	m->bufp = m->buf;
	m->ebufp = m->buf+n;
	m->buf[n] = 0;
	return 1;
}

/* read one line of text from buffer and process integers */
int
readnums(Machine *m, int n, uvlong *a, int spanlines)
{
	int i;
	char *p, *ep;

	if(spanlines)
		ep = m->ebufp;
	else
		for(ep=m->bufp; ep<m->ebufp; ep++)
			if(*ep == '\n')
				break;
	p = m->bufp;
	for(i=0; i<n && p<ep; i++){
		while(p<ep && (!isascii(*p) || !isdigit(*p)) && *p!='-')
			p++;
		if(p == ep)
			break;
		a[i] = strtoull(p, &p, 10);
	}
	if(ep < m->ebufp)
		ep++;
	m->bufp = ep;
	return i == n;
}

int
readswap(Machine *m, uvlong *a)
{
	static int xxx = 0;

	if(strstr(m->buf, "memory\n")){
		/* new /dev/swap - skip first 3 numbers */
		if(!readnums(m, 7, a, 1))
			return 0;

		a[Mem] = a[3];
		a[Maxmem] = a[4];
		a[Swap] = a[5];
		a[Maxswap] = a[6];

		a[Reclaim] = 0;
		a[Maxreclaim] = 0;
		if(m->bufp = strstr(m->buf, "reclaim")){
			while(m->bufp > m->buf && m->bufp[-1] != '\n')
				m->bufp--;
			a[Reclaim] = strtoull(m->bufp, &m->bufp, 10);
			while(*m->bufp++ == '/')
				a[Maxreclaim] = strtoull(m->bufp, &m->bufp, 10);
		}

		a[Kern] = 0;
		a[Maxkern] = 0;
		if(m->bufp = strstr(m->buf, "kernel malloc")){
			while(m->bufp > m->buf && m->bufp[-1] != '\n')
				m->bufp--;
			a[Kern] = strtoull(m->bufp, &m->bufp, 10);
			while(*m->bufp++ == '/')
				a[Maxkern] = strtoull(m->bufp, &m->bufp, 10);
		}

		a[Draw] = 0;
		a[Maxdraw] = 0;
		if(m->bufp = strstr(m->buf, "kernel draw")){
			while(m->bufp > m->buf && m->bufp[-1] != '\n')
				m->bufp--;
			a[Draw] = strtoull(m->bufp, &m->bufp, 10);
			while(*m->bufp++ == '/')
				a[Maxdraw] = strtoull(m->bufp, &m->bufp, 10);
		}

		return 1;
	}

	a[Reclaim] = 0;
	a[Maxreclaim] = 0;
	a[Kern] = 0;
	a[Maxkern] = 0;
	a[Draw] = 0;
	a[Maxdraw] = 0;

	return readnums(m, 4, a, 0);
}

char*
shortname(char *s)
{
	char *p, *e;

	p = estrdup(s);
	e = strchr(p, '.');
	if(e)
		*e = 0;
	return p;
}

int
ilog10(uvlong j)
{
	int i;

	for(i = 0; j >= 10; i++)
		j /= 10;
	return i;
}

int
initmach(Machine *m, char *name)
{
	int n, i, j, fd;
	uvlong a[MAXNUM];
	char *p, mpt[256], buf[256];
	Dir *d;

	p = strchr(name, '!');
	if(p)
		p++;
	else
		p = name;
	m->name = estrdup(p);
	m->shortname = shortname(p);
	m->remote = (strcmp(p, mysysname) != 0);
	if(m->remote == 0)
		strcpy(mpt, "");
	else{
		Waitmsg *w;
		int pid;

		snprint(mpt, sizeof mpt, "/n/%s", p);

		pid = fork();
		switch(pid){
		case -1:
			fprint(2, "can't fork: %r\n");
			return 0;
		case 0:
			execl("/bin/rimport", "rimport", name, "/", mpt, nil);
			fprint(2, "can't exec: %r\n");
			exits("exec");
		}
		w = wait();
		if(w == nil || w->pid != pid || w->msg[0] != '\0'){
			free(w);
			return 0;
		}
		free(w);
	}

	snprint(buf, sizeof buf, "%s/dev/swap", mpt);
	m->swapfd = open(buf, OREAD);
	if(loadbuf(m, &m->swapfd) && readswap(m, a))
		memmove(m->devswap, a, sizeof m->devswap);

	snprint(buf, sizeof buf, "%s/dev/sysstat", mpt);
	m->statsfd = open(buf, OREAD);
	if(loadbuf(m, &m->statsfd)){
		for(n=0; readnums(m, nelem(m->devsysstat), a, 0); n++)
			;
		m->nproc = n;
	}else
		m->nproc = 1;
	m->lgproc = ilog10(m->nproc);

	/* find all the ethernets */
	n = 0;
	snprint(buf, sizeof buf, "%s/net/", mpt);
	if((fd = open(buf, OREAD)) >= 0){
		for(d = nil; (i = dirread(fd, &d)) > 0; free(d)){
			for(j=0; j<i; j++){
				if(strncmp(d[j].name, "ether", 5))
					continue;
				snprint(buf, sizeof buf, "%s/net/%s/stats", mpt, d[j].name);
				if((m->etherfd[n] = open(buf, OREAD)) < 0)
					continue;
				if(++n >= nelem(m->etherfd))
					break;
			}
			if(n >= nelem(m->etherfd))
				break;
		}
		close(fd);
	}
	while(n < nelem(m->etherfd))
		m->etherfd[n++] = -1;

	snprint(buf, sizeof buf, "%s/mnt/apm/battery", mpt);
	m->batteryfd = open(buf, OREAD);
	if(m->batteryfd < 0){
		snprint(buf, sizeof buf, "%s/mnt/pm/battery", mpt);
		m->batteryfd = open(buf, OREAD);
	}
	m->bitsybatfd = -1;
	if(m->batteryfd >= 0){
		batteryperiod = 10000;
		if(loadbuf(m, &m->batteryfd) && readnums(m, nelem(m->batterystats), a, 0))
			memmove(m->batterystats, a, sizeof(m->batterystats));
	}else{
		snprint(buf, sizeof buf, "%s/dev/battery", mpt);
		m->bitsybatfd = open(buf, OREAD);
		if(loadbuf(m, &m->bitsybatfd) && readnums(m, 1, a, 0))
			memmove(m->batterystats, a, sizeof(m->batterystats));
	}
	snprint(buf, sizeof buf, "%s/dev/cputemp", mpt);
	m->tempfd = open(buf, OREAD);
	if(m->tempfd < 0){
		tempperiod = 5000;
		snprint(buf, sizeof buf, "%s/mnt/pm/cputemp", mpt);
		m->tempfd = open(buf, OREAD);
	}
	if(loadbuf(m, &m->tempfd))
		for(n=0; n < nelem(m->temp) && readnums(m, 2, a, 0); n++)
			 m->temp[n] = a[0];
	return 1;
}

jmp_buf catchalarm;

int
alarmed(void *a, char *s)
{
	if(strcmp(s, "alarm") == 0)
		notejmp(a, catchalarm, 1);
	return 0;
}

int
needswap(int init)
{
	return init | present[Mmem] | present[Mswap] | present[Mreclaim] | present[Mkern] | present[Mdraw];
}


int
needstat(int init)
{
	return init | present[Mcontext]  | present[Mfault] | present[Mintr] | present[Mload] | present[Midle] |
		present[Minintr] | present[Msyscall] | present[Mtlbmiss] | present[Mtlbpurge];
}


int
needether(int init)
{
	return init | present[Mether] | present[Metherin] | present[Metherout] | present[Methererr];
}

int
needbattery(int init)
{
	static uint step = 0;

	if(++step*sleeptime >= batteryperiod){
		step = 0;
		return init | present[Mbattery];
	}

	return 0;
}

int
needtemp(int init)
{
	static uint step = 0;

	if(++step*sleeptime >= tempperiod){
		step = 0;
		return init | present[Mtemp];
	}

	return 0;
}

void
vadd(uvlong *a, uvlong *b, int n)
{
	int i;

	for(i=0; i<n; i++)
		a[i] += b[i];
}

void
readmach(Machine *m, int init)
{
	int n;
	uvlong a[nelem(m->devsysstat)];
	char buf[32];

	if(m->remote && (m->disable || setjmp(catchalarm))){
		if (m->disable++ >= 5)
			m->disable = 0; /* give it another chance */
		memmove(m->devsysstat, m->prevsysstat, sizeof m->devsysstat);
		memmove(m->netetherstats, m->prevetherstats, sizeof m->netetherstats);
		return;
	}
	snprint(buf, sizeof buf, "%s", m->name);
	if (strcmp(m->name, buf) != 0){
		free(m->name);
		m->name = estrdup(buf);
		free(m->shortname);
		m->shortname = shortname(buf);
	}
	if(m->remote){
		atnotify(alarmed, 1);
		alarm(5000);
	}
	if(needswap(init) && loadbuf(m, &m->swapfd) && readswap(m, a))
		memmove(m->devswap, a, sizeof m->devswap);
	if(needstat(init) && loadbuf(m, &m->statsfd)){
		memmove(m->prevsysstat, m->devsysstat, sizeof m->devsysstat);
		memset(m->devsysstat, 0, sizeof m->devsysstat);
		for(n=0; n<m->nproc && readnums(m, nelem(m->devsysstat), a, 0); n++)
			vadd(m->devsysstat, a, nelem(m->devsysstat));
	}
	if(needether(init)){
		memmove(m->prevetherstats, m->netetherstats, sizeof m->netetherstats);
		memset(m->netetherstats, 0, sizeof(m->netetherstats));
		for(n=0; n<nelem(m->etherfd) && m->etherfd[n] >= 0; n++){
			if(loadbuf(m, &m->etherfd[n]) && readnums(m, nelem(m->netetherstats), a, 1))
				vadd(m->netetherstats, a, nelem(m->netetherstats));
		}
	}
	if(needbattery(init)){
		if(loadbuf(m, &m->batteryfd) && readnums(m, nelem(m->batterystats), a, 0))
			memmove(m->batterystats, a, sizeof(m->batterystats));
		else if(loadbuf(m, &m->bitsybatfd) && readnums(m, 1, a, 0))
			memmove(m->batterystats, a, sizeof(m->batterystats));
	}
	if(needtemp(init) && loadbuf(m, &m->tempfd))
		for(n=0; n < nelem(m->temp) && readnums(m, 2, a, 0); n++)
			 m->temp[n] = a[0];
	if(m->remote){
		alarm(0);
		atnotify(alarmed, 0);
	}
}

void
memval(Machine *m, uvlong *v, uvlong *vmax, int)
{
	*v = m->devswap[Mem];
	*vmax = m->devswap[Maxmem];
	if(*vmax == 0)
		*vmax = 1;
}

void
swapval(Machine *m, uvlong *v, uvlong *vmax, int)
{
	*v = m->devswap[Swap];
	*vmax = m->devswap[Maxswap];
	if(*vmax == 0)
		*vmax = 1;
}

void
reclaimval(Machine *m, uvlong *v, uvlong *vmax, int)
{
	*v = m->devswap[Reclaim];
	*vmax = m->devswap[Maxreclaim];
	if(*vmax == 0)
		*vmax = 1;
}

void
kernval(Machine *m, uvlong *v, uvlong *vmax, int)
{
	*v = m->devswap[Kern];
	*vmax = m->devswap[Maxkern];
	if(*vmax == 0)
		*vmax = 1;
}

void
drawval(Machine *m, uvlong *v, uvlong *vmax, int)
{
	*v = m->devswap[Draw];
	*vmax = m->devswap[Maxdraw];
	if(*vmax == 0)
		*vmax = 1;
}

void
contextval(Machine *m, uvlong *v, uvlong *vmax, int init)
{
	*v = (m->devsysstat[Context]-m->prevsysstat[Context])&0xffffffff;
	*vmax = sleeptime*m->nproc;
	if(init)
		*vmax = sleeptime;
}

/*
 * bug: need to factor in HZ
 */
void
intrval(Machine *m, uvlong *v, uvlong *vmax, int init)
{
	*v = (m->devsysstat[Interrupt]-m->prevsysstat[Interrupt])&0xffffffff;
	*vmax = sleeptime*m->nproc*10;
	if(init)
		*vmax = sleeptime*10;
}

void
syscallval(Machine *m, uvlong *v, uvlong *vmax, int init)
{
	*v = (m->devsysstat[Syscall]-m->prevsysstat[Syscall])&0xffffffff;
	*vmax = sleeptime*m->nproc;
	if(init)
		*vmax = sleeptime;
}

void
faultval(Machine *m, uvlong *v, uvlong *vmax, int init)
{
	*v = (m->devsysstat[Fault]-m->prevsysstat[Fault])&0xffffffff;
	*vmax = sleeptime*m->nproc;
	if(init)
		*vmax = sleeptime;
}

void
tlbmissval(Machine *m, uvlong *v, uvlong *vmax, int init)
{
	*v = (m->devsysstat[TLBfault]-m->prevsysstat[TLBfault])&0xffffffff;
	*vmax = (sleeptime/1000)*10*m->nproc;
	if(init)
		*vmax = (sleeptime/1000)*10;
}

void
tlbpurgeval(Machine *m, uvlong *v, uvlong *vmax, int init)
{
	*v = (m->devsysstat[TLBpurge]-m->prevsysstat[TLBpurge])&0xffffffff;
	*vmax = (sleeptime/1000)*10*m->nproc;
	if(init)
		*vmax = (sleeptime/1000)*10;
}

void
loadval(Machine *m, uvlong *v, uvlong *vmax, int init)
{
	*v = m->devsysstat[Load];
	*vmax = 1000*m->nproc;
	if(init)
		*vmax = 1000;
}

void
idleval(Machine *m, uvlong *v, uvlong *vmax, int)
{
	*v = m->devsysstat[Idle]/m->nproc;
	*vmax = 100;
}

void
inintrval(Machine *m, uvlong *v, uvlong *vmax, int)
{
	*v = m->devsysstat[InIntr]/m->nproc;
	*vmax = 100;
}

void
etherval(Machine *m, uvlong *v, uvlong *vmax, int)
{
	*v = m->netetherstats[In]-m->prevetherstats[In] + m->netetherstats[Out]-m->prevetherstats[Out];
	*vmax = sleeptime;
}

void
etherinval(Machine *m, uvlong *v, uvlong *vmax, int)
{
	*v = m->netetherstats[In]-m->prevetherstats[In];
	*vmax = sleeptime;
}

void
etheroutval(Machine *m, uvlong *v, uvlong *vmax, int)
{
	*v = m->netetherstats[Out]-m->prevetherstats[Out];
	*vmax = sleeptime;
}

void
ethererrval(Machine *m, uvlong *v, uvlong *vmax, int)
{
	int i;

	*v = 0;
	for(i=Err0; i<nelem(m->netetherstats); i++)
		*v += m->netetherstats[i]-m->prevetherstats[i];
	*vmax = (sleeptime/1000)*10;
}

void
batteryval(Machine *m, uvlong *v, uvlong *vmax, int)
{
	*v = m->batterystats[0];
	if(m->bitsybatfd >= 0)
		*vmax = 184;		// at least on my bitsy...
	else
		*vmax = 100;
}

void
tempval(Machine *m, uvlong *v, uvlong *vmax, int)
{
	ulong l;

	*vmax = 100;
	l = m->temp[0];
	if(l == ~0 || l == 0)
		*v = 0;
	else
		*v = l;
}

void
usage(void)
{
	fprint(2, "usage: nstats [-%s] [machine...]\n", argchars);
	exits("usage");
}

void
addgraph(int n)
{
	Graph *g, *ograph;
	int i, j;

	if(n > Nmenu2-1)
		abort();
	ograph = graph;
	graph = emalloc(nmach*(ngraph+1)*sizeof(Graph));
	for(i=0; i<nmach; i++)
		for(j=0; j<ngraph; j++)
			graph[i*(ngraph+1)+j] = ograph[i*ngraph+j];
	free(ograph);
	ngraph++;
	for(i=0; i<nmach; i++){
		g = &graph[i*ngraph+(ngraph-1)];
		memset(g, 0, sizeof(Graph));
		g->label = labels[n];
		g->newvalue = newvaluefn[n];
		g->mach = &mach[i];
	}
	present[n] = 1;
}

int
addmachine(char *name)
{
	if(ngraph > 0){
		fprint(2, "stats: internal error: ngraph>0 in addmachine()\n");
		usage();
	}
	if(mach == nil)
		nmach = 0;	/* a little dance to get us started with local machine by default */
	mach = erealloc(mach, (nmach+1)*sizeof(Machine));
	memset(mach+nmach, 0, sizeof(Machine));
	if (initmach(mach+nmach, name)){
		nmach++;
		return 1;
	} else
		return 0;
}

void
main(int argc, char *argv[])
{
	int i, j;
	double secs;
	uvlong v, vmax, nargs;
	double percentage;
	char args[100];
	Machine *currmach;

	quotefmtinstall();

	nmach = 1;
	mysysname = getenv("sysname");
	if(mysysname == nil){
		fprint(2, "stats: can't find $sysname: %r\n");
		exits("sysname");
	}

	nargs = 0;
	ARGBEGIN{
	case 'T':
		secs = atof(EARGF(usage()));
		if(secs > 0)
			sleeptime = 1000*secs;
		break;
	default:
		if(nargs>=sizeof args || strchr(argchars, ARGC())==nil)
			usage();
		args[nargs++] = ARGC();
	}ARGEND

	if(argc == 0){
		mach = emalloc(nmach*sizeof(Machine));
		initmach(&mach[0], mysysname);
		readmach(&mach[0], 1);
	}else{
		rfork(RFNAMEG);
		for(i=j=0; i<argc; i++){
			if (addmachine(argv[i]))
				readmach(&mach[j++], 1);
		}
		if (j == 0)
			exits("connect");
	}

	for(i=0; i<nargs; i++)
	switch(args[i]){
	default:
		fprint(2, "stats: internal error: unknown arg %c\n", args[i]);
		usage();
	case 'b':
		addgraph(Mbattery);
		break;
	case 'c':
		addgraph(Mcontext);
		break;
	case 'e':
		addgraph(Mether);
		break;
	case 'E':
		addgraph(Metherin);
		addgraph(Metherout);
		break;
	case 'f':
		addgraph(Mfault);
		break;
	case 'i':
		addgraph(Mintr);
		break;
	case 'I':
		addgraph(Mload);
		addgraph(Midle);
		addgraph(Minintr);
		break;
	case 'l':
		addgraph(Mload);
		break;
	case 'm':
		addgraph(Mmem);
		break;
	case 'n':
		addgraph(Metherin);
		addgraph(Metherout);
		addgraph(Methererr);
		break;
	case 'p':
		addgraph(Mtlbpurge);
		break;
	case 'r':
		addgraph(Mreclaim);
		break;
	case 's':
		addgraph(Msyscall);
		break;
	case 't':
		addgraph(Mtlbmiss);
		addgraph(Mtlbpurge);
		break;
	case 'w':
		addgraph(Mswap);
		break;
	case 'k':
		addgraph(Mkern);
		break;
	case 'd':
		addgraph(Mdraw);
		break;
	case 'z':
		addgraph(Mtemp);
		break;
	}

	if(ngraph == 0)
		addgraph(Mload);

	for(i=0; i<nmach; i++)
		for(j=0; j<ngraph; j++)
			graph[i*ngraph+j].mach = &mach[i];

	for(i=0; i<nmach; i++)
		readmach(&mach[i], 0);
	
	currmach = nil;
	for(i=0; i<nmach*ngraph; i++){
		if (nmach > 1 && currmach != graph[i].mach) {
			currmach = graph[i].mach;
			print("%s:\n", currmach->name);
		}
		graph[i].newvalue(graph[i].mach, &v, &vmax, 0);
		if(vmax == 0)
			vmax = 1;
		percentage = (double)v/vmax;
		print("%*s: %6.2f%% (%ulld/%ulld)\n", padlabels, graph[i].label, percentage*100., v, vmax);
	}
}