Click on our Sponsors to help Support SunWorld
Performance Q & A by Adrian Cockcroft

Perfmeter unmasked

We tell you how it works and what data it collects for display

SunWorld
February  1998
[Next story]
[Table of Contents]
[Search]
Subscribe to SunWorld, it's free!

Abstract
The perfmeter tool is well known, but few people know how it works or how easy it is to get at the data it displays. It is a trivial job to generate the code required to read the underlying remote procedure call. The data returned is more extensive than you might think and is quite useful. A new interface to this data from the SE performance toolkit and a remote vmstat clone are described. (3,600 words)


Mail this
article to
a friend
Q How does perfmeter work, and exactly what data does it collect for display?

A perfmeter makes a remote procedure call to the rpc.rstatd daemon on either the local or a remote machine.

The rpc.rstatd daemon is an RPC server that obtains performance data by using kstat, returning the data in an RPC message. The rup(1) and perfmeter(1) commands monitor local and remote system activity by using the rstatd interface. SunNet Manager also uses rpc.rstatd to monitor host performance. Two versions of the protocol are currently defined in /usr/include/rpcsvc/rstat.x. Version 3 is used by SunOS 4; Version 4, shown below, allows a variable number of CPU states and a variable number of disks to be defined. The information returned is the CPU user, system, wait, idle counters, the total number of operations on each disk, pages paged in and out, pages swapped in and out, the number of interrupts, total packet, error and collision counts summed across all interfaces, context switch count, average run queue lengths for 1-, 5-, and 15-minute averages (each run queue value is shifted by 8 bits, so divide by 256.0 and report it in floating point), and a microsecond resolution timestamp for the current time and the time the system was booted.


Advertisements

RPC rstatd returned data structure:

 
struct statsvar {
	struct {
		u_int cp_time_len; 
		int *cp_time_val; 
	} cp_time; 
	struct {
		u_int dk_xfer_len; 
		int *dk_xfer_val; 
	} 
	dk_xfer; u_int
	v_pgpgin; 
	u_int v_pgpgout; 
	u_int v_pswpin; 
	u_int v_pswpout;
	u_int v_intr; 
	int if_ipackets; 
	int if_ierrors; 
	int if_opackets;
	int if_oerrors; 
	int if_collisions; 
	u_int v_swtch; 
	long avenrun[3]; 
	rstat_timeval boottime; 
	rstat_timeval curtime; 
};

You can generate example code that collects this data, and you can use rpcgen(1) to generate a makefile:

 
% cp /usr/include/rpcsvc/rstat.x . 
% rpcgen -a rstat.x 
% ls *rstat* makefile.rstat
rstat.x          rstat_clnt.c     rstat_svc.c rstat.h
rstat_client.c   rstat_server.c   rstat_xdr.c 
% make -f makefile.rstat rstat_client 

You now have a program that gets the data from the system you specify but does nothing with it. We need to modify the main program in rstat_client.c to do something with the returned data structure so that we can see the results. The following code sample accomplishes this. Note that I deleted code for handling older versions of the rstat protocol and added printf statements, as shown by the highlighted code below. Everything else was generated by rpcgen:

 
/*
 * This is sample code generated by rpcgen.  
 * These are only templates. Use them as a 
 * guideline for developing your own functions.  
 */

#include "rstat.h" 
#include  
#include  /* getenv, exit */

/*
 * Copyright (c) 1985, 1990, 1991 by Sun Microsystems, Inc.  
 */ 
/* from rstat.x */

