#!mkcmd
# $Id: rrdisk.m,v 2.37 2008/07/17 16:13:07 ksb Exp $
# Filter a popen'd iostat and send it to PEG -- ksb, csg
#
# We instance a new stdin, either
#	iostat -Ixn [-p] [-P] [-e] delay [disks] |
#	iostat -a -d delay [disks] |
# and a new stdout
#	|rrdup target
# to push disk stats into an RRD for a performance monitor web site.
#
from '<netdb.h>'
from '<ctype.h>'
from '<fcntl.h>'
from '<sys/types.h>'
from '<sys/stat.h>'
from '<dirent.h>'
from '<unistd.h>'
from '"machine.h"'

basename "rrdisk" ""
%i
static int AtotmAutoZone();
%%

require "util_errno.m"
require "std_help.m" "std_control.m" "std_version.m"
require "util_ppm.m" "util_fgetln.m" "util_whittle.m"
require "util_pipefit.m"
require "time_tz.m"
require "rrrdupdate.m"

%i
static char rcsid[] =
	"$Id: rrdisk.m,v 2.37 2008/07/17 16:13:07 ksb Exp $";
extern char **environ;
static char **ppcOld, **ppcNew, **ppcMap, **ppcMetaMap;
static char acSufix[] =
	TUNE_OUR_DOMAIN;

static char acMdRoot[] =
	RRDISK_MD_ROOT;
%%

augment action 'V' {
	user "BuildMap(USE_IOSTAT_TYPE);Version(stdout);"
}
boolean 'P' {
	hidden named "fParOnly"
	help "passed to iostat, per-partition disk statistics only"
}
boolean 'ps' {
	named "fPerPar"
	help "allow per-partition disk statistics (a.k.a. -s under vxstat)"
}
boolean 'U' {
	named "fBustedIOStat"
	help "used when iostat -l is broken (sigh)"
}
char* 'g' {
	once
	named "pcVxGroup"
	param "diskgroup"
	help "use vxstat for Veritas io stats, for this group"
}
boolean 'e' {
	hidden
	named "fErrors"
	help "monitor errors, unimplemented"
}
string ["MAXHOSTNAMELEN"] 'N' {
	named "acOurName"
	before "gethostname(%n, sizeof(%n));Whittle(%n, acSufix);"
	param "hostname"
	help "fake our hostname, when gethostname is wrong"
}
char* 'u' {
	named "pcTemplate"
	track
	after 'if (!%U) {%n = ((char *)0 == %rgn) ? "host/%%s/d-%%s.rrd" : "host/%%s/vx-%%s.rrd";}'
	help "an update path, if the default is not what you want"
}
boolean 'O' {
	hidden named "fUseOld"
	init "0"
	help "use the old device names for update name"
}
number {
	named "iDelay"
	param "seconds"
	init "110"		# 5m/e
	help "number of seconds between polls (passed to iostat)"
}
char* 't' {
	named "pcPeg"
	param "target"
	init '"peg.sac.fedex.com:31415"'
	help "rrd host to update"
}
char* 'T' {
	named "pcTimeZone"
	param "TZ"
	help "specify the timezone to assume for vxstat input"
}
type "argv" 'm' {
	named "pvMeta"
	param "number=symbolic"
	help "list of meta mappings"
}
boolean 'A' {
	named "fAllToo"
	help "when a command line disk list is given include all other disks"
}
list {
	named "List"
	param "devices"
	help "list of devices to pass to iostat or vxstat"
}

char* named "pcIostat" {
	before 'pcIostat = %D/iostat/pqv;'
}
# We need to find "op" and "vxstat" if they exist on this host,
# mkcmd'd key search should to the trick.
# The name "false" must match acBroken, this is a trick to make mkcmd
# find some program (not the one we're looking for, so we compile. At
# run-time we can carp about it if we really needed it (viz. "vxstat") --ksb
%i
static char acBroken[] = "false";
%%
key "op" 1 init {
	"op" "false"
}
key "vxstat" 1 init {
	"vxstat" "false"
}
# should honor USE_OP_FOR_VS, but find both
char* named "pcOpPath" {
	before 'pcOpPath = %K<op>?p1qv;'
}
char* named "pcVxPath" {
	before 'pcVxPath = %K<vxstat>?p1qv;'
}
after 'BuildMap(USE_IOSTAT_TYPE);'

# pipefit grabs -n/-v, but we really don't want -n support, turn it off.
augment boolean 'n' {
	hidden init '1'
	user '%n = 1; /* force this on always */'
}

%c
/* dump the new<->old device name table for this host			(ksb)
 */
