diff options
Diffstat (limited to 'src/pmclient/pmclient.c')
-rw-r--r-- | src/pmclient/pmclient.c | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/src/pmclient/pmclient.c b/src/pmclient/pmclient.c new file mode 100644 index 0000000..5abbc95 --- /dev/null +++ b/src/pmclient/pmclient.c @@ -0,0 +1,357 @@ +/* + * pmclient - sample, simple PMAPI client + * + * Copyright (c) 2013-2014 Red Hat. + * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmnsmap.h" + +pmLongOptions longopts[] = { + PMAPI_GENERAL_OPTIONS, + PMAPI_OPTIONS_HEADER("Reporting options"), + { "pause", 0, 'P', 0, "pause between updates for archive replay" }, + PMAPI_OPTIONS_END +}; + +pmOptions opts = { + .flags = PM_OPTFLAG_STDOUT_TZ, + .short_options = PMAPI_OPTIONS "P", + .long_options = longopts, +}; + +typedef struct { + struct timeval timestamp; /* last fetched time */ + float cpu_util; /* aggregate CPU utilization, usr+sys */ + int peak_cpu; /* most utilized CPU, if > 1 CPU */ + float peak_cpu_util; /* utilization for most utilized CPU */ + float freemem; /* free memory (Mbytes) */ + unsigned int dkiops; /* aggregate disk I/O's per second */ + float load1; /* 1 minute load average */ + float load15; /* 15 minute load average */ +} info_t; + +static unsigned int ncpu; + +static unsigned int +get_ncpu(void) +{ + /* there is only one metric in the pmclient_init group */ + pmID pmidlist[1]; + pmDesc desclist[1]; + pmResult *rp; + pmAtomValue atom; + int sts; + + if ((sts = pmLookupName(1, pmclient_init, pmidlist)) < 0) { + fprintf(stderr, "%s: pmLookupName: %s\n", pmProgname, pmErrStr(sts)); + fprintf(stderr, "%s: metric \"%s\" not in name space\n", + pmProgname, pmclient_init[0]); + exit(1); + } + if ((sts = pmLookupDesc(pmidlist[0], desclist)) < 0) { + fprintf(stderr, "%s: cannot retrieve description for metric \"%s\" (PMID: %s)\nReason: %s\n", + pmProgname, pmclient_init[0], pmIDStr(pmidlist[0]), pmErrStr(sts)); + exit(1); + } + if ((sts = pmFetch(1, pmidlist, &rp)) < 0) { + fprintf(stderr, "%s: pmFetch: %s\n", pmProgname, pmErrStr(sts)); + exit(1); + } + + /* the thing we want is known to be the first value */ + pmExtractValue(rp->vset[0]->valfmt, rp->vset[0]->vlist, desclist[0].type, + &atom, PM_TYPE_U32); + pmFreeResult(rp); + + return atom.ul; +} + +static void +get_sample(info_t *ip) +{ + static pmResult *crp = NULL; /* current */ + static pmResult *prp = NULL; /* prior */ + static int first = 1; + static int numpmid; + static pmID *pmidlist; + static pmDesc *desclist; + static int inst1; + static int inst15; + static pmUnits mbyte_scale; + + int sts; + int i; + float u; + pmAtomValue tmp; + pmAtomValue atom; + double dt; + + if (first) { + /* first time initialization */ + mbyte_scale.dimSpace = 1; + mbyte_scale.scaleSpace = PM_SPACE_MBYTE; + + numpmid = sizeof(pmclient_sample) / sizeof(char *); + if ((pmidlist = (pmID *)malloc(numpmid * sizeof(pmidlist[0]))) == NULL) { + fprintf(stderr, "%s: get_sample: malloc: %s\n", pmProgname, osstrerror()); + exit(1); + } + if ((desclist = (pmDesc *)malloc(numpmid * sizeof(desclist[0]))) == NULL) { + fprintf(stderr, "%s: get_sample: malloc: %s\n", pmProgname, osstrerror()); + exit(1); + } + if ((sts = pmLookupName(numpmid, pmclient_sample, pmidlist)) < 0) { + printf("%s: pmLookupName: %s\n", pmProgname, pmErrStr(sts)); + for (i = 0; i < numpmid; i++) { + if (pmidlist[i] == PM_ID_NULL) + fprintf(stderr, "%s: metric \"%s\" not in name space\n", pmProgname, pmclient_sample[i]); + } + exit(1); + } + for (i = 0; i < numpmid; i++) { + if ((sts = pmLookupDesc(pmidlist[i], &desclist[i])) < 0) { + fprintf(stderr, "%s: cannot retrieve description for metric \"%s\" (PMID: %s)\nReason: %s\n", + pmProgname, pmclient_sample[i], pmIDStr(pmidlist[i]), pmErrStr(sts)); + exit(1); + } + } + } + + /* fetch the current metrics */ + if ((sts = pmFetch(numpmid, pmidlist, &crp)) < 0) { + fprintf(stderr, "%s: pmFetch: %s\n", pmProgname, pmErrStr(sts)); + exit(1); + } + + /* + * minor gotcha ... for archives, it helps to do the first fetch of + * real data before interrogating the instance domains ... this + * forces us to be "after" the first batch of instance domain info + * in the meta data files + */ + if (first) { + /* + * from now on, just want the 1 minute and 15 minute load averages, + * so limit the instance profile for this metric + */ + pmDelProfile(desclist[LOADAV].indom, 0, NULL); /* all off */ + if ((inst1 = pmLookupInDom(desclist[LOADAV].indom, "1 minute")) < 0) { + fprintf(stderr, "%s: cannot translate instance for 1 minute load average\n", pmProgname); + exit(1); + } + pmAddProfile(desclist[LOADAV].indom, 1, &inst1); + if ((inst15 = pmLookupInDom(desclist[LOADAV].indom, "15 minute")) < 0) { + fprintf(stderr, "%s: cannot translate instance for 15 minute load average\n", pmProgname); + exit(1); + } + pmAddProfile(desclist[LOADAV].indom, 1, &inst15); + + first = 0; + } + + /* if the second or later sample, pick the results apart */ + if (prp != NULL) { + + dt = __pmtimevalSub(&crp->timestamp, &prp->timestamp); + ip->cpu_util = 0; + ip->peak_cpu_util = -1; /* force re-assignment at first CPU */ + for (i = 0; i < ncpu; i++) { + pmExtractValue(crp->vset[CPU_USR]->valfmt, + &crp->vset[CPU_USR]->vlist[i], + desclist[CPU_USR].type, &atom, PM_TYPE_FLOAT); + u = atom.f; + pmExtractValue(prp->vset[CPU_USR]->valfmt, + &prp->vset[CPU_USR]->vlist[i], + desclist[CPU_USR].type, &atom, PM_TYPE_FLOAT); + u -= atom.f; + pmExtractValue(crp->vset[CPU_SYS]->valfmt, + &crp->vset[CPU_SYS]->vlist[i], + desclist[CPU_SYS].type, &atom, PM_TYPE_FLOAT); + u += atom.f; + pmExtractValue(prp->vset[CPU_SYS]->valfmt, + &prp->vset[CPU_SYS]->vlist[i], + desclist[CPU_SYS].type, &atom, PM_TYPE_FLOAT); + u -= atom.f; + /* + * really should use pmConvertValue, but I _know_ the times + * are in msec! + */ + u = u / (1000 * dt); + + if (u > 1.0) + /* small errors are possible, so clip the utilization at 1.0 */ + u = 1.0; + ip->cpu_util += u; + if (u > ip->peak_cpu_util) { + ip->peak_cpu_util = u; + ip->peak_cpu = i; + } + } + ip->cpu_util /= ncpu; + + /* freemem - expect just one value */ + pmExtractValue(crp->vset[FREEMEM]->valfmt, crp->vset[FREEMEM]->vlist, + desclist[FREEMEM].type, &tmp, PM_TYPE_FLOAT); + /* convert from today's units at the collection site to Mbytes */ + pmConvScale(PM_TYPE_FLOAT, &tmp, &desclist[FREEMEM].units, + &atom, &mbyte_scale); + ip->freemem = atom.f; + + /* disk IOPS - expect just one value, but need delta */ + pmExtractValue(crp->vset[DKIOPS]->valfmt, crp->vset[DKIOPS]->vlist, + desclist[DKIOPS].type, &atom, PM_TYPE_U32); + ip->dkiops = atom.ul; + pmExtractValue(prp->vset[DKIOPS]->valfmt, prp->vset[DKIOPS]->vlist, + desclist[DKIOPS].type, &atom, PM_TYPE_U32); + ip->dkiops -= atom.ul; + ip->dkiops = ((float)(ip->dkiops) + 0.5) / dt; + + /* load average ... process all values, matching up the instances */ + for (i = 0; i < crp->vset[LOADAV]->numval; i++) { + pmExtractValue(crp->vset[LOADAV]->valfmt, + &crp->vset[LOADAV]->vlist[i], + desclist[LOADAV].type, &atom, PM_TYPE_FLOAT); + if (crp->vset[LOADAV]->vlist[i].inst == inst1) + ip->load1 = atom.f; + else if (crp->vset[LOADAV]->vlist[i].inst == inst15) + ip->load15 = atom.f; + } + + /* free very old result */ + pmFreeResult(prp); + } + ip->timestamp = crp->timestamp; + + /* swizzle result pointers */ + prp = crp; +} + +int +main(int argc, char **argv) +{ + int c; + int sts; + int samples; + int pauseFlag = 0; + int lines = 0; + char *source; + const char *host; + info_t info; /* values to report each sample */ + char timebuf[26]; /* for pmCtime result */ + + setlinebuf(stdout); + + while ((c = pmGetOptions(argc, argv, &opts)) != EOF) { + switch (c) { + case 'p': + pauseFlag++; + break; + default: + opts.errors++; + break; + } + } + + if (pauseFlag && opts.context != PM_CONTEXT_ARCHIVE) { + pmprintf("%s: pause can only be used with archives\n", pmProgname); + opts.errors++; + } + + if (opts.errors || opts.optind < argc - 1) { + pmUsageMessage(&opts); + exit(1); + } + + if (opts.context == PM_CONTEXT_ARCHIVE) { + source = opts.archives[0]; + } else if (opts.context == PM_CONTEXT_HOST) { + source = opts.hosts[0]; + } else { + opts.context = PM_CONTEXT_HOST; + source = "local:"; + } + + if ((sts = c = pmNewContext(opts.context, source)) < 0) { + if (opts.context == PM_CONTEXT_HOST) + fprintf(stderr, "%s: Cannot connect to PMCD on host \"%s\": %s\n", + pmProgname, source, pmErrStr(sts)); + else + fprintf(stderr, "%s: Cannot open archive \"%s\": %s\n", + pmProgname, source, pmErrStr(sts)); + exit(1); + } + + /* complete TZ and time window option (origin) setup */ + if (pmGetContextOptions(c, &opts)) { + pmflush(); + exit(1); + } + + host = pmGetContextHostName(c); + ncpu = get_ncpu(); + + if ((opts.context == PM_CONTEXT_ARCHIVE) && + (opts.start.tv_sec != 0 || opts.start.tv_usec != 0)) { + if ((sts = pmSetMode(PM_MODE_FORW, &opts.start, 0)) < 0) { + fprintf(stderr, "%s: pmSetMode failed: %s\n", + pmProgname, pmErrStr(sts)); + exit(1); + } + } + + get_sample(&info); + + /* set a default sampling interval if none has been requested */ + if (opts.interval.tv_sec == 0 && opts.interval.tv_usec == 0) + opts.interval.tv_sec = 5; + + /* set sampling loop termination via the command line options */ + samples = opts.samples ? opts.samples : -1; + + while (samples == -1 || samples-- > 0) { + if (lines % 15 == 0) { + if (opts.context == PM_CONTEXT_ARCHIVE) + printf("Archive: %s, ", opts.archives[0]); + printf("Host: %s, %d cpu(s), %s", + host, ncpu, + pmCtime(&info.timestamp.tv_sec, timebuf)); +/* - report format + CPU Busy Busy Free Mem Disk Load Average + Util CPU Util (Mbytes) IOPS 1 Min 15 Min +X.XXX XXX X.XXX XXXXX.XXX XXXXXX XXXX.XX XXXX.XX +*/ + printf(" CPU"); + if (ncpu > 1) + printf(" Busy Busy"); + printf(" Free Mem Disk Load Average\n"); + printf(" Util"); + if (ncpu > 1) + printf(" CPU Util"); + printf(" (Mbytes) IOPS 1 Min 15 Min\n"); + } + if (opts.context != PM_CONTEXT_ARCHIVE || pauseFlag) + __pmtimevalSleep(opts.interval); + get_sample(&info); + printf("%5.2f", info.cpu_util); + if (ncpu > 1) + printf(" %3d %5.2f", info.peak_cpu, info.peak_cpu_util); + printf(" %9.3f", info.freemem); + printf(" %6d", info.dkiops); + printf(" %7.2f %7.2f\n", info.load1, info.load15); + lines++; + } + exit(0); +} |