void rstatprog_4(host)
	char *host; 
{
	int i; 
	CLIENT *clnt; 
	statsvar  *rs; 
	char *
	rstatproc_stats_4_arg; 
	u_int  *result_2; 
	char * rstatproc_havedisk_4_arg;

#ifndef DEBUG
	clnt = clnt_create(host, RSTATPROG, RSTATVERS_VAR, "netpath");
	if (clnt == (CLIENT *) NULL) {
		clnt_pcreateerror(host); 
		exit(1); 
	} 
#endif  /* DEBUG */

	rs = rstatproc_stats_4((void *)&rstatproc_stats_4_arg, clnt);
	if (rs == (statsvar *) NULL) {
		clnt_perror(clnt, "call failed"); 
	} 
	result_2 = rstatproc_havedisk_4((void *)&rstatproc_havedisk_4_arg, clnt);
	if (result_2 == (u_int *) NULL) {
		clnt_perror(clnt, "call failed"); 
	}
        printf("rstat information from %s at: %s", host,
		ctime(&rs->curtime.tv_sec)); 
	printf("booted at %s",
		ctime(&rs->boottime.tv_sec)); 
	printf("pgpgin: %d pgpgout: %d  pswpin: %d  pswpout: %d\n",
		rs->v_pgpgin, rs->v_pgpgout, rs->v_pswpin, rs->v_pswpout); 
	printf("intr: %d  swtch: %d  avenrun: %3.1f %3.1f %3.1f\n",
		rs->v_intr, rs->v_swtch, rs->avenrun[0]/256.0,
		rs->avenrun[1]/256.0, rs->avenrun[2]/256.0);
	printf("ipackets: %d  ierrors: %d  opackets: %d  oerrors: %d \ collisions: %d\n",
		rs->if_ipackets, rs->if_ierrors, rs->if_opackets,
		rs->if_oerrors, rs->if_collisions); 
	/* CPU states are 0=USER 1=WAIT 2=KERNEL 3=IDLE - weird... */ 
	printf("usr: %d sys: %d  wio: %d  idle: %d\n",
	  	rs->cp_time.cp_time_val[0], 
		rs->cp_time.cp_time_val[2],
	  	rs->cp_time.cp_time_val[1], 
		rs->cp_time.cp_time_val[3]);
	for(i=0; i < rs->dk_xfer.dk_xfer_len; i++) {
	    printf("disk: %d  xfers: %d\n", i, rs->dk_xfer.dk_xfer_val[i]); 
	} 
#ifndef DEBUG
	clnt_destroy(clnt); 
#endif          /* DEBUG */ 
}

main(argc, argv)
	int argc; char *argv[]; 
{
	char *host;

	if (argc < 2) {
		\x11\x11printf("usage:  %s server_host\n", argv[0]);
		\x11\x11exit(1); 
	} 
	host = argv[1]; 
	rstatprog_4(host); 
}

The raw data printout is shown below. The values are counters, so to get per-second rates, you would have to take measurements twice and use the differences over a time interval. The current time does include a microsecond resolution component, so accurate time differences are available. Some counters may wrap around, as intr has in the example, but differences will still be valid.

 
% rstat_client localhost rstat information from
localhost at: Fri Dec 19 09:21:53 1997 
booted at Sat Nov 29 11:42:36 1997 
pgpgin: 186806  pgpgout: 105086  pswpin: 24  pswpout: 145 
intr: -25232137  swtch: 56685160  avenrun: 0.0 0.0 0.0 
ipackets: 694345 ierrors: 0  opackets: 736283  oerrors: 0  collisions: 1 
usr: 1184180 sys: 351773  wio: 280473  idle: 127491765 
disk: 0  xfers: 1 
disk: 1  xfers: 225406 
disk: 2  xfers: 162365 
disk: 3  xfers: 37496 

The disk data is hard to map to named disk devices. I compared it with counters obtained from iostat to check this; the order is the same:

 
% iostat -xI
			      extended disk statistics
disk      r/i      w/i     Kr/i      Kw/i wait actv  svc_t  %w  %b
fd0       1.0      0.0      0.0       0.0  0.0  0.0    7.8   0   0 
sd0  118384.0 108737.0 841384.5 1344148.0  0.2  0.1  477.7   0   1 
sd1   94500.0  68373.0 615959.5  742512.0  0.0  0.0   53.1   0   1 
sd5   28362.0   9144.0 155954.5   78172.0  0.0  0.0   40.8   0   0 


Figure 1: Example perfmeter output

The data shown by perfmeter in Figure 1 summarizes all this information by adding together CPU user and system time, all paging activity, packets in and out, and all disk I/O counters. Perfmeter can log the data it is displaying to a file and has more options than most users realize, so take a look at the manual page.