static void
Version(fp)
FILE *fp;
{
	register int i;
	auto struct stat stExists;

	fprintf(fp, "%s: we report as `%s'", progname, acOurName);
	if ((char *)0 != pcTimeZone) {
		fprintf(fp, ", with TZ=%s", pcTimeZone);
	}
	fprintf(fp, "\n%s: timezone offset %d minutes\n", progname, AtotmAutoZone());
	fprintf(fp, "%s: iostat as \"%s\"\n", progname, pcIostat);
	fprintf(fp, "%s: op as \"%s\"\n", progname, pcOpPath);
	fprintf(fp, "%s: vxstat as \"%s\"\n", progname, pcVxPath);
	fprintf(fp, "%s: iostat type `%c'\n", progname, USE_IOSTAT_TYPE);
	if ('\000' != acMdRoot[0]) {
		fprintf(fp, "%s: md root: %s", progname, acMdRoot);

		if (-1 == stat(acMdRoot, & stExists)) {
			fprintf(fp, " [nonexistent]\n");
		} else if (S_IFDIR != (stExists.st_mode & S_IFMT)) {
			fprintf(fp, " [not a directory]\n");
		} else {
			fprintf(fp, " [mode %04o]\n", stExists.st_mode & 07777);
		}
	}
	if ((char **)0 != ppcMetaMap && (char *)0 != *ppcMetaMap) {
		fprintf(fp, "%s: meta mapping:\n", progname);
		for (i = 0; (char *)0 != ppcMetaMap[i]; ++i) {
			fprintf(fp, "\t%s\n", ppcMetaMap[i]);
		}
	}
	if ((char **)0 == ppcOld || (char **)0 == ppcNew) {
		fprintf(fp, "%s: no disk name mapping\n", progname);
	} else {
		fprintf(fp, "%s: mapping:\n", progname);
		for (i = 0; (char *)0 != ppcOld[i] && (char *)0 != ppcNew[i]; ++i) {
			fprintf(fp, "\t%s %s\n", ppcOld[i], ppcNew[i]);
		}
	}
	fflush(fp);
}

static void u_argv_add(struct PPMnode *, char *);

/* Solaris is a pain in my side: the meta devices for disksets		(ksb)
 * show up in 3 different ways, and we need to map them.
 * Viz. in (for example) /dev/md there will be symbolic links like
 *	photon -> shared/1
 * which means we need to map "1=photon" in "1/d80".  So iostat
 * needs to see "photon/d80".  But it reports "1/d80" or "1/md80".
 * I doubt any of the Sun Engineers that did this are bright enough to
 * understand how fwcked up it is for the rest of us.
 */
static char **
MetaMapping(pcRoot)
char *pcRoot;
{
	register DIR *pDIMdRoot;
	register struct dirent *pDE;
	register int i;
	register char *pcSlash;
	auto char acReadMe[MAXPATHLEN+4], acIndir[MAXPATHLEN+4];

	if ((DIR *)0 == (pDIMdRoot = opendir(pcRoot))) {
		return (char **)util_ppm_size(pvMeta, 0);
	}

	/* look for symbolic links in the md root (readlink tells me)
	 * that point to a number in the local directory
	 */
	while ((struct dirent *)0 != (pDE = readdir(pDIMdRoot))) {
		if ('.' == pDE->d_name[0] && ('\000' == pDE->d_name[1] || ('.' == pDE->d_name[1] && '\000' == pDE->d_name[2])))
			continue;

		sprintf(acReadMe, "%s/%s", pcRoot, pDE->d_name);
		if (-1 == (i = readlink(acReadMe, acIndir, sizeof acIndir)))
			continue;
		acIndir[i] = '\000';
		if ('/' == acIndir[0] || (char *)0 == (pcSlash = strchr(acIndir, '/')))
			continue;
		*pcSlash++ = '\000';
		for (i = 0; '\000' != pcSlash[i]; ++i) {
			if (!isdigit(pcSlash[i]))
				break;
		}
		if ('\000' != pcSlash[i])
			continue;
		sprintf(acReadMe, "%s=%s", pcSlash, pDE->d_name);
		u_argv_add(pvMeta, strdup(acReadMe));
	}
	return (char **)util_ppm_size(pvMeta, 0);
}

/* Don't send these unless asked					(ksb)
 *	tapes		rmt/number
 *	nfs		nostromo.sac.fedex.com:vold(pid535)
 *	floppies	fd0
 */
static int
Ignore(pcDev, piSet)
char *pcDev;
int *piSet;
{
	static int iDef = 0;

	switch (*pcDev) {
	case 'f':
		if (0 == strcmp("fd0", pcDev)) {
			static int iFloppy = 1;
			if ((int *)0 != piSet) {
				iFloppy = *piSet;
			}
			return iFloppy;
		}
		break;
	case 'r':
		if (0 == strncmp("rmt/", pcDev, 4)) {
			static int iTape = 1;
			if ((int *)0 != piSet) {
				iTape = *piSet;
			}
			return iTape;
		}
		break;
	default:
		break;
	}
	if ((char *)0 != strchr(pcDev, '('/*)*/)) {
		static int iNfs = 1;
		if ((int *)0 != piSet) {
			iNfs = *piSet;
		}
		return iNfs;
	}
	if ((int *)0 != piSet) {
		iDef = *piSet;
	}
	return iDef;
}


/* find the name in the list, return the index, or -1			(ksb)
 */
