summaryrefslogtreecommitdiff
path: root/src/pmclient
diff options
context:
space:
mode:
Diffstat (limited to 'src/pmclient')
-rw-r--r--src/pmclient/GNUmakefile47
-rw-r--r--src/pmclient/GNUmakefile.install37
-rw-r--r--src/pmclient/README36
-rw-r--r--src/pmclient/pmclient.c357
-rw-r--r--src/pmclient/pmlogger.config16
-rw-r--r--src/pmclient/pmnsmap.spec11
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
+}