Interfacing the SE Toolkit to the rstat RPC data
So far, we have code in C that gets at a new source of performance information. It is an interesting data source because it is enabled by default on all Solaris (and most other Unix) systems, and you don't need to install anything or be able to login to the machine to monitor it. I like to have all my performance data available in the SE language, so I spent a few hours working out how. It also acts as a useful tutorial in how to interface C code to SE scripts.

There are three components that need to be built. First, the existing C code must be turned into a set of callable routines in a shared library. Second, an SE header file must be built to interface to the shared library and implement an SE class structure that returns the data in a usable form. Third, a test script is needed to display the class data and provide a starting point for any other scripts that use this data source.

Shared library rstat client
The code was modified to make the RPC client data persistent, and it was broken out into three routines and a debug switch, which provide the public API to the library:

 
int rstat_debug;

int rstat_setup(char * host);

statsvar *rstat_update();

void rstat_shutdown(); 

If debug_switch is set nonzero, then the debug printf statements are executed to produce the same output we saw in the first rstat_client program.

The rstat_setup call is given the hostname to collect data from, and returns zero if it has a problem, nonzero if it succeeds.

Each time rstat_update is called, the RPC call is made to the server being monitored, and an updated statsvar data structure is returned.

To terminate the client and free up its resources, rstat_shutdown is used. Failure to shutdown, when accessing several hosts in sequence, would cause a memory leak. The new code is shown in librstat.c. It is compiled with the same files as the rstat_client program, but with the -G linker option to generate a shared library:

 
% cc -G -o librstat.so rstat_clnt.c librstat.c
rstat_xdr.c 

Sample code for librstat.c:

 
/*
 * This is sample code generated by rpcgen.  
 * These are only templates and you can use them 
 * as a guideline for developing your own functions. 
 */

#include "rstat.h" 
#include  
#include  
/* getenv, exit */

/*
 * Copyright (c) 1985, 1990, 1991 by Sun Microsystems, Inc.  
 */ 
/* from rstat.x */ 
/* externally visible */ 
int     rstat_debug = 0;        /* set to 1 to get printf */

/* private */ 
static  CLIENT *clnt; 
static  statsvar  *rs; 
static  char *  rstatproc_stats_4_arg; 
static  char *  rstatproc_havedisk_4_arg;
static  char    hostname[80];

int rstat_setup(char *host) {
	clnt = clnt_create(host, RSTATPROG, RSTATVERS_VAR, "netpath");
	if (clnt == (CLIENT *) NULL) {
		clnt_pcreateerror(host); return 0; /* fail */ 
	} else {
		strncpy(hostname, host, 79); 
		return 1; /* success */ 
	}
}

statsvar *rstat_update() {
	u_int * result_2; 
	int i;

	rs = rstatproc_stats_4((void *)&rstatproc_stats_4_arg, clnt);
	if (rs == (statsvar *) NULL) {
		clnt_perror(clnt, "call failed"); 
	}

	result_2 = rstatproc_havedisk_4((void *)&rstatproc_havedisk_4_arg, clnt);
	if (result_2 == (u_int *) NULL) {
		clnt_perror(clnt, "call failed"); 
	}

if (rstat_debug) {
	printf("rstat information from %s at: %s", hostname,
		ctime(&rs->curtime.tv_sec)); 
	printf("booted at %s",
		ctime(&rs->boottime.tv_sec)); 
	printf("pgpgin: %d pgpgout: %d  pswpin: %d  pswpout: %d\n",
		rs->v_pgpgin, rs->v_pgpgout, rs->v_pswpin, rs->v_pswpout); 
	printf("intr: %d  swtch: %d  avenrun: %3.1f %3.1f %3.1f\n",
		rs->v_intr, rs->v_swtch, rs->avenrun[0]/256.0,
		rs->avenrun[1]/256.0, rs->avenrun[2]/256.0);
	printf("ipackets: %d  ierrors: %d  opackets: %d  oerrors: %d
		collisions: %d\n", rs->if_ipackets, rs->if_ierrors,
		rs->if_opackets, rs->if_oerrors, rs->if_collisions); 
	/* CPU states are 0=USER 1=WAIT 2=KERNEL 3=IDLE - wierd... */
	printf("usr: %d  sys: %d  wio: %d  idle: %d\n",
		rs->cp_time.cp_time_val[0], 
		rs->cp_time.cp_time_val[2],
		rs->cp_time.cp_time_val[1], 
		rs->cp_time.cp_time_val[3]); 
	for(i=0; i < rs->dk_xfer.dk_xfer_len; i++) {
		printf("disk: %d  xfers: %d\n", i, rs->dk_xfer.dk_xfer_val[i]); 	} 
     } 
     return rs; 
}