static int
FindName(pcName, ppcList)
char *pcName, **ppcList;
{
	register int i;
	register char *pc;

	for (i = 0; (char *)0 != (pc = *ppcList++); ++i) {
		if (0 == strcmp(pc, pcName))
			return i;
	}
	return -1;
}


/* return the slash in a diskset device name, or NULL			(ksb)
 */
static char *
IsDiskSetName(pcDev)
char *pcDev;
{
	if ((char *)0 == pcDev)
		return "imp";
	while (isdigit(*pcDev))
		++pcDev;
	return '/' == *pcDev ? pcDev : "gimp";
}

/* Open 2 iostat's, one with the old names, one with the new		(ksb)
 * names.  Then build a map list ppcOld <-> ppcNew as two argvs.
 * We do not sanity check the lines from iostat as well as we should.
 */
static void
BuildMap(fType)
int fType;
{
	auto PPM_BUF PMOld, PMNew;
	auto size_t iLineLen;
	static char *apcIoClue[10];
	auto int iWas;
	register int c, i, iPrefix;
	register char *pcLine, *pcDelim, **ppcSearch;

	ppcMetaMap = MetaMapping(acMdRoot);
	i = 0;
	if ((char *)0 != pcVxGroup) {
		register char *pcTail;
		static char acNotFound[] = "%s: cannot find a path to required program \"%s\"\n";
#if USE_OP_FOR_VX
		apcIoClue[i] = pcOpPath;
		if ((char *)0 == (pcTail = strrchr(apcIoClue[i++], '/')) || 0 == strcmp(pcTail+1, acBroken)) {
			fprintf(stderr, acNotFound, progname, "op");
			apcIoClue[i-1] = pcVxPath;
			if ((char *)0 == (pcTail = strrchr(apcIoClue[i-1], '/')) || 0 == strcmp(pcTail+1, acBroken)) {
				exit(2);
			}
			fprintf(stderr, "%s: fallback to direct \"%s\"\n", progname, apcIoClue[i-1]);
		} else {
			apcIoClue[i++] = "vxstat";
		}
#else
		apcIoClue[i] = pcVxPath;
		if ((char *)0 == (pcTail = strrchr(apcIoClue[i++], '/')) || 0 == strcmp(pcTail+1, acBroken)) {
			fprintf(stderr, acNotFound, progname, "vxstat");
			exit(1);
		}
#endif
		if (fPerPar) {
			apcIoClue[i++] = "-s";
		}
		apcIoClue[i++] = "-g";
		apcIoClue[i++] = pcVxGroup;
	} else if ('x' == fType) {
		apcIoClue[i++] = pcIostat;
		apcIoClue[i++] = fPerPar ? "-xp" : "-x" ;
	} else {
		/* some other type of iostat output
		 */
		return;
	}
	apcIoClue[i] = (char *)0;
	if (-1 == PipeFit(apcIoClue[0], apcIoClue, environ, 0)) {
		exit(3);
	}
	/* get the old names from iostat
	 * eat headers, first word on each line is the old name:
	 * in the vx case it is the second word
	 */
	util_ppm_init(& PMOld, sizeof(char *), 256);
	ppcOld = (char **)util_ppm_size(& PMOld, 0);
	while (EOF != (c = getchar()) && '\n' != c) {
		/* ignore extended header */
	}
	while (EOF != (c = getchar()) && '\n' != c) {
		/* ignore col header */
	}
	i = 0;
	while ((char *)0 != (pcLine = fgetln(stdin, & iLineLen))) {
		if (0 == iLineLen || '\n' != pcLine[iLineLen-1])
			break;
		pcDelim = pcLine+iLineLen;
		pcDelim[-1] = '\000';
		while (isspace(*pcLine)) {
			++pcLine;
		}
		pcDelim = pcLine;
		while (!isspace(*pcDelim)) {
			++pcDelim;
		}
		if ((char *)0 != pcVxGroup) {
			while (isspace(*pcDelim)) {
				++pcDelim;
			}
			pcLine = pcDelim;
			while ('\000' != *pcDelim && !isspace(*pcDelim)) {
				++pcDelim;
			}
		}
		*pcDelim = '\000';
		ppcOld = (char **)util_ppm_size(& PMOld, i+2);
		ppcOld[i++] = strdup(pcLine);
	}
	(void)wait((void *)0);
	if ((char *)0 != pcVxGroup) {
		ppcNew = ppcOld;
		return;
	}
	if ((char **)0 == ppcOld) {
		return;
	}
	ppcOld[i] = (char *)0;
	iWas = i;

	/* Get the new names from iostat
	 * open iostat -n, eat headers, last word on each line is new name
	 */
	apcIoClue[2] = "-n";
	if (-1 == PipeFit(pcIostat, apcIoClue, environ, 0)) {
		exit(4);
	}
	i += 2;
	i |= 7;
	++i;
	util_ppm_init(& PMNew, sizeof(char *), i);
	while (EOF != (c = getchar()) && '\n' != c) {
		/* ignore extended header */
	}
	while (EOF != (c = getchar()) && '\n' != c) {
		/* ignore col header */
	}
	i = 0;
	while ((char *)0 != (pcLine = fgetln(stdin, & iLineLen))) {
		register char *pcSuffix, *pcReplace;
		auto char acForm[MAXPATHLEN+4];

		if (0 == iLineLen || '\n' != pcLine[iLineLen-1])
			break;
		pcLine[iLineLen-1] = '\000';
		pcDelim = pcLine+iLineLen-1;
		while (isspace(*pcDelim) && pcDelim > pcLine) {
			*pcDelim-- = '\000';
		}
		for (; pcDelim > pcLine && !isspace(*pcDelim); --pcDelim) {
			/* backup */
		}
		ppcNew = (char **)util_ppm_size(& PMNew, i+2);

		pcSuffix = IsDiskSetName(pcDelim+1);
		if ((char **)0 == ppcMetaMap || '/' != *pcSuffix) {
			ppcNew[i++] = strdup(pcDelim+1);
			continue;
		}
		/* must be a meta device, remap it for iostat
		 */
		*pcSuffix++ = '\000';
		iPrefix = atoi(pcDelim+1);
		for (ppcSearch = ppcMetaMap; (char *)0 != *ppcSearch; ++ppcSearch) {
			if (iPrefix == atoi(*ppcSearch))
				break;
		}
		if ((char *)0 == *ppcSearch) {
			ppcNew[i++] = strdup(pcDelim+1);
			continue;
		}
		if ((char *)0 == (pcReplace = strchr(*ppcSearch, '=')))
			pcReplace = "malformed input";
		else
			++pcReplace;
		sprintf(acForm, "%s/%s", pcReplace, pcSuffix);
		ppcNew[i++] = strdup(acForm);
	}
	ppcNew[i] = (char *)0;
	(void)wait((void *)0);
	if (iWas != i) {
		fprintf(stderr, "%s: iostat mapping failed (%d old != %d new)\n", progname, iWas, i);
		ppcNew = ppcOld = (char **)0;
	}
}

