summaryrefslogtreecommitdiff
path: root/src/pmdas/mmv
diff options
context:
space:
mode:
Diffstat (limited to 'src/pmdas/mmv')
-rw-r--r--src/pmdas/mmv/GNUmakefile62
-rw-r--r--src/pmdas/mmv/Makefile.demos33
-rw-r--r--src/pmdas/mmv/README.demos26
-rw-r--r--src/pmdas/mmv/acme.c125
-rw-r--r--src/pmdas/mmv/mmvdump.c346
-rw-r--r--src/pmdas/mmv/src/GNUmakefile59
-rwxr-xr-xsrc/pmdas/mmv/src/Install36
-rwxr-xr-xsrc/pmdas/mmv/src/Remove23
-rw-r--r--src/pmdas/mmv/src/mmv.c915
-rw-r--r--src/pmdas/mmv/src/root_mmv13
10 files changed, 1638 insertions, 0 deletions
diff --git a/src/pmdas/mmv/GNUmakefile b/src/pmdas/mmv/GNUmakefile
new file mode 100644
index 0000000..e674bd1
--- /dev/null
+++ b/src/pmdas/mmv/GNUmakefile
@@ -0,0 +1,62 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2009-2010 Aconex. 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
+
+IAM = mmv
+TARGET = mmvdump$(EXECSUFFIX)
+CFILES = mmvdump.c
+
+SUBDIRS = src
+
+LCFILES = acme.c
+LTARGET = acme$(EXECSUFFIX)
+LSRCFILES = $(LCFILES) README.demos Makefile.demos
+TARGETS = $(TARGET) $(LTARGET)
+
+LLDFLAGS = -L$(TOPDIR)/src/libpcp_mmv/src -L$(TOPDIR)/src/libpcp/src
+LLDLIBS = -lpcp_mmv $(PCPLIB)
+LDIRT = mmvdump acme
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+DEMODIR = $(PCP_DEMOS_DIR)/$(IAM)
+DEMOFILES = README.demos Makefile.demos
+CONF_LINE = "mmv 70 dso mmv_init $(PCP_PMDAS_DIR)/mmv/pmda_mmv.$(DSOSUFFIX)"
+
+default_pcp default :: $(TARGETS)
+
+default_pcp default :: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+ @if [ `grep -c $(CONF_LINE) ../pmcd.conf` -eq 0 ]; then \
+ echo $(CONF_LINE) >> ../pmcd.conf ; \
+ fi
+
+include $(BUILDRULES)
+
+install_pcp install :: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+install_pcp install :: $(SUBDIRS)
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 $(TARGET) $(PMDADIR)/$(TARGET)
+ $(INSTALL) -m 755 -d $(DEMODIR)
+ $(INSTALL) -m 644 Makefile.demos $(DEMODIR)/Makefile
+ $(INSTALL) -m 644 README.demos $(DEMODIR)/README
+ $(INSTALL) -m 644 $(CFILES) $(LCFILES) $(DEMODIR)
+
+# check-build only, binary not installed (but source is)
+$(LTARGET): acme.c
+ $(CCF) -o $@ $^ $(LDFLAGS) $(LDLIBS)
diff --git a/src/pmdas/mmv/Makefile.demos b/src/pmdas/mmv/Makefile.demos
new file mode 100644
index 0000000..5c6341c
--- /dev/null
+++ b/src/pmdas/mmv/Makefile.demos
@@ -0,0 +1,33 @@
+#
+# Copyright (c) 2013 Red Hat.
+#
+# 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.
+#
+
+SHELL = /bin/sh
+CC = cc
+CFLAGS = -g
+
+TARGETS = acme mmvdump
+
+default: $(TARGETS)
+
+mmvdump: mmvdump.c
+ $(CC) $(CFLAGS) -o $@ $^ -lpcp
+
+acme: acme.c
+ $(CC) $(CFLAGS) -o $@ $^ -lpcp_mmv -lpcp
+
+clean:
+ rm -f *.o
+
+clobber: clean
+ rm -f $(TARGETS)
diff --git a/src/pmdas/mmv/README.demos b/src/pmdas/mmv/README.demos
new file mode 100644
index 0000000..5ed4ef9
--- /dev/null
+++ b/src/pmdas/mmv/README.demos
@@ -0,0 +1,26 @@
+sample pcp_mmv applications
+===========================
+
+acme.c
+ is a sample application that is instrumented with the pcp_mmv
+interface to generate metrics available from the MMV PMDA
+(Performance Metrics Domain Agent). It runs without exiting,
+and continually updates the mmv.acme.* metric values. Detailed
+discussion about the workings of this application and each of
+the instrumentation API calls it uses, is available as part of
+the "Performance Co-Pilot Programmer's Guide".
+
+mmvdump.c
+ an example program that dumps the contents of a (memory mapped)
+mmv(5) format file, as created by the pcp_mmv library and read by
+the MMV PMDA. The binary is also shipped as part of pcp and can
+be found installed below ${PCP_PMDAS_DIR}/mmv.
+
+
+All source is shipped as part of pcp as well and is installed in
+${PCP_DEMOS_DIR}/mmv. If you have a C toolchain installed, the
+sources and Makefile in this directory may be used to create the
+functionally equivalent binaries, by entering the command
+
+ % make acme mmvdump
+
diff --git a/src/pmdas/mmv/acme.c b/src/pmdas/mmv/acme.c
new file mode 100644
index 0000000..212ce39
--- /dev/null
+++ b/src/pmdas/mmv/acme.c
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ *
+ * 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 <pcp/pmapi.h>
+#include <pcp/mmv_stats.h>
+
+static mmv_instances_t products[] = {
+ { .internal = 0, .external = "Anvils" },
+ { .internal = 1, .external = "Rockets" },
+ { .internal = 2, .external = "Giant_Rubber_Bands" },
+};
+#define ACME_PRODUCTS_INDOM 61
+#define ACME_PRODUCTS_COUNT (sizeof(products)/sizeof(products[0]))
+
+static mmv_indom_t indoms[] = {
+ { .serial = ACME_PRODUCTS_INDOM,
+ .count = ACME_PRODUCTS_COUNT,
+ .instances = products,
+ .shorttext = "Acme products",
+ .helptext = "Most popular products produced by the Acme Corporation",
+ },
+};
+
+static mmv_metric_t metrics[] = {
+ { .name = "products.count",
+ .item = 7,
+ .type = MMV_TYPE_U64,
+ .semantics = MMV_SEM_COUNTER,
+ .dimension = MMV_UNITS(0,0,1,0,0,PM_COUNT_ONE),
+ .indom = ACME_PRODUCTS_INDOM,
+ .shorttext = "Acme factory product throughput",
+ .helptext =
+"Monotonic increasing counter of products produced in the Acme Corporation\n"
+"factory since starting the Acme production application. Quality guaranteed.",
+ },
+ { .name = "products.time",
+ .item = 8,
+ .type = MMV_TYPE_U64,
+ .semantics = MMV_SEM_COUNTER,
+ .dimension = MMV_UNITS(0,1,0,0,PM_TIME_USEC,0),
+ .indom = ACME_PRODUCTS_INDOM,
+ .shorttext = "Machine time spent producing Acme products",
+ .helptext =
+"Machine time spent producing Acme Corporation products. Does not include\n"
+"time in queues waiting for production machinery.",
+ },
+ { .name = "products.queuetime",
+ .item = 10,
+ .type = MMV_TYPE_U64,
+ .semantics = MMV_SEM_COUNTER,
+ .dimension = MMV_UNITS(0,1,0,0,PM_TIME_USEC,0),
+ .indom = ACME_PRODUCTS_INDOM,
+ .shorttext = "Queued time while producing Acme products",
+ .helptext =
+"Time spent in the queue waiting to build Acme Corporation products,\n"
+"while some other Acme product was being built instead of this one.",
+ },
+};
+
+#define INDOM_COUNT (sizeof(indoms)/sizeof(indoms[0]))
+#define METRIC_COUNT (sizeof(metrics)/sizeof(metrics[0]))
+#define ACME_CLUSTER 321 /* PMID cluster identifier */
+
+int
+main(int argc, char * argv[])
+{
+ void *base;
+ pmAtomValue *count[ACME_PRODUCTS_COUNT];
+ pmAtomValue *machine[ACME_PRODUCTS_COUNT];
+ pmAtomValue *inqueue[ACME_PRODUCTS_COUNT];
+ unsigned int working;
+ unsigned int product;
+ unsigned int i;
+
+ base = mmv_stats_init("acme", ACME_CLUSTER, 0,
+ metrics, METRIC_COUNT, indoms, INDOM_COUNT);
+ if (!base) {
+ perror("mmv_stats_init");
+ return 1;
+ }
+
+ for (i = 0; i < ACME_PRODUCTS_COUNT; i++) {
+ count[i] = mmv_lookup_value_desc(base,
+ "products.count", products[i].external);
+ machine[i] = mmv_lookup_value_desc(base,
+ "products.time", products[i].external);
+ inqueue[i] = mmv_lookup_value_desc(base,
+ "products.queuetime", products[i].external);
+ }
+
+ while (1) {
+ /* choose a random number between 0-N -> product */
+ product = rand() % ACME_PRODUCTS_COUNT;
+
+ /* assign a time spent "working" on this product */
+ working = rand() % 50000;
+
+ /* pretend to "work" so process doesn't burn CPU */
+ usleep(working);
+
+ /* update the memory mapped values for this one: */
+ /* one more product produced and work time spent */
+ mmv_inc_value(base, machine[product], working); /* API */
+ count[product]->ull += 1; /* or direct mmap update */
+
+ /* all other products are "queued" for this time */
+ for (i = 0; i < ACME_PRODUCTS_COUNT; i++)
+ if (i != product)
+ mmv_inc_value(base, inqueue[i], working);
+ }
+
+ mmv_stats_stop("acme", base);
+ return 0;
+}
diff --git a/src/pmdas/mmv/mmvdump.c b/src/pmdas/mmv/mmvdump.c
new file mode 100644
index 0000000..0943618
--- /dev/null
+++ b/src/pmdas/mmv/mmvdump.c
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2013 Red Hat.
+ * Copyright (C) 2009 Aconex. All Rights Reserved.
+ * Copyright (C) 2001 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 <pcp/pmapi.h>
+#include <pcp/mmv_stats.h>
+#include <pcp/mmv_dev.h>
+#include <pcp/impl.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+
+void
+dump_indoms(void *addr, int idx, long base, __uint64_t offset, __int32_t count)
+{
+ int i;
+ mmv_disk_string_t * string;
+ mmv_disk_indom_t * indom = (mmv_disk_indom_t *)
+ ((char *)addr + offset);
+
+ printf("\nTOC[%d]: offset %ld, indoms offset %" PRIu64 " (%d entries)\n",
+ idx, base, offset, count);
+
+ for (i = 0; i < count; i++) {
+ __uint64_t off = offset + i * sizeof(mmv_disk_indom_t);
+ printf(" [%u/%"PRIi64"] %d instances, starting at offset %"PRIi64"\n",
+ indom[i].serial, off, indom[i].count, indom[i].offset);
+ if (indom[i].shorttext) {
+ string = (mmv_disk_string_t *)
+ ((char *)addr + indom[i].shorttext);
+ printf(" shorttext=%s\n", string->payload);
+ }
+ else
+ printf(" (no shorttext)\n");
+ if (indom[i].helptext) {
+ string = (mmv_disk_string_t *)
+ ((char *)addr + indom[i].helptext);
+ printf(" helptext=%s\n", string->payload);
+ }
+ else
+ printf(" (no helptext)\n");
+ }
+}
+
+void
+dump_insts(void *addr, int idx, long base, __uint64_t offset, __int32_t count)
+{
+ int i;
+ mmv_disk_instance_t * inst = (mmv_disk_instance_t *)
+ ((char *)addr + offset);
+
+ printf("\nTOC[%d]: offset %ld, instances offset %"PRIi64" (%d entries)\n",
+ idx, base, offset, count);
+
+ for (i = 0; i < count; i++) {
+ mmv_disk_indom_t * indom = (mmv_disk_indom_t *)
+ ((char *)addr + inst[i].indom);
+ printf(" [%u/%"PRIi64"] instance = [%d or \"%s\"]\n",
+ indom->serial,
+ (offset + i * sizeof(mmv_disk_instance_t)),
+ inst[i].internal, inst[i].external);
+ }
+}
+
+static char *
+metrictype(int mtype)
+{
+ char *type;
+
+ switch (mtype) {
+ case MMV_TYPE_I32:
+ type = "32-bit int";
+ break;
+ case MMV_TYPE_U32:
+ type = "32-bit unsigned int";
+ break;
+ case MMV_TYPE_I64:
+ type = "64-bit int";
+ break;
+ case MMV_TYPE_U64:
+ type = "64-bit unsigned int";
+ break;
+ case MMV_TYPE_FLOAT:
+ type = "float";
+ break;
+ case MMV_TYPE_DOUBLE:
+ type = "double";
+ break;
+ case MMV_TYPE_STRING:
+ type = "string";
+ break;
+ case MMV_TYPE_ELAPSED:
+ type = "elapsed";
+ break;
+ default:
+ type = "?";
+ break;
+ }
+ return type;
+}
+
+static char *
+metricsem(int msem)
+{
+ char *sem;
+
+ switch (msem) {
+ case PM_SEM_COUNTER:
+ sem = "counter";
+ break;
+ case PM_SEM_INSTANT:
+ sem = "instant";
+ break;
+ case PM_SEM_DISCRETE:
+ sem = "discrete";
+ break;
+ default:
+ sem = "?";
+ break;
+ }
+ return sem;
+}
+
+void
+dump_metrics(void *addr, int idx, long base, __uint64_t offset, __int32_t count)
+{
+ int i;
+ mmv_disk_string_t * string;
+ mmv_disk_metric_t * m = (mmv_disk_metric_t *)
+ ((char *)addr + offset);
+
+ printf("\nTOC[%d]: toc offset %ld, metrics offset %"PRIi64" (%d entries)\n",
+ idx, base, offset, count);
+
+ for (i = 0; i < count; i++) {
+ __uint64_t off = offset + i * sizeof(mmv_disk_metric_t);
+ printf(" [%u/%"PRIi64"] %s\n", m[i].item, off, m[i].name);
+ printf(" type=%s (0x%x), sem=%s (0x%x), pad=0x%x\n",
+ metrictype(m[i].type), m[i].type,
+ metricsem(m[i].semantics), m[i].semantics,
+ m[i].padding);
+ printf(" units=%s\n", pmUnitsStr(&m[i].dimension));
+ if (m[i].indom != PM_INDOM_NULL && m[i].indom != 0)
+ printf(" indom=%d\n", m[i].indom);
+ else
+ printf(" (no indom)\n");
+ if (m[i].shorttext) {
+ string = (mmv_disk_string_t *)
+ ((char *)addr + m[i].shorttext);
+ printf(" shorttext=%s\n", string->payload);
+ }
+ else
+ printf(" (no shorttext)\n");
+ if (m[i].helptext) {
+ string = (mmv_disk_string_t *)
+ ((char *)addr + m[i].helptext);
+ printf(" helptext=%s\n", string->payload);
+ }
+ else
+ printf(" (no helptext)\n");
+ }
+}
+
+void
+dump_values(void *addr, int idx, long base, __uint64_t offset, __int32_t count)
+{
+ int i;
+ mmv_disk_value_t * vals = (mmv_disk_value_t *)
+ ((char *)addr + offset);
+
+ printf("\nTOC[%d]: offset %ld, values offset %"PRIu64" (%d entries)\n",
+ idx, base, offset, count);
+
+ for (i = 0; i < count; i++) {
+ mmv_disk_string_t * string;
+ mmv_disk_metric_t * m = (mmv_disk_metric_t *)
+ ((char *)addr + vals[i].metric);
+ __uint64_t off = offset + i * sizeof(mmv_disk_value_t);
+
+ printf(" [%u/%"PRIu64"] %s", m->item, off, m->name);
+ if (m->indom && m->indom != PM_IN_NULL) {
+ mmv_disk_instance_t *indom = (mmv_disk_instance_t *)
+ ((char *)addr + vals[i].instance);
+ printf("[%d or \"%s\"]",
+ indom->internal, indom->external);
+ }
+
+ switch (m->type) {
+ case MMV_TYPE_I32:
+ printf(" = %d", vals[i].value.l);
+ break;
+ case MMV_TYPE_U32:
+ printf(" = %u", vals[i].value.ul);
+ break;
+ case MMV_TYPE_I64:
+ printf(" = %" PRIi64, vals[i].value.ll);
+ break;
+ case MMV_TYPE_U64:
+ printf(" = %" PRIu64, vals[i].value.ull);
+ break;
+ case MMV_TYPE_FLOAT:
+ printf(" = %f", vals[i].value.f);
+ break;
+ case MMV_TYPE_DOUBLE:
+ printf(" = %lf", vals[i].value.d);
+ break;
+ case MMV_TYPE_STRING:
+ string = (mmv_disk_string_t *)((char *)addr + vals[i].extra);
+ printf(" = \"%s\"", string->payload);
+ break;
+ case MMV_TYPE_ELAPSED: {
+ struct timeval tv;
+ __int64_t t;
+
+ __pmtimevalNow(&tv);
+ t = vals[i].value.ll;
+ if (vals[i].extra < 0)
+ t += ((tv.tv_sec*1e6 + tv.tv_usec) + vals[i].extra);
+ printf(" = %"PRIi64" (value=%"PRIi64"/extra=%"PRIi64")",
+ t, vals[i].value.ll, vals[i].extra);
+ if (vals[i].extra > 0)
+ printf("Bad ELAPSED 'extra' value found!");
+ break;
+ }
+ default:
+ printf("Unknown type %d", m->type);
+ }
+ putchar('\n');
+ }
+}
+
+void
+dump_strings(void *addr, int idx, long base, __uint64_t offset, __int32_t count)
+{
+ int i;
+ mmv_disk_string_t * string = (mmv_disk_string_t *)
+ ((char *)addr + offset);
+
+ printf("\nTOC[%d]: offset %ld, string offset %"PRIu64" (%d entries)\n",
+ idx, base, offset, count);
+
+ for (i = 0; i < count; i++) {
+ printf(" [%u/%"PRIu64"] %s\n",
+ i+1, offset + i * sizeof(mmv_disk_string_t),
+ string[i].payload);
+ }
+}
+
+int
+dump(const char *file, void *addr)
+{
+ int i;
+ mmv_disk_header_t * hdr = (mmv_disk_header_t *) addr;
+ mmv_disk_toc_t * toc = (mmv_disk_toc_t *)
+ ((char *)addr + sizeof(mmv_disk_header_t));
+
+ if (strcmp(hdr->magic, "MMV")) {
+ printf("Bad magic: %c%c%c\n",
+ hdr->magic[0], hdr->magic[1], hdr->magic[2]);
+ return 1;
+ }
+ if (hdr->version != MMV_VERSION) {
+ printf("version %d not supported\n", hdr->version);
+ return 1;
+ }
+
+ printf("MMV file = %s\n", file);
+ printf("Version = %d\n", hdr->version);
+ printf("Generated = %"PRIu64"\n", hdr->g1);
+ if (hdr->g1 != hdr->g2) {
+ printf("Generated2 = %"PRIu64"\n", hdr->g2);
+ printf("Mismatched generation numbers\n");
+ return 1;
+ }
+ printf("TOC count = %u\n", hdr->tocs);
+ printf("Cluster = %u\n", hdr->cluster);
+ printf("Process = %d\n", hdr->process);
+ printf("Flags = 0x%x\n", hdr->flags);
+
+ for (i = 0; i < hdr->tocs; i++) {
+ __uint64_t base = ((char *)&toc[i] - (char *)addr);
+ switch (toc[i].type) {
+ case MMV_TOC_INDOMS:
+ dump_indoms(addr, i, base, toc[i].offset, toc[i].count);
+ break;
+ case MMV_TOC_INSTANCES:
+ dump_insts(addr, i, base, toc[i].offset, toc[i].count);
+ break;
+ case MMV_TOC_VALUES:
+ dump_values(addr, i, base, toc[i].offset, toc[i].count);
+ break;
+ case MMV_TOC_METRICS:
+ dump_metrics(addr, i, base, toc[i].offset, toc[i].count);
+ break;
+ case MMV_TOC_STRINGS:
+ dump_strings(addr, i, base, toc[i].offset, toc[i].count);
+ break;
+ default:
+ printf("Unrecognised TOC[%d] type: 0x%x\n", i, toc[i].type);
+ }
+ }
+ return 0;
+}
+
+int
+main(int argc, char * argv[])
+{
+ int fd;
+ char file[MAXPATHLEN];
+
+ if (argc > 2) {
+ printf("USAGE: %s <filename>\n", argv[0]);
+ exit(1);
+ }
+ if (argc > 1)
+ strncpy(file, argv[1], MAXPATHLEN);
+ else
+ snprintf(file, MAXPATHLEN, "%s%cmmv%ctest",
+ pmGetConfig("PCP_TMP_DIR"),
+ __pmPathSeparator(), __pmPathSeparator());
+ file[MAXPATHLEN-1] = '\0';
+
+ if ((fd = open(file, O_RDONLY)) < 0)
+ perror(file);
+ else {
+ struct stat s;
+ void * addr;
+
+ if (fstat(fd, &s) < 0)
+ perror(file);
+ else if ((addr = __pmMemoryMap(fd, s.st_size, 0)) != NULL)
+ return dump(file, addr);
+ }
+ return 1;
+}
diff --git a/src/pmdas/mmv/src/GNUmakefile b/src/pmdas/mmv/src/GNUmakefile
new file mode 100644
index 0000000..421c38b
--- /dev/null
+++ b/src/pmdas/mmv/src/GNUmakefile
@@ -0,0 +1,59 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2009-2010 Aconex. All Rights Reserved.
+# Copyright (c) 2000-2001,2009 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
+
+IAM = mmv
+DOMAIN = MMV
+CMDTARGET = pmda$(IAM)$(EXECSUFFIX)
+LIBTARGET = pmda_$(IAM).$(DSOSUFFIX)
+PMDAINIT = $(IAM)_init
+TARGETS = $(CMDTARGET) $(LIBTARGET)
+
+CFILES = mmv.c
+VERSION_SCRIPT = exports
+LSRCFILES = Install Remove root_mmv
+LLDLIBS = $(PCP_PMDALIB)
+LCFLAGS = $(INVISIBILITY)
+LDIRT = domain.h *.log pmns $(VERSION_SCRIPT)
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+default_pcp default: $(TARGETS) pmns
+
+include $(BUILDRULES)
+
+install_pcp install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 644 domain.h $(PMDADIR)/domain.h
+ $(INSTALL) -m 755 $(TARGETS) Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmns $(PMDADIR)/root_mmv
+ $(INSTALL) -m 644 root_mmv $(PCP_VAR_DIR)/pmns/root_mmv
+
+$(CMDTARGET): $(OBJECTS)
+
+$(IAM).o : domain.h
+$(LIBTARGET) : $(VERSION_SCRIPT)
+
+$(VERSION_SCRIPT):
+ $(VERSION_SCRIPT_MAKERULE)
+
+domain.h: ../../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+pmns :
+ $(LN_S) -f root_mmv pmns
diff --git a/src/pmdas/mmv/src/Install b/src/pmdas/mmv/src/Install
new file mode 100755
index 0000000..d8f148b
--- /dev/null
+++ b/src/pmdas/mmv/src/Install
@@ -0,0 +1,36 @@
+#! /bin/sh
+#
+# Copyright (C) 1997,2009 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (C) 2009 Aconex. 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.
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=mmv
+dso_opt=true
+socket_opt=true
+socket_inet_def=2081
+forced_restart=false
+pmda_interface=4
+pmns_source=root_mmv
+
+if [ ! -e "$PCP_TMP_DIR/mmv" ]
+then
+ echo "creating $PCP_TMP_DIR/mmv"
+ mkdir -p -m 1777 "$PCP_TMP_DIR/mmv"
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/mmv/src/Remove b/src/pmdas/mmv/src/Remove
new file mode 100755
index 0000000..a463371
--- /dev/null
+++ b/src/pmdas/mmv/src/Remove
@@ -0,0 +1,23 @@
+#! /bin/sh
+#
+# Copyright (C) 1997,2009 Silicon Graphics, Inc., All Rights Reserved.
+# Copyright (C) 2009 Aconex. 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.
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=mmv
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/mmv/src/mmv.c b/src/pmdas/mmv/src/mmv.c
new file mode 100644
index 0000000..ecc97ed
--- /dev/null
+++ b/src/pmdas/mmv/src/mmv.c
@@ -0,0 +1,915 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2009-2010 Aconex. All Rights Reserved.
+ * Copyright (c) 1995-2000,2009 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.
+ *
+ *
+ * MMV PMDA
+ *
+ * This PMDA uses specially formatted files in either /var/tmp/mmv or some
+ * other directory, as specified on the command line. Each file represents
+ * a separate "cluster" of values with flat name structure for each cluster.
+ * Names for the metrics are optionally prepended with mmv and then the name
+ * of the file (by default - this can be changed).
+ */
+
+#include "pmapi.h"
+#include "mmv_stats.h"
+#include "mmv_dev.h"
+#include "impl.h"
+#include "pmda.h"
+#include "./domain.h"
+#include <sys/stat.h>
+#include <ctype.h>
+
+static int isDSO = 1;
+static char *username;
+
+/* command line option handling - both short and long options */
+static pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+static pmdaOptions opts = {
+ .short_options = "D:d:l:U:?",
+ .long_options = longopts,
+};
+
+static pmdaMetric * metrics;
+static int mcnt;
+static pmdaIndom * indoms;
+static int incnt;
+
+static int reload;
+static __pmnsTree * pmns;
+static int statsdir_code; /* last statsdir stat code */
+static time_t statsdir_ts; /* last statsdir timestamp */
+static char * prefix = "mmv";
+
+static char * pcptmpdir; /* probably /var/tmp */
+static char * pcpvardir; /* probably /var/pcp */
+static char * pcppmdasdir; /* probably /var/pcp/pmdas */
+static char pmnsdir[MAXPATHLEN]; /* pcpvardir/pmns */
+static char statsdir[MAXPATHLEN]; /* pcptmpdir/<prefix> */
+
+typedef struct {
+ char * name; /* strdup client name */
+ void * addr; /* mmap */
+ mmv_disk_value_t * values; /* values in mmap */
+ mmv_disk_metric_t * metrics; /* metric descs in mmap */
+ int vcnt; /* number of values */
+ int mcnt; /* number of metrics */
+ pid_t pid; /* process identifier */
+ int cluster; /* cluster identifier */
+ __int64_t len; /* mmap region len */
+ __uint64_t gen; /* generation number on open */
+} stats_t;
+
+static stats_t * slist;
+static int scnt;
+
+/*
+ * Choose an unused cluster ID while honouring specific requests.
+ * If a specific (non-zero) cluster is requested we always use it.
+ */
+static int
+choose_cluster(int requested, const char *path)
+{
+ int i;
+
+ if (!requested) {
+ int next_cluster = 1;
+
+ for (i = 0; i < scnt; i++) {
+ if (slist[i].cluster == next_cluster) {
+ next_cluster++;
+ i = 0; /* restart, we're filling holes */
+ }
+ }
+ return next_cluster;
+ }
+
+ for (i = 0; i < scnt; i++) {
+ if (slist[i].cluster == requested) {
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG,
+ "MMV: %s: duplicate cluster %d in use",
+ pmProgname, requested);
+ break;
+ }
+ }
+ return requested;
+}
+
+static int
+create_client_stat(const char *client, const char *path, size_t size)
+{
+ int fd;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: create_client_stat: %s, %s", client, path);
+
+ if ((fd = open(path, O_RDONLY)) >= 0) {
+ void *m = __pmMemoryMap(fd, size, 0);
+
+ close(fd);
+ if (m != NULL) {
+ mmv_disk_header_t * hdr = (mmv_disk_header_t *)m;
+ int cluster;
+
+ if (strncmp(hdr->magic, "MMV", 4)) {
+ __pmMemoryUnmap(m, size);
+ return -EINVAL;
+ }
+
+ if (hdr->version != MMV_VERSION) {
+ __pmNotifyErr(LOG_ERR, "%s: %s client version %d "
+ "not supported (current is %d)",
+ pmProgname, prefix, hdr->version, MMV_VERSION);
+ __pmMemoryUnmap(m, size);
+ return -ENOSYS;
+ }
+
+ if (!hdr->g1 || hdr->g1 != hdr->g2) {
+ /* still in flux, wait till next time */
+ __pmMemoryUnmap(m, size);
+ return -EAGAIN;
+ }
+
+ /* optionally verify the creator PID is running */
+ if (hdr->process && (hdr->flags & MMV_FLAG_PROCESS) &&
+ !__pmProcessExists((pid_t)hdr->process)) {
+ __pmMemoryUnmap(m, size);
+ return -ESRCH;
+ }
+
+ /* all checks out, we'll use this one */
+ cluster = choose_cluster(hdr->cluster, path);
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: %s: loading %s client: %d \"%s\"",
+ pmProgname, prefix, cluster, path);
+
+ slist = realloc(slist, sizeof(stats_t)*(scnt+1));
+ if (slist != NULL) {
+ slist[scnt].name = strdup(client);
+ slist[scnt].addr = m;
+ slist[scnt].pid = (pid_t)((hdr->flags & MMV_FLAG_PROCESS)? hdr->process : 0);
+ slist[scnt].cluster = cluster;
+ slist[scnt].mcnt = 0;
+ slist[scnt].gen = hdr->g1;
+ slist[scnt].len = size;
+ scnt++;
+ } else {
+ __pmNotifyErr(LOG_ERR, "%s: client \"%s\" out of memory - %s",
+ pmProgname, client, osstrerror());
+ __pmMemoryUnmap(m, size);
+ scnt = 0;
+ }
+ } else {
+ __pmNotifyErr(LOG_ERR, "%s: failed to memory map \"%s\" - %s",
+ pmProgname, path, osstrerror());
+ }
+ } else {
+ __pmNotifyErr(LOG_ERR, "%s: failed to open client file \"%s\" - %s",
+ pmProgname, client, osstrerror());
+ }
+ return 0;
+}
+
+/* check validity of client metric name, return non-zero if bad or duplicate */
+static int
+verify_metric_name(const char *name, int pos, stats_t *s)
+{
+ const char *p = name;
+ pmID pmid;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: verify_metric_name: %s", name);
+
+ if (p == NULL || *p == '\0' || !isalpha((int)*p)) {
+ __pmNotifyErr(LOG_WARNING, "Invalid metric[%d] name start in %s, ignored",
+ pos, s->name);
+ return -EINVAL;
+ }
+ for (++p; (p != NULL && *p != '\0'); p++) {
+ if (isalnum((int)*p) || *p == '_' || *p == '.')
+ continue;
+ __pmNotifyErr(LOG_WARNING, "invalid metric[%d] name in %s (@%c), ignored",
+ pos, s->name, *p);
+ return -EINVAL;
+ }
+ if (pmdaTreePMID(pmns, name, &pmid) == 0)
+ return -EEXIST;
+ return 0;
+}
+
+/* check client item number validity - must not be too large to fit in PMID! */
+static int
+verify_metric_item(unsigned int item, char *name, stats_t *s)
+{
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: verify_metric_item: %u - %s", item, name);
+
+ if (pmid_item(item) != item) {
+ __pmNotifyErr(LOG_WARNING, "invalid item %u (%s) in %s, ignored",
+ item, name, s->name);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int
+create_metric(pmdaExt *pmda, stats_t *s, mmv_disk_metric_t *m, char *name, pmID pmid)
+{
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: create_metric: %s - %s", name, pmIDStr(pmid));
+
+ metrics = realloc(metrics, sizeof(pmdaMetric) * (mcnt + 1));
+ if (metrics == NULL) {
+ __pmNotifyErr(LOG_ERR, "cannot grow MMV metric list: %s", s->name);
+ return -ENOMEM;
+ }
+
+ metrics[mcnt].m_user = NULL;
+ metrics[mcnt].m_desc.pmid = pmid;
+
+ if (m->type == MMV_TYPE_ELAPSED) {
+ pmUnits unit = PMDA_PMUNITS(0,1,0,0,PM_TIME_USEC,0);
+ metrics[mcnt].m_desc.sem = PM_SEM_COUNTER;
+ metrics[mcnt].m_desc.type = MMV_TYPE_I64;
+ metrics[mcnt].m_desc.units = unit;
+ } else {
+ if (m->semantics)
+ metrics[mcnt].m_desc.sem = m->semantics;
+ else
+ metrics[mcnt].m_desc.sem = PM_SEM_COUNTER;
+ metrics[mcnt].m_desc.type = m->type;
+ memcpy(&metrics[mcnt].m_desc.units, &m->dimension, sizeof(pmUnits));
+ }
+ metrics[mcnt].m_desc.indom = (!m->indom || m->indom == PM_INDOM_NULL) ?
+ PM_INDOM_NULL : pmInDom_build(pmda->e_domain,
+ (s->cluster << 11) | m->indom);
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: map_stats adding metric[%d] %s %s from %s\n",
+ mcnt, name, pmIDStr(pmid), s->name);
+
+ mcnt++;
+ __pmAddPMNSNode(pmns, pmid, name);
+
+ return 0;
+}
+
+/* check client serial number validity, and check for a duplicate */
+static int
+verify_indom_serial(pmdaExt *pmda, int serial, stats_t *s, pmInDom *p, pmdaIndom **i)
+{
+ int index;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: verify_indom_serial: %u", serial);
+
+ if (pmInDom_serial(serial) != serial) {
+ __pmNotifyErr(LOG_WARNING, "invalid serial %u in %s, ignored",
+ serial, s->name);
+ return -EINVAL;
+ }
+
+ *p = pmInDom_build(pmda->e_domain, (s->cluster << 11) | serial);
+ for (index = 0; index < incnt; index++) {
+ *i = &indoms[index];
+ if (indoms[index].it_indom == *p)
+ return -EEXIST;
+ }
+ *i = NULL;
+ return 0;
+}
+
+static int
+update_indom(pmdaExt *pmda, stats_t *s, mmv_disk_indom_t *id, pmdaIndom *ip)
+{
+ int i, j, size, newinsts = 0;
+ mmv_disk_instance_t *in = (mmv_disk_instance_t *)((char *)s->addr + id->offset);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: update_indom: %u (%d insts)",
+ id->serial, ip->it_numinst);
+
+ /* first calculate how many new instances, so we know what to alloc */
+ for (i = 0; i < id->count; i++) {
+ for (j = 0; j < ip->it_numinst; j++)
+ if (ip->it_set[j].i_inst == in[i].internal)
+ continue;
+ if (j == ip->it_numinst)
+ newinsts++;
+ }
+
+ if (!newinsts)
+ return 0;
+
+ /* allocate memory, then append new instances to the known set */
+ size = sizeof(pmdaInstid) * (ip->it_numinst + newinsts);
+ ip->it_set = (pmdaInstid *)realloc(ip->it_set, size);
+ if (ip->it_set != NULL) {
+ for (i = 0; i < id->count; i++) {
+ for (j = 0; j < ip->it_numinst; j++)
+ if (ip->it_set[j].i_inst == in[j].internal)
+ continue;
+ if (j == ip->it_numinst) {
+ ip->it_set[j].i_inst = in[i].internal;
+ ip->it_set[j].i_name = in[i].external;
+ ip->it_numinst++;
+ }
+ }
+ } else {
+ __pmNotifyErr(LOG_ERR, "%s: cannot get memory for instance list in %s",
+ pmProgname, s->name);
+ ip->it_numinst = 0;
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static int
+create_indom(pmdaExt *pmda, stats_t *s, mmv_disk_indom_t *id, pmInDom indom)
+{
+ int i;
+ pmdaIndom *ip;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: create_indom: %u", id->serial);
+
+ indoms = realloc(indoms, sizeof(pmdaIndom) * (incnt + 1));
+ if (indoms == NULL) {
+ __pmNotifyErr(LOG_ERR, "%s: cannot grow indom list in %s",
+ pmProgname, s->name);
+ return -ENOMEM;
+ }
+ ip = &indoms[incnt++];
+ ip->it_indom = indom;
+ ip->it_set = (pmdaInstid *)calloc(id->count, sizeof(pmdaInstid));
+ if (ip->it_set != NULL) {
+ mmv_disk_instance_t * in = (mmv_disk_instance_t *)
+ ((char *)s->addr + id->offset);
+ ip->it_numinst = id->count;
+ for (i = 0; i < ip->it_numinst; i++) {
+ ip->it_set[i].i_inst = in[i].internal;
+ ip->it_set[i].i_name = in[i].external;
+ }
+ } else {
+ __pmNotifyErr(LOG_ERR, "%s: cannot get memory for instance list in %s",
+ pmProgname, s->name);
+ ip->it_numinst = 0;
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static void
+map_stats(pmdaExt *pmda)
+{
+ struct dirent **files;
+ char name[64];
+ int need_reload = 0;
+ int i, j, k, sts, num;
+
+ if (pmns)
+ __pmFreePMNS(pmns);
+
+ if ((sts = __pmNewPMNS(&pmns)) < 0) {
+ __pmNotifyErr(LOG_ERR, "%s: failed to create new pmns: %s\n",
+ pmProgname, pmErrStr(sts));
+ pmns = NULL;
+ return;
+ }
+
+ /* hard-coded metrics (not from mmap'd files */
+ snprintf(name, sizeof(name), "%s.reload", prefix);
+ __pmAddPMNSNode(pmns, pmid_build(pmda->e_domain, 0, 0), name);
+ snprintf(name, sizeof(name), "%s.debug", prefix);
+ __pmAddPMNSNode(pmns, pmid_build(pmda->e_domain, 0, 1), name);
+ mcnt = 2;
+
+ if (indoms != NULL) {
+ for (i = 0; i < incnt; i++)
+ free(indoms[i].it_set);
+ free(indoms);
+ indoms = NULL;
+ incnt = 0;
+ }
+
+ if (slist != NULL) {
+ for (i = 0; i < scnt; i++) {
+ free(slist[i].name);
+ __pmMemoryUnmap(slist[i].addr, slist[i].len);
+ }
+ free(slist);
+ slist = NULL;
+ scnt = 0;
+ }
+
+ num = scandir(statsdir, &files, NULL, NULL);
+ for (i = 0; i < num; i++) {
+ struct stat statbuf;
+ char path[MAXPATHLEN];
+ char *client;
+
+ if (files[i]->d_name[0] == '.')
+ continue;
+
+ client = files[i]->d_name;
+ sprintf(path, "%s%c%s", statsdir, __pmPathSeparator(), client);
+
+ if (stat(path, &statbuf) >= 0 && S_ISREG(statbuf.st_mode))
+ if (create_client_stat(client, path, statbuf.st_size) == -EAGAIN)
+ need_reload = 1;
+ }
+
+ for (i = 0; i < num; i++)
+ free(files[i]);
+ if (num > 0)
+ free(files);
+
+ for (i = 0; slist && i < scnt; i++) {
+ stats_t * s = slist + i;
+ mmv_disk_header_t * hdr = (mmv_disk_header_t *)s->addr;
+ mmv_disk_toc_t * toc = (mmv_disk_toc_t *)
+ ((char *)s->addr + sizeof(mmv_disk_header_t));
+
+ for (j = 0; j < hdr->tocs; j++) {
+ switch (toc[j].type) {
+ case MMV_TOC_METRICS: {
+ mmv_disk_metric_t *ml = (mmv_disk_metric_t *)
+ ((char *)s->addr + toc[j].offset);
+
+ s->metrics = ml;
+ s->mcnt = toc[j].count;
+
+ for (k = 0; k < toc[j].count; k++) {
+ char name[MAXPATHLEN];
+ pmID pmid;
+
+ /* build name, check its legitimate and unique */
+ if (hdr->flags & MMV_FLAG_NOPREFIX)
+ sprintf(name, "%s.", prefix);
+ else
+ sprintf(name, "%s.%s.", prefix, s->name);
+ strcat(name, ml[k].name);
+ if (verify_metric_name(name, k, s) != 0)
+ continue;
+ if (verify_metric_item(ml[k].item, name, s) != 0)
+ continue;
+
+ pmid = pmid_build(pmda->e_domain, s->cluster, ml[k].item);
+ create_metric(pmda, s, &ml[k], name, pmid);
+ }
+ break;
+ }
+
+ case MMV_TOC_INDOMS: {
+ mmv_disk_indom_t * id = (mmv_disk_indom_t *)
+ ((char *)s->addr + toc[j].offset);
+
+ for (k = 0; k < toc[j].count; k++) {
+ int sts, serial = id[k].serial;
+ pmInDom pmindom;
+ pmdaIndom *ip;
+
+ sts = verify_indom_serial(pmda, serial, s, &pmindom, &ip);
+ if (sts == -EINVAL)
+ continue;
+ else if (sts == -EEXIST)
+ /* see if we have new instances to add here */
+ update_indom(pmda, s, &id[k], ip);
+ else
+ /* first time we've observed this indom */
+ create_indom(pmda, s, &id[k], pmindom);
+ }
+ break;
+ }
+
+ case MMV_TOC_VALUES: {
+ s->vcnt = toc[j].count;
+ s->values = (mmv_disk_value_t *)
+ ((char *)s->addr + toc[j].offset);
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ }
+
+ pmdaTreeRebuildHash(pmns, mcnt); /* for reverse (pmid->name) lookups */
+ reload = need_reload;
+}
+
+static int
+mmv_lookup_stat_metric_value(pmID pmid, unsigned int inst,
+ stats_t **sout, mmv_disk_metric_t **mout, mmv_disk_value_t **vout)
+{
+ __pmID_int * id = (__pmID_int *)&pmid;
+ mmv_disk_metric_t * m;
+ mmv_disk_value_t * v;
+ stats_t * s;
+ int si, mi, vi;
+ int sts = PM_ERR_PMID;
+
+ for (si = 0; si < scnt; si++) {
+ s = &slist[si];
+ if (s->cluster != id->cluster)
+ continue;
+
+ m = s->metrics;
+ for (mi = 0; mi < s->mcnt; mi++) {
+ if (m[mi].item != id->item)
+ continue;
+
+ sts = PM_ERR_INST;
+ v = s->values;
+ for (vi = 0; vi < s->vcnt; vi++) {
+ mmv_disk_metric_t * mt = (mmv_disk_metric_t *)
+ ((char *)s->addr + v[vi].metric);
+ mmv_disk_instance_t * is = (mmv_disk_instance_t *)
+ ((char *)s->addr + v[vi].instance);
+
+ if ((mt == &m[mi]) &&
+ (mt->indom == PM_INDOM_NULL || mt->indom == 0 ||
+ inst == PM_IN_NULL || is->internal == inst)) {
+ *sout = s;
+ *mout = &m[mi];
+ *vout = &v[vi];
+ return 0;
+ }
+ }
+ }
+ }
+ return sts;
+}
+
+/*
+ * callback provided to pmdaFetch
+ */
+static int
+mmv_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int * id = (__pmID_int *)&(mdesc->m_desc.pmid);
+
+ if (id->cluster == 0) {
+ if (id->item == 0) {
+ atom->l = reload;
+ return 1;
+ }
+ if (id->item == 1) {
+ atom->l = pmDebug;
+ return 1;
+ }
+ return PM_ERR_PMID;
+
+ } else if (scnt > 0) { /* We have at least one source of metrics */
+ mmv_disk_string_t * str;
+ mmv_disk_metric_t * m;
+ mmv_disk_value_t * v;
+ stats_t * s;
+ int rv;
+
+ rv = mmv_lookup_stat_metric_value(mdesc->m_desc.pmid, inst, &s, &m, &v);
+ if (rv < 0)
+ return rv;
+
+ switch (m->type) {
+ case MMV_TYPE_I32:
+ case MMV_TYPE_U32:
+ case MMV_TYPE_I64:
+ case MMV_TYPE_U64:
+ case MMV_TYPE_FLOAT:
+ case MMV_TYPE_DOUBLE:
+ memcpy(atom, &v->value, sizeof(pmAtomValue));
+ break;
+ case MMV_TYPE_ELAPSED: {
+ atom->ll = v->value.ll;
+ if (v->extra < 0) { /* inside a timed section */
+ struct timeval tv;
+ __pmtimevalNow(&tv);
+ atom->ll += (tv.tv_sec * 1e6 + tv.tv_usec) + v->extra;
+ }
+ break;
+ }
+ case MMV_TYPE_STRING: {
+ str = (mmv_disk_string_t *)((char *)s->addr + v->extra);
+ atom->cp = str->payload;
+ break;
+ }
+ case MMV_TYPE_NOSUPPORT:
+ return PM_ERR_APPVERSION;
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+static void
+mmv_reload_maybe(pmdaExt *pmda)
+{
+ int i;
+ struct stat s;
+ int need_reload = reload;
+
+ /* check if generation numbers changed or monitored process exited */
+ for (i = 0; i < scnt; i++) {
+ mmv_disk_header_t *hdr = (mmv_disk_header_t *)slist[i].addr;
+ if (hdr->g1 != slist[i].gen || hdr->g2 != slist[i].gen) {
+ need_reload++;
+ break;
+ }
+ if (slist[i].pid && !__pmProcessExists(slist[i].pid)) {
+ need_reload++;
+ break;
+ }
+ }
+
+ /*
+ * check if the directory has been modified, reload if so;
+ * note modification may involve removal or newly appeared,
+ * a change in permissions from accessible to not (or vice-
+ * versa), and so on.
+ */
+ if (stat(statsdir, &s) >= 0) {
+ if (s.st_mtime != statsdir_ts) {
+ need_reload++;
+ statsdir_code = 0;
+ statsdir_ts = s.st_mtime;
+ }
+ } else {
+ i = oserror();
+ if (statsdir_code != i) {
+ statsdir_code = i;
+ statsdir_ts = 0;
+ need_reload++;
+ }
+ }
+
+ if (need_reload) {
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: %s: reloading", pmProgname);
+ map_stats(pmda);
+
+ pmda->e_indoms = indoms;
+ pmda->e_nindoms = incnt;
+ pmdaRehash(pmda, metrics, mcnt);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG,
+ "MMV: %s: %d metrics and %d indoms after reload",
+ pmProgname, mcnt, incnt);
+ }
+}
+
+/* Intercept request for descriptor and check if we'd have to reload */
+static int
+mmv_desc(pmID pmid, pmDesc *desc, pmdaExt *ep)
+{
+ mmv_reload_maybe(ep);
+ return pmdaDesc(pmid, desc, ep);
+}
+
+static int
+mmv_text(int ident, int type, char **buffer, pmdaExt *ep)
+{
+ if (type & PM_TEXT_INDOM)
+ return PM_ERR_TEXT;
+
+ mmv_reload_maybe(ep);
+ if (pmid_cluster(ident) == 0) {
+ if (pmid_item(ident) == 0) {
+ static char reloadoneline[] = "Control maps reloading";
+ static char reloadtext[] =
+"Writing anything other then 0 to this metric will result in\n"
+"re-reading directory and re-mapping files.\n";
+
+ *buffer = (type & PM_TEXT_ONELINE) ? reloadoneline : reloadtext;
+ return 0;
+ }
+ else if (pmid_item(ident) == 1) {
+ static char debugoneline[] = "Debug flag";
+ static char debugtext[] =
+"See pmdbg(1). pmstore into this metric to change the debug value.\n";
+
+ *buffer = (type & PM_TEXT_ONELINE) ? debugoneline : debugtext;
+ return 0;
+ }
+ else
+ return PM_ERR_PMID;
+ }
+ else {
+ mmv_disk_string_t * str;
+ mmv_disk_metric_t * m;
+ mmv_disk_value_t * v;
+ stats_t * s;
+
+ if (mmv_lookup_stat_metric_value(ident, PM_IN_NULL, &s, &m, &v) != 0)
+ return PM_ERR_PMID;
+
+ if ((type & PM_TEXT_ONELINE) && m->shorttext) {
+ str = (mmv_disk_string_t *)((char *)s->addr + m->shorttext);
+ *buffer = str->payload;
+ return 0;
+ }
+ if ((type & PM_TEXT_HELP) && m->helptext) {
+ str = (mmv_disk_string_t *)((char *)s->addr + m->helptext);
+ *buffer = str->payload;
+ return 0;
+ }
+ }
+
+ return PM_ERR_TEXT;
+}
+
+static int
+mmv_instance(pmInDom indom, int inst, char *name,
+ __pmInResult **result, pmdaExt *ep)
+{
+ mmv_reload_maybe(ep);
+ return pmdaInstance(indom, inst, name, result, ep);
+}
+
+static int
+mmv_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ mmv_reload_maybe(pmda);
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+static int
+mmv_store(pmResult *result, pmdaExt *ep)
+{
+ int i, m;
+
+ mmv_reload_maybe(ep);
+
+ for (i = 0; i < result->numpmid; i++) {
+ pmValueSet * vsp = result->vset[i];
+ __pmID_int * id = (__pmID_int *)&vsp->pmid;
+
+ if (id->cluster == 0) {
+ for (m = 0; m < mcnt; m++) {
+ __pmID_int * mid = (__pmID_int *)&(metrics[m].m_desc.pmid);
+
+ if (mid->cluster == 0 && mid->item == id->item) {
+ pmAtomValue atom;
+ int sts;
+
+ if (vsp->numval != 1 )
+ return PM_ERR_CONV;
+
+ if ((sts = pmExtractValue(vsp->valfmt, &vsp->vlist[0],
+ PM_TYPE_32, &atom, PM_TYPE_32)) < 0)
+ return sts;
+ if (id->item == 0)
+ reload = atom.l;
+ else if (id->item == 1)
+ pmDebug = atom.l;
+ else
+ return PM_ERR_PERMISSION;
+ }
+ }
+ }
+ else
+ return PM_ERR_PERMISSION;
+ }
+ return 0;
+}
+
+static int
+mmv_pmid(const char *name, pmID *pmid, pmdaExt *pmda)
+{
+ mmv_reload_maybe(pmda);
+ return pmdaTreePMID(pmns, name, pmid);
+}
+
+static int
+mmv_name(pmID pmid, char ***nameset, pmdaExt *pmda)
+{
+ mmv_reload_maybe(pmda);
+ return pmdaTreeName(pmns, pmid, nameset);
+}
+
+static int
+mmv_children(const char *name, int traverse, char ***kids, int **sts, pmdaExt *pmda)
+{
+ mmv_reload_maybe(pmda);
+ return pmdaTreeChildren(pmns, name, traverse, kids, sts);
+}
+
+void
+__PMDA_INIT_CALL
+mmv_init(pmdaInterface *dp)
+{
+ int m;
+ int sep = __pmPathSeparator();
+
+ if (isDSO) {
+ pmdaDSO(dp, PMDA_INTERFACE_4, "mmv", NULL);
+ } else {
+ __pmSetProcessIdentity(username);
+ }
+
+ pcptmpdir = pmGetConfig("PCP_TMP_DIR");
+ pcpvardir = pmGetConfig("PCP_VAR_DIR");
+ pcppmdasdir = pmGetConfig("PCP_PMDAS_DIR");
+
+ snprintf(statsdir, sizeof(statsdir), "%s%c%s", pcptmpdir, sep, prefix);
+ snprintf(pmnsdir, sizeof(pmnsdir), "%s%c" "pmns", pcpvardir, sep);
+ statsdir[sizeof(statsdir)-1] = '\0';
+ pmnsdir[sizeof(pmnsdir)-1] = '\0';
+
+ /* Initialize internal dispatch table */
+ if (dp->status == 0) {
+ /*
+ * number of hard-coded metrics here has to match initializer
+ * cases below, and pmns initialization in map_stats()
+ */
+ mcnt = 2;
+ if ((metrics = malloc(mcnt*sizeof(pmdaMetric))) != NULL) {
+ /*
+ * all the hard-coded metrics have the same semantics
+ */
+ for (m = 0; m < mcnt; m++) {
+ if (m == 0)
+ metrics[m].m_user = &reload;
+ else if (m == 1)
+ metrics[m].m_user = &pmDebug;
+ metrics[m].m_desc.pmid = pmid_build(dp->domain, 0, m);
+ metrics[m].m_desc.type = PM_TYPE_32;
+ metrics[m].m_desc.indom = PM_INDOM_NULL;
+ metrics[m].m_desc.sem = PM_SEM_INSTANT;
+ memset(&metrics[m].m_desc.units, 0, sizeof(pmUnits));
+ }
+ } else {
+ __pmNotifyErr(LOG_ERR, "%s: pmdaInit - out of memory\n",
+ pmProgname);
+ if (isDSO)
+ return;
+ exit(0);
+ }
+
+ dp->version.four.fetch = mmv_fetch;
+ dp->version.four.store = mmv_store;
+ dp->version.four.desc = mmv_desc;
+ dp->version.four.text = mmv_text;
+ dp->version.four.instance = mmv_instance;
+ dp->version.four.pmid = mmv_pmid;
+ dp->version.four.name = mmv_name;
+ dp->version.four.children = mmv_children;
+ pmdaSetFetchCallBack(dp, mmv_fetchCallBack);
+
+ pmdaSetFlags(dp, PMDA_EXT_FLAG_HASHED);
+ pmdaInit(dp, indoms, incnt, metrics, mcnt);
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ char logfile[32];
+ pmdaInterface dispatch = { 0 };
+
+ isDSO = 0;
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ if (strncmp(pmProgname, "pmda", 4) == 0 && strlen(pmProgname) > 4)
+ prefix = pmProgname + 4;
+ snprintf(logfile, sizeof(logfile), "%s.log", prefix);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_4, pmProgname, MMV, logfile, NULL);
+
+ pmdaGetOptions(argc, argv, &opts, &dispatch);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+ if (opts.username)
+ username = opts.username;
+
+ pmdaOpenLog(&dispatch);
+ mmv_init(&dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+ exit(0);
+}
diff --git a/src/pmdas/mmv/src/root_mmv b/src/pmdas/mmv/src/root_mmv
new file mode 100644
index 0000000..28c810a
--- /dev/null
+++ b/src/pmdas/mmv/src/root_mmv
@@ -0,0 +1,13 @@
+/*
+ * MMV metrics name space
+ */
+
+#ifndef MMV
+#define MMV 70
+#endif
+
+root {
+ mmv MMV:*:*
+}
+
+#undef MMV