void rstat_shutdown() {
	clnt_destroy(clnt); 
} 

SE interface rstat_class.se
SE uses a simple syntax to attach to shared libraries. The attach statement specifies where to find the library. By default for SPARC systems libraries should be in /opt/RICHPse/lib/sparc. Like many other SE classes I decided to return the measurements as differences over time, rather than absolute values of counters. The SE code must call rstat_update, save the resulting data, then on the next call, return the differences or average per-second rates over that interval. The first time around, the average since the system was booted can be returned instead. Time stamps are provided in the rstat data, so measurements can be accurate, without needing to worry about any network delays.

The code is a little tricky where pointers are used. SE handles pointers using integers and two routines, struct_fill and struct_empty, which fill or empty an SE structure from or to an address. The returned data from the rstat_update call is a pointer, and the statsvar structure also contains two pointers -- one to the CPU data and one to the disk data. The CPU data is always four words long on Solaris 2, so I cheated and defined a structure rather than an array for convenience. The disk data is variable length, so I have to read it one element at a time into a dummy structure definition with some pointer arithmetic. I also need to allocate an array to hold the disk data. I make the array at least big enough to hold the number of disks on the local system, then added 100 to allow for monitoring a big system remotely from a small desktop. It's trivial to edit this if you want more and doesn't use up enough space to worry about on small systems.

The CPU data is returned in units of ticks. This accumulates over all CPUs, so if it adds up to several times the tick rate, there are several CPUs. It's a bit unreliable, as the math doesn't work if the system has been put through save/resume cycles or had its date changed significantly; but I can calculate how many CPUs there are.

Sample code for rstat_class.se:

 
/* rstat_class.se reads data using rstat RPC protocol */ 
/* Adrian 23 Jan 98 */

/*
 * Scale factor for scaled integers used to count load averages.  
 */

#ifndef FSCALE 
#define FSHIFT  8       

/* bits to right of fixed binary point */ 
#define FSCALE  (1<<FSHIFT) 
#endif  /* ndef FSCALE */

/* this is a hack, but its not a lot of space so be generous */ 

#define RSTAT_DISK_COUNT(MAX_DISK+100) 

/* lots, 100 remote */

#define RSTAT_CPUSTATES 4 
#define RSTAT_CPU_USER 0 
#define RSTAT_CPU_WAIT 1 
#define RSTAT_CPU_SYS 2 
#define RSTAT_CPU_IDLE 3

/* this is what comes back from the rpc call */ 
struct statsvar {
	ulong cp_time_len; /* always 4 */ 
	ulong cp_time_val_ptr; 
	ulong dk_xfer_len; 
	ulong dk_xfer_val_ptr; 
	ulong v_pgpgin; 
	ulong v_pgpgout; 
	ulong v_pswpin; 
	ulong v_pswpout; 
	ulong v_intr; int
	if_ipackets; 
	int if_ierrors; 
	int if_opackets; 
	int if_oerrors;
	int if_collisions; 
	ulong v_swtch; 
	long avenrun[3]; 
	timeval_t boottime; 
	timeval_t curtime; 
};

struct statscpu {
	ulong user; 
	ulong wait; 
	ulong system; 
	ulong idle; 
};

struct statsdisk {
	ulong xfer;     /* needed so struct fill can be used */ 
};

attach "librstat.so" {
	extern int rstat_debug; int rstat_setup(string host); ulong
	rstat_update();   /* returns address of statvar */ 
	int rstat_shutdown(); 
};

/* strcmp handling nil strings */ 
int strdiff(string s1, string s2) {
	if (s1 == nil || s2 == nil) {
		return 1; /* both nil taken as being different */ 
	}
	return strcmp(s1, s2);  /* returns 0 if they are the same */ 
}