/* Crazy ksb wants to take either the old [sd1] or new [c0t1d0]		(ksb)
 * name to match a device.  And be able to group devices into a meta
 * Map	tape="rmt/1 rmt/2" for me, someday LLL, today map only 1=1
 *	tape1=rmt/1  tape2=rmt/2  opt=c0t1d0s5  var=c0t0d0s6
 *
 * Will rrd add them correctly, I think it will for:
 *	tape=rmt/1 tape=rmt/2
 * We'll output two lines with the same file and rrd will add them.
 * RRD has logic for duplicate updated, but I think absolutes work.
 */
static void
MapArgs(argc, argv)
int argc;
char **argv;
{
	auto int iError, iSet;
	register int i, iInd;
	register char *pcAlias, *pcDev;
	auto char **ppcDef;
	auto PPM_BUF PMMap;

	ppcDef = fUseOld ? ppcOld : ppcNew;
	if ((char **)0 == ppcOld || (char **)0 == ppcNew || 0 == argc) {
		ppcMap = ppcNew;
		return;
	}

	/* Build an array the same size as Old, rounded up
	 * and load it with (char *)0;
	 */
	for (i = 0; (char *)0 != ppcOld[i]; ++i) {
		/* nada */;
	}
	util_ppm_init(& PMMap, sizeof(char *), (i|7)+1);

	ppcMap = util_ppm_size(& PMMap, 1);
	do {
		ppcMap[i] = (char *)0;
	} while (i-- > 0);

	iError = 0;
	for (i = 0; i < argc; ++i) {
		/* Find a name for the device you listed;
		 * to preserve a name use "=st4" which means "st4=st4".
		 */
		if ((char *)0 == (pcDev = strchr(argv[i], '='))) {
			pcDev = argv[i];
			pcAlias = (char *)0;
		} else if (pcDev == argv[i]) {
			++pcDev;
			pcAlias = pcDev;
		} else {
			*pcDev++ = '\000';
			pcAlias = argv[i];
		}
		if ('\000' == pcDev[0]) {
			fprintf(stderr, "%s: empty device name ignored\n", progname);
			continue;
		}

		/* find the device by either name, and don't ignore it.
		 */
		if (-1 != (iInd = FindName(pcDev, ppcOld))) {
			;
		} else if (-1 != (iInd = FindName(pcDev, ppcNew))) {
			;
		} else {
			fprintf(stderr, "%s: %s: unknown device\n", progname, pcDev);
			iError = 21;
			continue;
		}
		argv[i] = (char *)0 != IsDiskSetName(ppcOld[iInd]) ? ppcNew[iInd] : ppcOld[iInd];

		iSet = 0;
		Ignore(ppcNew[iInd], & iSet);

		if ((char *)0 != pcAlias && (char *)0 != ppcMap[iInd]) {
			fprintf(stderr, "%s: %s: listed more than once (%s and %s)\n", progname, ppcDef[iInd], ppcMap[iInd], pcAlias);
			iError = 22;
		} else if ((char *)0 != pcAlias) {
			ppcMap[iInd] = pcAlias;
		}
		if (fVerbose && (char *)0 != ppcMap[iInd] && 0 != strcmp(pcDev, ppcMap[iInd])) {
			fprintf(stderr, "%s: %s maps to %s\n", progname, pcDev, ppcMap[iInd]);
		}
	}
	/* fill in names for the ones we didn't match
	 */
	for (i = 0; fAllToo && (char *)0 != ppcDef[i]; ++i) {
		if ((char *)0 != ppcMap[i])
			continue;
		ppcMap[i] = ppcDef[i];
	}

	/* Don't free the PMMap, we are using the data globally, but
	 * error out if we can't do what they asked.
	 */
	if (0 != iError) {
		exit(iError);
	}
}

/* process the iostat (x) output into rrd format			(ksb)
 * update host/$host/d-$device.rrd -t nr:nw:kr:kw:w:a:wsv:avt:pw:pb \
 *                            N:samples:for:the:above
 */
static void
ChewSun(fType)
int fType;
{
	register int c, iState, iFirst, iDx;
	auto char acIR[128], acIW[128], acKR[128], acKW[128],
		acWait[128], acActv[128], acWsvc[128], acAsvc[128],
		acW[32], acB[32], acDevice[1024], 
		acTemplBuf[1024];

	iState = '\n';
	iFirst = 3;
	while (EOF != (c = getchar())) {
		if (' ' == c || '\t' == c) {
			continue;
		}
		if ('\n' == c) {
			if (iFirst > 0 && 'e' == iState) {
				--iFirst;
			}
			iState = '\n';
			continue;
		}
		if ('e' == iState) {
			continue;
		}
		/* Remove "extended device statistics"
		 * and "r/i  w/i   kr/i   kw/i wait actv ..."
		 * headers.
		 */
		if ('e' == c || 'r' == c) {
			iState = 'e';
			continue;
		}
		if (! isdigit(c)) {
			fprintf(stderr, "%s: unknown iostat output format \"%c...\"\n", progname, c);
			exit(20);
		}
		iState = '\n';
		ungetc(c, stdin);
		scanf("%s %s %s %s %s %s %s %s %s %s %s", acIR, acIW, acKR, acKW, acWait, acActv, acWsvc, acAsvc, acW, acB, acDevice);
		if (iFirst || Ignore(acDevice, (int *)0) || -1 == (iDx = FindName(acDevice, ppcNew)) || (char *)0 == ppcMap[iDx]) {
			continue;
		}

		snprintf(acTemplBuf, sizeof(acTemplBuf), pcTemplate, acOurName, ppcMap[iDx], acOurName, ppcMap[iDx]);
		rrrdupdate((char *)0, acTemplBuf, "nr:nw:kr:kw:w:a:wsv:avt:pw:pb", "N:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s",
				acIR, acIW, acKR, acKW, acWait, acActv, acWsvc, acAsvc, acW, acB);
	}
}

/* HPUX's version of the iostat (h)					(ksb)
 * Lame alert, on HPUX11 we get fewer stats:
 *	device    bps     sps    msps
 *	c1t2d0      9     3.6     1.0
 *	c2t2d0      7     2.3     1.0
 *	c5t8d0      8     1.3     1.0
 * bps Kilobytes transferred/second
 * sps seeks/sec
 * msps Milliseconds per average seek
 *
 * We are going to try to be really generic here, we set the column
 * headers where is see a line that's all [a-zA-Z \t], we ignore leading
 * white-space, on every line.
 */
static void
ChewHP(fType)
int fType;
{
	static PPM_BUF PMStrSpace;
	register int fWord, c;
	register char *pcLine, *pcList, *pcPack;
	auto char *pcColNames;
	auto size_t iLineLen;
	auto int fSawDigit;
	char acTemplBuf[1024];

	(void)util_ppm_init(& PMStrSpace, sizeof(char), 992);

	/* For each line,
	 *	remove trailing white space s,/[ \t\n]*$,,
	 *	remove leading white space  s,^[ \t]*,,
	 *	count colums and record data
	 *	if no digits, or no colums set, then set headers
	 *	send rrdup for data rows (only)
	 */
	pcColNames = (char *)0;
	while ((char *)0 != (pcLine = fgetln(stdin, & iLineLen))) {
		if (iLineLen == 0 || '\n' != pcLine[iLineLen-1]) {
			break;
		}
		do {
			pcLine[--iLineLen] = '\000';
		} while (iLineLen > 0 && isspace(pcLine[iLineLen-1]));
		/* done with iLineLen, skip leading white-space
		 */
		while (isspace(*pcLine)) {
			++pcLine;
		}
		/* skip blank lines
		 */
		if ('\000' == *pcLine) {
			continue;
		}
		fSawDigit = 0, fWord = 0;
		for (pcPack = pcList = pcLine; '\000' != (c = *pcLine); ++pcLine) {
			if (!isspace(c)) {
				*pcPack++ = c;
				if (isdigit(c)) {
					fSawDigit = 1;
				}
				fWord = 1;
				continue;
			}
			if (fWord) {
				*pcPack++ = ':';
				fWord = 0;
			}
		}
		if ('\000' != *pcPack) {
			*pcPack = '\000';
		}

		/* the column header has to have at least "device:stat1"
		 * in it, we remove "device", the data lines have to
		 * have the device name and at least 1 statistic.
		 */
		if ((char *)0 == (pcPack = strchr(pcList, ':'))) {
			continue;
		}
		*pcPack++ = '\000';
		if ((char *)0 == pcColNames || !fSawDigit) {
			static char acDevice[] = "device";
			if (0 != strcmp(acDevice, pcList)) {
				fprintf(stderr, "%s: first column of iostat output must be the name \"%s\"\n", progname, acDevice);
				exit(9);
			}
			pcColNames = (char *)util_ppm_size(& PMStrSpace, (strlen(pcPack)|7)+1);
			(void)strcpy(pcColNames, pcPack);
			continue;
		}
		snprintf(acTemplBuf, sizeof(acTemplBuf), pcTemplate, acOurName, pcList, acOurName, pcList);
		rrrdupdate((char *)0, acTemplBuf, pcColNames, "N:%s", pcPack);
	}
	if ((char *)0 != pcLine) {
		fprintf(stderr, "%s: incomplete line, %s\n", progname, pcLine);
	}
}

/* AIX version of the above						(ksb)
 * This one is amazing, in that it give controlers as well, which
 * we just treat as a disk
 */
static void
ChewAIX(fType)
int fType;
{
	static PPM_BUF PMName, PMCols;
	register char *pcLine, *pcPack, *pcList;
	register int c, iMode, fHeader;
	auto char *pcHeaders, *pcSubject;
	auto size_t iLineLen;
	auto int fSawDigit, fWord;
	char acTemplBuf[1024];

	(void)util_ppm_init(& PMName, sizeof(char), 992);
	(void)util_ppm_init(& PMCols, sizeof(char), 992);

	/* For each line,
	 *	remove trailing white space s,/[ \t\n]*$,,
	 *	remove leading white space  s,^[ \t]*,,
	 *	count colums and record data
	 *	if no digits, or no colums set, then set headers
	 *	send rrdup for data rows (only)
	 */
	pcHeaders = (char *)0;
	iMode = '-';
	fHeader = 0;
	while ((char *)0 != (pcLine = fgetln(stdin, & iLineLen))) {
		if (iLineLen == 0 || '\n' != pcLine[iLineLen-1]) {
			break;
		}
		do {
			pcLine[--iLineLen] = '\000';
		} while (iLineLen > 0 && isspace(pcLine[iLineLen-1]));
		while (isspace(*pcLine)) {
			++pcLine;
		}
		/* skip blank lines, and the " data not avil... "
		 */
		if ('\000' == *pcLine || '"' == *pcLine) {
			continue;
		}
		/* look for section marks
		 * System, Adapter, Disks
		 */
		if ((char *)0 != (pcPack = strchr(pcLine, ':'))) {
			*pcPack++ = '\000';
			while (isspace(*pcPack)) {
				++pcPack;
			}
			fHeader = 1;
			if (0 == strcasecmp("System", pcLine)) {
				iMode = 's';
				pcSubject = (char *)util_ppm_size(& PMName, (strlen(pcPack)|63)+1);
				(void)strcpy(pcSubject, pcPack);
				continue;
			}
			if (0 == strcasecmp("Adapter", pcLine)) {
				iMode = 'a';
			} else if (0 == strcasecmp("Disks", pcLine)) {
				iMode = 'd';
			}
			pcLine = pcPack;
		}
		fSawDigit = 0, fWord = 0;
		for (pcPack = pcList = pcLine; '\000' != (c = *pcLine); ++pcLine) {
			if ('%' == c) {		/* always skip these */
				continue;
			}
			if (!isspace(c)) {
				*pcPack++ = c;
				if (isdigit(c)) {
					fSawDigit = 1;
				}
				fWord = 1;
				continue;
			}
			if (fWord) {
				*pcPack++ = ':';
				fWord = 0;
			}
		}
		if ('\000' != *pcPack) {
			*pcPack = '\000';
		}
		if (fHeader) {
			fHeader = 0;
			pcHeaders = (char *)util_ppm_size(& PMCols, (strlen(pcList)|63)+1);
			(void)strcpy(pcHeaders, pcList);
			continue;
		}
		switch (iMode) {
		default:
		case '-':
			continue;
		case 's':
			pcPack = pcList;
			break;
		case 'a':
		case 'd':
			if ((char *)0 == (pcPack = strchr(pcList, ':'))) {
				continue;
			}
			*pcPack++ = '\000';
			pcSubject = (char *)util_ppm_size(& PMName, (strlen(pcList)|63)+1);
			(void)strcpy(pcSubject, pcList);
			break;
		}

		snprintf(acTemplBuf, sizeof(acTemplBuf), pcTemplate, acOurName, pcSubject, acOurName, pcSubject);
		rrrdupdate((char *)0, acTemplBuf, pcHeaders, "N:%s", pcPack);
	}
}

/* The vxstat we run from op doesn't have a TZ set,			(ksb)
 * and vxstat doesn't output a time zone in the timestamp format:
 *	Wed Nov  6 19:01:45 2002
 * We need to converted to a format with our timezone:
 *	Wed Nov  6 19:01:45 $TZ 2002
 * where TZ could set on the command line, or default to $TZ.
 */
static time_t
ForceTZ(pcSample, iWest)
char *pcSample;
int iWest;
{
	register char *pcYear, *pcMight;
	register int iLen;
	static PPM_BUF *pPMKeep;

	if ((char *)0 == pcSample) {
		return 0;
	}

	pcYear = pcSample + (iLen = strlen(pcSample));
	do {
		if (pcYear == pcSample)
			break;
		--pcYear;
	} while (isdigit(*pcYear));
	while (pcYear > pcSample && isspace(pcYear[-1])) {
		--pcYear;
	}
	pcMight = pcYear;
	while (pcMight > pcSample && !isspace(pcMight[-1])) {
		--pcMight;
	}
	/* sample hasa timezone, return it converted
	 */
	if (!isdigit(*pcMight) || (char *)0 == pcTimeZone || '\000' == pcTimeZone[0]) {
		return atotm(pcSample, iWest);
	}
	/* make a buffer and build the new string to convert
	 */
	iLen += strlen(pcTimeZone);
	iLen |= 3;
	++iLen;
	if ((PPM_BUF *)0 == pPMKeep) {
		pPMKeep = (PPM_BUF *)calloc(1, sizeof(PPM_BUF));
		util_ppm_init(pPMKeep, sizeof(char), (iLen|511)+1);
	}
	pcMight = (char *)util_ppm_size(pPMKeep, iLen);
	iLen = *pcYear;
	*pcYear = '\000';
	sprintf(pcMight, "%s %s %s", pcSample, pcTimeZone, pcYear+1);
	*pcYear = iLen;
	return atotm(pcMight, iWest);
}


/* process the vxstat output into rrd format				(ksb)
 * update host/$host/d-$device.rrd -t nr:nw:br:bw:avr:avw \
 *                            N:samples:for:the:above
 *
 * a LITTLE harder than iostat because we have to parse the date in the
 * stream (the program buffers a _lot_ when the output is a pipe).
                        OPERATIONS           BLOCKS        AVG TIME(ms)
TYP NAME              READ     WRITE      READ     WRITE   READ  WRITE

Mon Sep 09 11:43:21 2002
vol vol01           424926   4270299  20331280  61917110   12.4    0.7
sd  ivdbdg01-01     255652   3763905  12165840  52494387   11.4    0.6
sd  ivdbdg01-03     169274    506402   8165440   9422832   13.9    1.0
sd  ivdbdg02-01     171205    543890  10188488   3654271    4.1    1.2
sd  ivdbdg03-01   19417937   5156311 2276244208  82500932    4.0    1.1
 */
static void
ChewVX(fType)
int fType;
{
	register int c, cLast, iState, iFirst, iCur, iDx;
	auto time_t tSample;
	auto char acSample[64];	/* Mon Sep 09 11:43:21 2002 */
	auto char acTyp[32], acDevice[1024],
		acIR[128], acIW[128], acKR[128], acKW[128],
		acAvr[128], acAvw[128], acTemplBuf[1024];

	iState = '\n';
	iFirst = 2;
	iCur = 0;
	c = '\n';
	while (cLast = c, EOF != (c = getchar())) {
		if ('D' == iState) {
			acSample[iCur] = c;
			if ('\n' != c) {
				++iCur;
				continue;
			}
			acSample[iCur] = '\000';
			tSample = ForceTZ(acSample, AtotmAutoZone());
			if (iFirst > 0) {
				--iFirst;
			}
			iState = '\n';
			iCur = 0;
			continue;
		}
		if (' ' == c || '\t' == c) {
			continue;
		}
		if ('\n' == c) {
			iState = '\n' == cLast ? 'D' : '\n';
			continue;
		}
		if ('O' == iState || 'T' == iState) {
			continue;
		}

		/* Remove "OPERATIONS ..." and "TYP ..."  headers
		 * (state 'O' and 'T').  State 'D' for need a date stamp).
		 */
		if ('O' == c || 'T' == c) {
			iState = c;
			continue;
		}
		if (!('v' == c || 's' == c)) {
			fprintf(stderr, "%s: unknown vxstat output format \"", progname);
			do
				putc(c, stderr);
			while (EOF != (c = getchar()) && '\n' != c);
			fprintf(stderr, "\"\n");
			exit(20);
		}
		iState = '\n';
		ungetc(c, stdin);
		scanf("%s %s %s %s %s %s %s %s", acTyp, acDevice, acIR, acIW, acKR, acKW, acAvr, acAvw);
		if (iFirst || Ignore(acDevice, (int *)0) || -1 == (iDx = FindName(acDevice, ppcNew)) || (char *)0 == ppcMap[iDx]) {
			continue;
		}

		snprintf(acTemplBuf, sizeof(acTemplBuf), pcTemplate, acOurName, ppcMap[iDx], acOurName, ppcMap[iDx]);
		if (-1 == (long int)tSample) {
			rrrdupdate((char *)0, acTemplBuf, "nr:nw:br:bw:avr:avw", "N:%s:%s:%s:%s:%s:%s", 
				acIR, acIW, acKR, acKW, acAvr, acAvw);
		} else {
			rrrdupdate((char *)0, acTemplBuf, "nr:nw:br:bw:avr:avw", "%ld:%s:%s:%s:%s:%s:%s",
				(long int)tSample, acIR, acIW, acKR, acKW, acAvr, acAvw);
		}
	}
}

