diff options
Diffstat (limited to 'src/pmclient')
-rw-r--r-- | src/pmclient/GNUmakefile | 47 | ||||
-rw-r--r-- | src/pmclient/GNUmakefile.install | 37 | ||||
-rw-r--r-- | src/pmclient/README | 36 | ||||
-rw-r--r-- | src/pmclient/pmclient.c | 357 | ||||
-rw-r--r-- | src/pmclient/pmlogger.config | 16 | ||||
-rw-r--r-- | src/pmclient/pmnsmap.spec | 11 |
6 files changed, 504 insertions, 0 deletions
diff --git a/src/pmclient/GNUmakefile b/src/pmclient/GNUmakefile new file mode 100644 index 0000000..095be2a --- /dev/null +++ b/src/pmclient/GNUmakefile @@ -0,0 +1,47 @@ +# +# Copyright (c) 2013-2013 Red Hat. +# Copyright (c) 2000,2004 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. +# + +TOPDIR = ../.. +include $(TOPDIR)/src/include/builddefs + +CFILES = pmclient.c +CMDTARGET = pmclient$(EXECSUFFIX) +LLDLIBS = $(PCPLIB) +LDIRT = pmnsmap.h mylog.* runme.sh +CONFIGS = pmnsmap.spec pmlogger.config +LSRCFILES = README GNUmakefile.install $(CONFIGS) + +DEMODIR = $(PCP_DEMOS_DIR)/pmclient + +default: $(CMDTARGET) + +include $(BUILDRULES) + +install: $(CMDTARGET) + $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET) + $(INSTALL) -m 755 -d $(DEMODIR) + $(INSTALL) -m 644 GNUmakefile.install $(DEMODIR)/Makefile + $(INSTALL) -m 644 $(CFILES) $(CONFIGS) README $(DEMODIR) + +pmclient.o: pmnsmap.h + +pmnsmap.h: pmnsmap.spec + sed -e "s;^\. .PCP_DIR.etc.pcp.env;. $(TOPDIR)/src/include/pcp.env;" \ + ../pmgenmap/pmgenmap.sh >runme.sh; \ + $(RUN_IN_BUILD_ENV) sh ./runme.sh pmnsmap.spec >pmnsmap.h + +default_pcp: default + +install_pcp: install diff --git a/src/pmclient/GNUmakefile.install b/src/pmclient/GNUmakefile.install new file mode 100644 index 0000000..db72b27 --- /dev/null +++ b/src/pmclient/GNUmakefile.install @@ -0,0 +1,37 @@ +# +# Copyright (c) 2000,2004 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. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + +CFILES = pmclient.c +CFLAGS = -I/usr/include/pcp -DPCP_DEBUG=1 +TARGETS = pmclient +LDOPTS = +LDLIBS = -lpcp + +default: $(TARGETS) + +install: + +clobber: + rm -f $(TARGETS) *.o core a.out pmnsmap.h mylog.* pmlogger.log + +pmclient: pmclient.c pmnsmap.h + rm -f $@ + $(CC) $(CFLAGS) pmclient.c -o $@ $(LDOPTS) $(LDLIBS) + +pmnsmap.h: pmnsmap.spec + pmgenmap pmnsmap.spec >pmnsmap.h diff --git a/src/pmclient/README b/src/pmclient/README new file mode 100644 index 0000000..a2d4aab --- /dev/null +++ b/src/pmclient/README @@ -0,0 +1,36 @@ +pmclient - a sample client using the PMAPI +========================================== + +pmclient is a sample client that uses the Performance Metrics +Application Programming Interface (PMAPI) to report some performance +data, collected from either a local host, a remote host, or a +Performance Co-Pilot (PCP) performance metrics archive log. + +The binary is shipped as part of pcp and is typically installed in +/usr/bin/pmclient. A pmclient(1) "man" page is shipped in the pcp +package also. + +The source is shipped as part of the pcp development packages and +is installed in $PCP_DEMOS_DIR/pmclient. If you have a C compiler +installed, the source and Makefile in this directory may be used to +create a functionally equivalent binary, by entering the command + + % make + +The source in pmclient.c demonstrates many of the PMAPI services, and +may be used as a template and style guide when creating your own PMAPI +clients. Note in particular, the use of ./pmnsmap.spec and the shipped +tool pmgenmap(1) to assist in the creation of arguments to the PMAPI +routines, and the manipulation of PMAPI data structures. + +To experiment with the archives, + + % rm -f mylog.* + % config=$PCP_DEMOS_DIR/pmclient/pmlogger.config + % cat $config + % pmlogger -c $config -s 6 mylog + +this will collect 30 seconds of performance data into the archive +stored as the files mylog.*. To play this back, + + % pmclient -a mylog 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); +} diff --git a/src/pmclient/pmlogger.config b/src/pmclient/pmlogger.config new file mode 100644 index 0000000..5673538 --- /dev/null +++ b/src/pmclient/pmlogger.config @@ -0,0 +1,16 @@ +# +# pmlogger(1) configuration file suitable for creating an archive to be +# used with pmclient(1) +# + +log mandatory on once { + hinv.ncpu +} + +log mandatory on 5 secs { + kernel.all.load [ "1 minute", "15 minute" ] + kernel.percpu.cpu.user + kernel.percpu.cpu.sys + mem.freemem + disk.all.total +} diff --git a/src/pmclient/pmnsmap.spec b/src/pmclient/pmnsmap.spec new file mode 100644 index 0000000..fc8fb97 --- /dev/null +++ b/src/pmclient/pmnsmap.spec @@ -0,0 +1,11 @@ +pmclient_init { + hinv.ncpu NUMCPU +} + +pmclient_sample { + kernel.all.load LOADAV + kernel.percpu.cpu.user CPU_USR + kernel.percpu.cpu.sys CPU_SYS + mem.freemem FREEMEM + disk.all.total DKIOPS +} |