class rstat_class {
	string hostname$; /* hostname, set it to localhost for local data */ 
	double interval; /* seconds */ 
	double user;     /* percentages over the interval */ double system; double wait;
	double idle; 
	ulong ncpus;	/* number of CPUs */
	ulong dk_xfer_len; /* number of disks */ 
	ulong dk_xfer_val[RSTAT_DISK_COUNT]; /* number of ops in interval */
	ulong v_pgpgin; /* number in interval, divide to get rate */
	ulong v_pgpgout; 
	ulong v_pswpin; 
	ulong v_pswpout; 
	ulong v_intr;
	int   if_ipackets; 
	int   if_ierrors; 
	int   if_opackets; 
	int if_oerrors; 
	int   if_collisions; 
	ulong v_swtch; 
	double avenrun_1m; 
	double avenrun_5m; 
	double avenrun_15m; 
	timeval_t boottime; 
	timeval_t curtime;

	rstat$() {
		ulong dk_old[RSTAT_DISK_COUNT]; 
		long hz = sysconf(_SC_CLK_TCK); 
		statsdisk dk_new; 
		string client;
		statsvar sv_new; 
		statsvar sv_old;
#define DIFF(x) x = sv_new.x - sv_old.x
		statscpu cp_new; 
		statscpu cp_old; 
		int tmp; 
		int i;

		if (strdiff(client, hostname$) != 0) {
			/* different or nil */ 
			if (client != nil) {
				rstat_shutdown(); /* clear out old client */ 
			} 
			if (hostname$ == nil) {
				return; 
			} 
			if (rstat_setup(hostname$) == 0) {
				perror("bad rstat hostname"); return; 
			}
			client = hostname$; 
		} 
		tmp = rstat_update(); 
		if (tmp == 0) {
			perror("rstat read error"); 
			client = nil;   /* reset for next try */ 
			rstat_shutdown(); 
			return;
		}

		struct_fill(sv_new, tmp); 
		struct_fill(cp_new,
		sv_new.cp_time_val_ptr); 
		for(i = 0; i < RSTAT_DISK_COUNT && i < sv_new.dk_xfer_len; i++)	{
			/* simulate pointer arithmetic */
			struct_fill(dk_new, sv_new.dk_xfer_val_ptr + 4*i); 
			dk_xfer_val[i] = dk_new.xfer - dk_old[i];
			dk_old[i] = dk_new.xfer; 
		} 
		dk_xfer_len = sv_new.dk_xfer_len; 
		if (sv_old.curtime.tv_sec == 0) {
			/* first time in */ 
			sv_old.curtime = sv_new.boottime; 
		} 
		interval = (sv_new.curtime.tv_sec + 
			sv_new.curtime.tv_usec/1000000.0) - 
			(sv_old.curtime.tv_sec + 
			sv_old.curtime.tv_usec/1000000.0); 
		if (interval == 0.0) {
			return; 
		}

		/* units are clock ticks */ 
		user = (cp_new.user - cp_old.user) / interval; 
		system = (cp_new.system - cp_old.system) / interval; 
		wait = (cp_new.wait - cp_old.wait) / interval; 
		idle = (cp_new.idle - cp_old.idle) / interval; 
		/* adds up to hz per CPU but allow for rounding and trunc */ 
		ncpus = (user + system + wait + idle) / (hz-1);
		if (ncpus == 0) {
		     ncpus = 1;      /* save/resume or date set problem */ 
		} 
		user /= ncpus; 
		system /= ncpus; 
		wait /= ncpus; 
		idle /= ncpus;

		DIFF(v_pgpgin); 
		DIFF(v_pgpgout); 
		DIFF(v_pswpin);
		DIFF(v_pswpout); 
		DIFF(v_intr); 
		DIFF(if_ipackets);
		DIFF(if_ierrors); 
		DIFF(if_opackets); 
		DIFF(if_oerrors);
		DIFF(if_collisions); 
		DIFF(v_swtch); 
		avenrun_1m = sv_new.avenrun[0]/256.0;
		avenrun_5m = sv_new.avenrun[1]/256.0; 
		avenrun_15m = sv_new.avenrun[2]/256.0; 
		boottime = sv_new.boottime; 
		curtime = sv_new.curtime;

		sv_old = sv_new; 
		cp_old = cp_new; 
	} 
}; 