/* allocate an argv, fork an iostat					(ksb)
 */
static void
List(argc, argv)
int argc;
char **argv;
{
	register int iMax, i, iCopy, fType;
	register char **ppcArgv;
	auto char acInterval[32], acLimit[32];
	auto int wIostatPid;

	fType = (((char *)0 != pcVxGroup) ? 'V' : USE_IOSTAT_TYPE);
	MapArgs(argc, argv);
	sprintf(acInterval, "%d", iDelay);
	iMax = argc+23;	/* 11 at least! */
	if ((char **)0 == (ppcArgv = ((char **)calloc(iMax, sizeof(char *))))) {
		fprintf(stderr, "%s: calloc: %d: %s\n", progname, iMax, strerror(errno));
		exit(1);
	}
	/* stdin becomes the command line for vxstat or iostat; make it so.
	 */
	i = 0;
	if ((char *)0 != pcVxGroup) {
#if USE_OP_FOR_VX
		ppcArgv[i++] = pcOpPath;
		ppcArgv[i++] = "vxstat";
#else
		ppcArgv[i++] = pcVxPath;
#endif
		if (fPerPar) {
			ppcArgv[i++] = "-s";
		}
		ppcArgv[i++] = "-i";
		ppcArgv[i++] = acInterval;
		ppcArgv[i++] = "-g";
		ppcArgv[i++] = pcVxGroup;
		for (iCopy = 0; iCopy < argc; ++iCopy) {
			ppcArgv[i++] = argv[iCopy];
		}
	} else {
		ppcArgv[i++] = pcIostat;
		switch (fType) {
		case 'x':
			ppcArgv[i++] = "-Ixn";
			if (fPerPar) {
				ppcArgv[i++] = "-p";
			}
			if (fParOnly) {
				ppcArgv[i++] = "-P";
			}
			if (fErrors) {
				ppcArgv[i++] = "-e";
			}
			if (0 != argc && ! fAllToo) {
				if (fPerPar && ! fBustedIOStat && 'x' == fType) {
					sprintf(acLimit, "%d", argc);
					ppcArgv[i++] = "-l";
					ppcArgv[i++] = acLimit;
				}
				for (iCopy = 0; iCopy < argc; ++iCopy) {
					ppcArgv[i++] = argv[iCopy];
				}
			}
			ppcArgv[i++] = acInterval;
			break;
		case 'h':
			ppcArgv[i++] = acInterval;
			break;
		case 'a':
			ppcArgv[i++] = "-d";
			if (fPerPar) {
				ppcArgv[i++] = "-a";
			}
			if (fParOnly) {
				ppcArgv[i++] = "-s";
			}
			ppcArgv[i++] = acInterval;
			break;
		default:
			break;
		}
	}
	ppcArgv[i] = (char *)0;
	if (-1 == (wIostatPid = PipeFit(ppcArgv[0], ppcArgv, environ, 0))) {
		fprintf(stderr, "%s: %s: failed", progname, pcIostat);
		exit(9);
	}

	// only set a static variable inside the function, do not update
	if (0 > rrrdupdate(pcPeg, (char *)0, (char *)0, (char *)0)) {
		fprintf(stderr, "%s: failed to open UDP socket to %s\n", progname, pcPeg);
		exit(10);
	}

	if (fVerbose && (char **)0 != ppcOld && (char **)0 != ppcNew && (char **)0 != ppcMap) {
		for (i = 0; (char *)0 != ppcOld[i] && (char *)0 != ppcNew[i]; ++i) {
			if ((char *)0 == ppcMap[i])
				continue;
			fprintf(stderr, "\t%s %s %s\n", ppcOld[i], ppcNew[i], ppcMap[i]);
		}
	}

	switch (fType) {
	case 'V':
		ChewVX(fType);
		break;
	case 'B':
		fprintf(stderr, "%s: iostat is too broken to parse on this host\n", progname);
		break;
	case 'x':
		ChewSun(fType);
		break;
	case 'h':
		ChewHP(fType);
		break;
	case 'a':
		ChewAIX(fType);
		break;
	case '?':
	default:
		fprintf(stderr, "%s: unknown iostat output type `%c'\n", progname, fType);
		break;
	}
	while (wIostatPid != (i = wait((void *)0)) && -1 != i) {
		/* pid */
	}
}
