#!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 '' from '' from '' from '' from '' from '' from '' 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?p1qv;' } char* named "pcVxPath" { before 'pcVxPath = %K?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 */ } }