SE remote rvmstat.se example program
I decided to build something that looked a bit like vmstat -- it has different data available and only has room to list the first four disks, but it's a good starting place. The rstat_class.se code must be placed in /opt/RICHPse/include so it can be included by the preprocessor. As usual the SE code to use a class is trivial. After handling the command line arguments it is a simple printf in a loop with a call to read the rstat_class data and a sleep.

SE test program rvmstat.se:

 
/* test program for rstat class */ 
#include  
#include  
#include  
#include  
#include  
#include 

main(int argc, string argv[3]) {
	rstat_class rstat$r; 
	rstat_class tmpr; 
	int sleeptime = 5;
	string host = "localhost"; 
	int lines; 
	int start;

	switch(argc) {
		case 4: rstat_debug = 1; 
		case 3: sleeptime = atoi(argv[2]); 
		case 2: host = argv[1]; 
		case 1: break;
		default: printf("usage: se %s [hostname] [interval] [debug]\n",
				argv[0]);
	} 
	rstat$r.hostname$ = host; 
	tmpr = rstat$r; /* grab initial stapshot */ 
	start = tmpr.curtime.tv_sec; 
	printf("Hostname %s, %d CPUs, %d disks, interval %ds, %s", 
		host, tmpr.ncpus, tmpr.dk_xfer_len, 
		sleeptime, ctime(&start)); 
	for(lines=0;;lines++) {
		sleep(sleeptime); 
		if (lines % 24 == 0) {
printf(" load average   pages      disk ops       faults     net packets      cpu\n"); 
printf("  1m  5m 15m   pi   po   0   1   2   3 in    cs   in  out er cl usr sys idl\n");
		} 
		tmpr = rstat$r; 
		printf("%4.1f %3.1f %3.1f ",
			tmpr.avenrun_1m, tmpr.avenrun_5m, tmpr.avenrun_15m); 
		printf("%4.0f %4.0f %3.0f %3.0f %3.0f %3.0f ",
			tmpr.v_pgpgin/tmpr.interval,
			tmpr.v_pgpgout/tmpr.interval,
			tmpr.dk_xfer_val[0]/tmpr.interval,
			tmpr.dk_xfer_val[1]/tmpr.interval,
			tmpr.dk_xfer_val[2]/tmpr.interval,
			tmpr.dk_xfer_val[3]/tmpr.interval);
			     printf("%4.0f %5.0f %4.0f %4.0f %2.0f
			     %2.0f ", tmpr.v_intr/tmpr.interval,
			tmpr.v_swtch/tmpr.interval,
			tmpr.if_ipackets/tmpr.interval,
			tmpr.if_opackets/tmpr.interval,
			(tmpr.if_ierrors+tmpr.if_oerrors)/tmpr.interval,
			tmpr.if_collisions/tmpr.interval);
			     printf("%3.0f %3.0f %3.0f\n", tmpr.user, 
				tmpr.system, tmpr.idle + tmpr.wait);
	}

} 

Here are some examples of use of rvmstat.se in action. The command takes up to three arguments, a hostname, a time interval in seconds, and a debug flag. It defaults to localhost with a 5 second interval. The data shown is the load averages, pages in and out (we cannot tell the page size remotely), disk operations per-second for the first four disks, interrupt and context switch rates, total network packet rates, input, output error and collision packets per-second, and the CPU user system and idle time. It's a lot more useful information than perfmeterdisplays.

 
% se rvmstat.se Hostname localhost, 2 CPUs, 4 disks, interval 5s, Fri Jan 23 10:27:23 1998 
load average   pages      disk ops       faults     net packets      cpu
 1m  5m 15m   pi   po   0   1   2   3   in    cs   in  out er cl usr sys idl 
0.0 0.0 0.1    0    0   0   0   0   0  105    59    2    2  0  0   1   0  99 
0.0 0.0 0.1    0    0   0   5   1   0  130    72    2    2  0  0   3   0  96 
0.0 0.0 0.1    0    0   0   0   0   0  102    75    2    2  0  0   0   0 100 

This example shows the data for another system on the same network:

 
% se rvmstat.se otherhost Hostname otherhost, 1 CPUs, 1 disks, interval 5s, Fri Jan 23 10:41:07 1998
load average   pages      disk ops       faults     net packets      cpu
 1m  5m 15m   pi   po   0   1   2   3   in    cs   in  out er cl usr sys idl 
0.0 0.0 0.0    0    0   0   0   0   0   31    27    0    1  0  0   0   0 100 
0.0 0.0 0.0    0    0   0   0   0   0   74    46    0    1  0  0   6   1  93 
0.0 0.0 0.0    0    0   4   0   0   0  288   160    0    1  0  0  29   7  63 
0.0 0.0 0.0    0    0   0   0   0   0  162    87    0    1  0  0  10   2  88 

If the time interval is specified, the debug flag can also be added, and the raw data counts being returned can be seen. I made the output data bold in the following example, so it can be seen amongst all the debug output.

 
% se rvmstat.se otherhost 2 1 
rstat information from otherhost at: Fri Jan 23 10:42:50 1998 
booted at Fri Jan  9 12:29:05 1998 
pgpgin: 48861  pgpgout: 78299  pswpin: 0  pswpout: 0
intr: -75704389  swtch: 16122403  avenrun: 0.0 0.0 0.0 
ipackets: 51700 ierrors: 0  opackets: 120217  oerrors: 6600  collisions: 0 
usr: 1209483  sys: 320960  wio: 79774  idle: 19626811 
disk: 0  xfers: 108022 

Hostname otherhost, 1 CPUs, 1 disks, interval 2s, Fri Jan 23
10:42:50 1998
 load average   pages      disk ops       faults     net packets      cpu
  1m  5m 15m   pi   po   0   1   2   3   in    cs   in  out er cl usr sys idl
 
rstat information from otherhost at: Fri Jan 23 10:42:52 1998 
booted at Fri Jan  9 12:29:05 1998 
pgpgin: 48861 pgpgout: 78299  pswpin: 0  pswpout: 0 
intr: -75704298  swtch: 16122485 avenrun: 0.0 0.0 0.0 
ipackets: 51703  ierrors: 0  opackets: 120221 oerrors: 6600  collisions: 0 
usr: 1209483  sys: 320961  wio: 79774 idle: 19627010 
disk: 0  xfers: 108022  
 0.0 0.0 0.0    0    0   0   0   0   0   45    41    1    2  0  0   0   0  99 
rstat information from otherhost at: Fri Jan 23 10:42:54 1998 
booted at Fri Jan  9 12:29:05 1998 
pgpgin: 48861  pgpgout: 78299  pswpin: 0  pswpout: 0 
intr: -75704249  swtch: 16122529  avenrun: 0.0 0.0 0.0 
ipackets: 51705  ierrors: 0  opackets: 120223  oerrors: 6600  collisions: 0 
usr: 1209483  sys: 320962  wio: 79774  idle: 19627209 
disk: 0  xfers: 108022
 0.0 0.0 0.0    0    0   0   0   0   0   25    22    1    1  0 0   0   1 100 

Wrap up
I didn't set out to write rvmstat.se this month, but when I found out how easy it was to get at the rstat information I couldn't resist seeing if I could make it callable from SE. This code will be added to a future version of the SE toolkit and can be downloaded as a tar file from the main SE3 download page. I hope you find it useful, and that it may inspire some other developments that use C code libraries to do the hard work.


Click on our Sponsors to help Support SunWorld


Resources

Other Cockcroft columns at www.sun.com

About the author
Adrian Cockcroft joined Sun Microsystems in 1988, and currently works as a performance specialist for the Server Division of SMCC. He wrote Sun Performance and Tuning: SPARC and Solaris, published by SunSoft Press PTR Prentice Hall. Reach at adrian.cockcroft@sunworld.com.

What did you think of this article?
-Very worth reading
-Worth reading
-Not worth reading
-Too long
-Just right
-Too short
-Too technical
-Just right
-Not technical enough
 
 
 
    

SunWorld
[Table of Contents]
Subscribe to SunWorld, it's free!
[Search]
Feedback
[Next story]
Sun's Site

[(c) Copyright  Web Publishing Inc., and IDG Communication company]

If you have technical problems with this magazine, contact webmaster@sunworld.com

URL: http://www.sunworld.com/swol-02-1998/swol-02-perf.html
Last modified: