summaryrefslogtreecommitdiff
path: root/src/pmdas/rpm
diff options
context:
space:
mode:
Diffstat (limited to 'src/pmdas/rpm')
-rw-r--r--src/pmdas/rpm/GNUmakefile67
-rw-r--r--src/pmdas/rpm/Install31
-rw-r--r--src/pmdas/rpm/Remove25
-rw-r--r--src/pmdas/rpm/help57
-rw-r--r--src/pmdas/rpm/migrate.conf8
-rw-r--r--src/pmdas/rpm/pmns38
-rw-r--r--src/pmdas/rpm/root10
-rw-r--r--src/pmdas/rpm/rpm.c704
-rw-r--r--src/pmdas/rpm/rpm.h111
-rw-r--r--src/pmdas/rpm/timer.c45
-rw-r--r--src/pmdas/rpm/timer.h24
11 files changed, 1120 insertions, 0 deletions
diff --git a/src/pmdas/rpm/GNUmakefile b/src/pmdas/rpm/GNUmakefile
new file mode 100644
index 0000000..29738bf
--- /dev/null
+++ b/src/pmdas/rpm/GNUmakefile
@@ -0,0 +1,67 @@
+#
+# Copyright (c) 2013, 2014 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.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = rpm
+DOMAIN = RPM
+CMDTARGET = pmda$(IAM)$(EXECSUFFIX)
+LIBTARGET = pmda_$(IAM).$(DSOSUFFIX)
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+PMDAINIT = $(IAM)_init
+
+HFILES = rpm.h timer.h
+CFILES = rpm.c timer.c
+
+SCRIPTS = Install Remove
+VERSION_SCRIPT = exports
+LSRCFILES = Install Remove pmns root help
+LDIRT = domain.h $(IAM).log $(VERSION_SCRIPT)
+
+LIB_FOR_RPM = -lrpm
+LLDLIBS = $(PCP_PMDALIB) $(LIB_FOR_RPM) $(LIB_FOR_PTHREADS)
+LCFLAGS = $(INVISIBILITY)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq ($(HAVE_RPMLIB),1)
+build-me: domain.h $(CMDTARGET) $(LIBTARGET)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 644 root pmns domain.h help $(PMDADIR)
+ $(INSTALL) -m 755 $(CMDTARGET) $(LIBTARGET) $(SCRIPTS) $(PMDADIR)
+ $(INSTALL) -m 644 migrate.conf $(PCP_VAR_DIR)/config/pmlogrewrite/rpm_migrate.conf
+else
+build-me:
+install:
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+$(VERSION_SCRIPT):
+ $(VERSION_SCRIPT_MAKERULE)
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+rpm.o: rpm.h
+rpm.o timer.o: timer.h
+rpm.o: $(VERSION_SCRIPT)
diff --git a/src/pmdas/rpm/Install b/src/pmdas/rpm/Install
new file mode 100644
index 0000000..8f11751
--- /dev/null
+++ b/src/pmdas/rpm/Install
@@ -0,0 +1,31 @@
+#! /bin/sh
+#
+# 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.
+#
+# Install the rpm PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=rpm
+pmda_interface=5
+forced_restart=false
+
+dso_opt=true
+pipe_opt=true
+daemon_opt=true
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/rpm/Remove b/src/pmdas/rpm/Remove
new file mode 100644
index 0000000..73e423f
--- /dev/null
+++ b/src/pmdas/rpm/Remove
@@ -0,0 +1,25 @@
+#! /bin/sh
+#
+# 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.
+#
+# Remove the rpm PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=rpm
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/rpm/help b/src/pmdas/rpm/help
new file mode 100644
index 0000000..2a0320a
--- /dev/null
+++ b/src/pmdas/rpm/help
@@ -0,0 +1,57 @@
+#
+# Copyright (c) 2013-2014 Red Hat, 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.
+#
+# rpm PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ rpm.arch package architecture
+@ rpm.buildhost package build host
+@ rpm.buildtime package buildtime
+@ rpm.description package description
+@ rpm.epoch package install epoch
+@ rpm.group group of the package
+@ rpm.installtime package install time
+@ rpm.license package license
+@ rpm.packager entity responsible for packaging
+@ rpm.release release list of the package
+@ rpm.size size of the package in bytes
+@ rpm.sourcerpm package source rpm
+@ rpm.summary summary of the package
+@ rpm.url url of the package
+@ rpm.vendor package vendor
+@ rpm.version package version
+@ rpm.name package name
+
+@ rpm.refresh.count Cumulative count of rpmdb scans performed
+@ rpm.refresh.time.user Cumulative count of user mode scan time
+@ rpm.refresh.time.sys Cumulative count of kernel mode scan time
+@ rpm.refresh.time.elapsed Cumulative count of elapsed scan time
+@ rpm.datasize Space allocated for pmdarpms data segment (K)
+This metric returns the amount of memory in kilobytes allocated for the
+data segment of pmdarpm. This is handy for tracking memory utilization.
+
+@ rpm.total.count Count of packages returned in last rpmdb scan
+@ rpm.total.bytes Size of all packages from the last rpmdb scan
diff --git a/src/pmdas/rpm/migrate.conf b/src/pmdas/rpm/migrate.conf
new file mode 100644
index 0000000..1eb4086
--- /dev/null
+++ b/src/pmdas/rpm/migrate.conf
@@ -0,0 +1,8 @@
+# Copyright 2014 Red Hat.
+#
+# pmlogrewrite configuration for migrating archives containing old
+# 32 bit rpm values to the 64 bit variants, matching up
+# with changes in the metadata supplied by the PMDA.
+#
+
+metric 123.1.10 { type -> U64 }
diff --git a/src/pmdas/rpm/pmns b/src/pmdas/rpm/pmns
new file mode 100644
index 0000000..84aa5b3
--- /dev/null
+++ b/src/pmdas/rpm/pmns
@@ -0,0 +1,38 @@
+rpm {
+ arch RPM:1:0
+ buildhost RPM:1:1
+ buildtime RPM:1:2
+ description RPM:1:3
+ epoch RPM:1:4
+ group RPM:1:5
+ installtime RPM:1:6
+ license RPM:1:7
+ packager RPM:1:8
+ release RPM:1:9
+ size RPM:1:10
+ sourcerpm RPM:1:11
+ summary RPM:1:12
+ url RPM:1:13
+ vendor RPM:1:14
+ version RPM:1:15
+ name RPM:1:16
+ refresh
+ datasize RPM:0:4
+ total
+}
+
+rpm.refresh {
+ count RPM:0:0
+ time
+}
+
+rpm.refresh.time {
+ user RPM:0:1
+ sys RPM:0:2
+ elapsed RPM:0:3
+}
+
+rpm.total {
+ count RPM:2:0
+ bytes RPM:2:1
+}
diff --git a/src/pmdas/rpm/root b/src/pmdas/rpm/root
new file mode 100644
index 0000000..fb869d5
--- /dev/null
+++ b/src/pmdas/rpm/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { rpm }
+
+#include "pmns"
+
diff --git a/src/pmdas/rpm/rpm.c b/src/pmdas/rpm/rpm.c
new file mode 100644
index 0000000..348d9c1
--- /dev/null
+++ b/src/pmdas/rpm/rpm.c
@@ -0,0 +1,704 @@
+/*
+ * RPM Package Manager PMDA
+ *
+ * Copyright (c) 2013-2014 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 <sys/stat.h>
+#include <pthread.h>
+#include <search.h>
+#include <sys/inotify.h>
+#include <rpm/rpmlib.h>
+#include <rpm/header.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmdb.h>
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <pcp/pmda.h>
+#include "domain.h"
+#include "timer.h"
+#include "rpm.h"
+
+static pmdaIndom indomtab[] = {
+ {RPM_INDOM, 0, NULL},
+ {CACHE_INDOM, 1, NULL},
+ {STRINGS_INDOM, 2, NULL},
+};
+
+static pmdaMetric metrictab[] = {
+ /* PMDA internals metrics - timing, count of refreshes, memory */
+ { NULL, { PMDA_PMID(0, REFRESH_COUNT_ID), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE)}},
+ { NULL, { PMDA_PMID(0, REFRESH_TIME_USER_ID), PM_TYPE_DOUBLE,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0)}},
+ { NULL, { PMDA_PMID(0, REFRESH_TIME_KERNEL_ID), PM_TYPE_DOUBLE,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0)}},
+ { NULL, { PMDA_PMID(0, REFRESH_TIME_ELAPSED_ID), PM_TYPE_DOUBLE,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0)}},
+ { NULL, { PMDA_PMID(0, DATASIZE_ID), PM_TYPE_U32,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0)}},
+
+ /* rpm package metrics */
+ { NULL, { PMDA_PMID(1, ARCH_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, BUILDHOST_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, BUILDTIME_ID), PM_TYPE_U32,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0)}},
+ { NULL, { PMDA_PMID(1, DESCRIPTION_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, EPOCH_ID), PM_TYPE_U32,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, GROUP_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, INSTALLTIME_ID), PM_TYPE_U32,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0)}},
+ { NULL, { PMDA_PMID(1, LICENSE_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, PACKAGER_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, RELEASE_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, SIZE_ID), PM_TYPE_U64,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0)}},
+ { NULL, { PMDA_PMID(1, SOURCERPM_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, SUMMARY_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, URL_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, VENDOR_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, VERSION_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, NAME_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+ /* cumulative rpm metrics - total package count, size */
+ { NULL, { PMDA_PMID(2, TOTAL_COUNT_ID), PM_TYPE_U32,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE)}},
+ { NULL, { PMDA_PMID(2, TOTAL_BYTES_ID), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0)}},
+};
+
+static pthread_t inotify_thread; /* runs all librpm queries, esp. when the rpmdb changes */
+static unsigned long long numrefresh; /* updated by background thread, protected by indom_mutex */
+static unsigned long long packagesize; /* sum of sizes of all packages */
+static unsigned long numpackages; /* total count for all packages */
+
+static pthread_mutex_t indom_mutex;
+
+static int isDSO = 1; /* invoked as shlib or daemon */
+static char *username;
+static char *dbpath = "/var/lib/rpm/Packages";
+
+static pmInDom
+INDOM(int serial)
+{
+ return indomtab[serial].it_indom;
+}
+
+static char *
+dict_lookup(int index)
+{
+ char *value;
+ pmInDom dict = INDOM(STRINGS_INDOM);
+
+ if (pmdaCacheLookup(dict, index, &value, NULL) == PMDA_CACHE_ACTIVE)
+ return value;
+ return "";
+}
+
+static int
+dict_insert(const char *string)
+{
+ pmInDom dict = INDOM(STRINGS_INDOM);
+ if (!string)
+ string = "";
+ return pmdaCacheStore(dict, PMDA_CACHE_ADD, string, NULL);
+}
+
+static int
+rpm_fetch_pmda(int item, pmAtomValue *atom)
+{
+ int sts = PMDA_FETCH_STATIC;
+ unsigned long datasize;
+
+ switch (item) {
+ case REFRESH_COUNT_ID: /* rpm.refresh.count */
+ atom->ull = numrefresh; /* XXX: unlocked */
+ break;
+ case REFRESH_TIME_USER_ID: /* rpm.refresh.time.user */
+ atom->d = get_user_timer();
+ break;
+ case REFRESH_TIME_KERNEL_ID: /* rpm.refresh.time.kernel */
+ atom->d = get_kernel_timer();
+ break;
+ case REFRESH_TIME_ELAPSED_ID: /* rpm.refresh.time.elapsed */
+ atom->d = get_elapsed_timer();
+ break;
+ case DATASIZE_ID: /* rpm.datasize */
+ __pmProcessDataSize(&datasize);
+ atom->ul = datasize;
+ break;
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ return sts;
+}
+
+static int
+rpm_fetch_package(int item, unsigned int inst, pmAtomValue *atom)
+{
+ package *p;
+ char *name;
+ int sts;
+
+ sts = pmdaCacheLookup(INDOM(RPM_INDOM), inst, &name, (void **)&p);
+ if (sts < 0 || sts == PMDA_CACHE_INACTIVE)
+ return PM_ERR_INST;
+
+ sts = PMDA_FETCH_STATIC;
+ switch (item) {
+ case ARCH_ID:
+ atom->cp = dict_lookup(p->values.arch);
+ break;
+ case BUILDHOST_ID:
+ atom->cp = dict_lookup(p->values.buildhost);
+ break;
+ case BUILDTIME_ID:
+ atom->ul = p->values.buildtime;
+ break;
+ case DESCRIPTION_ID:
+ atom->cp = dict_lookup(p->values.description);
+ break;
+ case EPOCH_ID:
+ atom->ul = p->values.epoch;
+ break;
+ case GROUP_ID:
+ atom->cp = dict_lookup(p->values.group);
+ break;
+ case INSTALLTIME_ID:
+ atom->ul = p->values.installtime;
+ break;
+ case LICENSE_ID:
+ atom->cp = dict_lookup(p->values.license);
+ break;
+ case PACKAGER_ID:
+ atom->cp = dict_lookup(p->values.packager);
+ break;
+ case RELEASE_ID:
+ atom->cp = dict_lookup(p->values.release);
+ break;
+ case SIZE_ID:
+ atom->ull = p->values.longsize;
+ break;
+ case SOURCERPM_ID:
+ atom->cp = dict_lookup(p->values.sourcerpm);
+ break;
+ case SUMMARY_ID:
+ atom->cp = dict_lookup(p->values.summary);
+ break;
+ case URL_ID:
+ atom->cp = dict_lookup(p->values.url);
+ break;
+ case VENDOR_ID:
+ atom->cp = dict_lookup(p->values.vendor);
+ break;
+ case VERSION_ID:
+ atom->cp = dict_lookup(p->values.version);
+ break;
+ case NAME_ID:
+ atom->cp = dict_lookup(p->values.name);
+ break;
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ return sts;
+}
+
+static int
+rpm_fetch_totals(int item, pmAtomValue *atom)
+{
+ int sts = PMDA_FETCH_STATIC;
+
+ switch (item) {
+ case TOTAL_COUNT_ID: /* rpm.total.count */
+ atom->ul = numpackages;
+ break;
+ case TOTAL_BYTES_ID: /* rpm.total.bytes */
+ atom->ull = packagesize;
+ break;
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ return sts;
+}
+
+static int
+rpm_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *) &mdesc->m_desc.pmid;
+ int sts;
+
+ pthread_mutex_lock(&indom_mutex);
+ switch (idp->cluster) {
+ case 0:
+ if (inst != PM_IN_NULL)
+ sts = PM_ERR_INST;
+ else
+ sts = rpm_fetch_pmda(idp->item, atom);
+ break;
+ case 1:
+ sts = rpm_fetch_package(idp->item, inst, atom);
+ break;
+ case 2:
+ sts = rpm_fetch_totals(idp->item, atom);
+ break;
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ pthread_mutex_unlock(&indom_mutex);
+ return sts;
+}
+
+/*
+ * Sync the active rpm package instances with the reference database
+ * maintained by the background threads.
+ */
+static void
+rpm_indom_refresh(unsigned long long refresh)
+{
+ pmInDom rpmdb, cache;
+ package *p;
+ char *name;
+ int sts;
+
+ rpmdb = INDOM(RPM_INDOM);
+ cache = INDOM(CACHE_INDOM);
+
+ pmdaCacheOp(rpmdb, PMDA_CACHE_INACTIVE);
+
+ pthread_mutex_lock(&indom_mutex);
+ for (pmdaCacheOp(cache, PMDA_CACHE_WALK_REWIND);;) {
+ if ((sts = pmdaCacheOp(cache, PMDA_CACHE_WALK_NEXT)) < 0)
+ break;
+ if ((pmdaCacheLookup(cache, sts, &name, (void **)&p) < 0) || !p)
+ continue;
+ if (p->refresh < refresh)
+ continue;
+ pmdaCacheStore(rpmdb, PMDA_CACHE_ADD, name, (void *)p);
+ }
+ pthread_mutex_unlock(&indom_mutex);
+}
+
+/*
+ * Sync up with the (initial) indom loading thread
+ */
+static int
+notready(pmdaExt *pmda)
+{
+ unsigned iterations = 0;
+
+ __pmSendError(pmda->e_outfd, FROM_ANON, PM_ERR_PMDANOTREADY);
+
+ /*
+ * We need to wait for at least the initial rpm_update_cache()
+ * cycle to have finished. We could use a pthread condition
+ * variable, except that those have timing constraints on
+ * wait-precede-signal that we cannot enforce. So we poll.
+ */
+ while (1) {
+ unsigned long long refresh;
+
+ pthread_mutex_lock(&indom_mutex);
+ refresh = numrefresh;
+ pthread_mutex_unlock(&indom_mutex);
+
+ if (refresh > 0)
+ break;
+
+ if (iterations++ > 30) { /* Complain every 30 seconds. */
+ __pmNotifyErr(LOG_WARNING, "notready waited too long");
+ iterations = 0; /* XXX: or exit? */
+ }
+ sleep(1);
+ }
+
+ return PM_ERR_PMDAREADY;
+}
+
+/*
+ * Called once for each pmFetch(3) operation
+ */
+static int
+rpm_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ unsigned long long refresh;
+
+ pthread_mutex_lock(&indom_mutex);
+ refresh = numrefresh;
+ pthread_mutex_unlock(&indom_mutex);
+
+ if (refresh == 0)
+ return notready(pmda);
+ rpm_indom_refresh(refresh);
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+/*
+ * Called once for each pmGetInDom(3) operation
+ */
+static int
+rpm_instance(pmInDom id, int i, char *name, __pmInResult **in, pmdaExt *pmda)
+{
+ unsigned long long refresh;
+
+ pthread_mutex_lock(&indom_mutex);
+ refresh = numrefresh;
+ pthread_mutex_unlock(&indom_mutex);
+
+ if (refresh == 0)
+ return notready(pmda);
+ rpm_indom_refresh(refresh);
+ return pmdaInstance(id, i, name, in, pmda);
+}
+
+static const char *
+rpm_extract_string(rpmtd td, Header h, int tag)
+{
+ headerGet(h, tag, td, HEADERGET_EXT | HEADERGET_MINMEM);
+ /*
+ * RPM_STRING_ARRAY_TYPE being the alternative, e.g. filenames
+ * (which we never expect to see, for the metrics we export).
+ */
+ if (td->type == RPM_STRING_ARRAY_TYPE)
+ __pmNotifyErr(LOG_ERR,
+ "rpm_extract_string: unexpected string array: %d", tag);
+
+ return rpmtdGetString(td);
+}
+
+static __uint64_t
+rpm_extract_value(rpmtd td, Header h, int tag)
+{
+ __uint64_t value;
+
+ headerGet(h, tag, td, HEADERGET_EXT | HEADERGET_MINMEM);
+ switch (td->type) {
+ case RPM_INT8_TYPE:
+ value = ((char *)(td->data))[0];
+ break;
+ case RPM_INT16_TYPE:
+ value = ((short *)(td->data))[0];
+ break;
+ case RPM_INT32_TYPE:
+ value = ((int *)(td->data))[0];
+ break;
+ case RPM_INT64_TYPE:
+ value = ((long long *)(td->data))[0];
+ break;
+ default:
+ value = 0;
+ break;
+ }
+ return value;
+}
+
+static void
+rpm_extract_metadata(const char *name, rpmtd td, Header h, metadata *m)
+{
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_INFO, "updating package %s metadata", name);
+
+ m->name = dict_insert(rpm_extract_string(td, h, RPMTAG_NAME));
+ m->arch = dict_insert(rpm_extract_string(td, h, RPMTAG_ARCH));
+ m->buildhost = dict_insert(rpm_extract_string(td, h, RPMTAG_BUILDHOST));
+ m->buildtime = rpm_extract_value(td, h, RPMTAG_BUILDTIME);
+ m->description = dict_insert(rpm_extract_string(td, h, RPMTAG_DESCRIPTION));
+ m->epoch = rpm_extract_value(td, h, RPMTAG_EPOCH);
+ m->group = dict_insert(rpm_extract_string(td, h, RPMTAG_GROUP));
+ m->installtime = rpm_extract_value(td, h, RPMTAG_INSTALLTIME);
+ m->license = dict_insert(rpm_extract_string(td, h, RPMTAG_LICENSE));
+ m->packager = dict_insert(rpm_extract_string(td, h, RPMTAG_PACKAGER));
+ m->release = dict_insert(rpm_extract_string(td, h, RPMTAG_RELEASE));
+ m->longsize = rpm_extract_value(td, h, RPMTAG_LONGSIZE);
+ m->sourcerpm = dict_insert(rpm_extract_string(td, h, RPMTAG_SOURCERPM));
+ m->summary = dict_insert(rpm_extract_string(td, h, RPMTAG_SUMMARY));
+ m->url = dict_insert(rpm_extract_string(td, h, RPMTAG_URL));
+ m->vendor = dict_insert(rpm_extract_string(td, h, RPMTAG_VENDOR));
+ m->version = dict_insert(rpm_extract_string(td, h, RPMTAG_VERSION));
+}
+
+/*
+ * Refresh the RPM package names and values in the cache.
+ * This is to be only ever invoked from a single thread.
+ */
+void *
+rpm_update_cache(void *ptr)
+{
+ rpmtd td;
+ rpmts ts;
+ Header h;
+ rpmdbMatchIterator mi;
+ unsigned long long refresh;
+ unsigned long long totalsize = 0;
+ unsigned long packages = 0;
+ static int rpmReadConfigFiles_p = 0;
+
+ pthread_mutex_lock(&indom_mutex);
+ start_timing();
+ refresh = numrefresh + 1; /* current iteration */
+ pthread_mutex_unlock(&indom_mutex);
+
+ /*
+ * It appears unnecessary to check the return value from these functions,
+ * since the only (?) thing that can fail is memory allocation, which
+ * rpmlib internally maps to an exit(1).
+ */
+ td = rpmtdNew();
+ ts = rpmtsCreate();
+
+ if (rpmReadConfigFiles_p == 0) {
+ int sts = rpmReadConfigFiles(NULL, NULL);
+ if (sts == -1)
+ __pmNotifyErr(LOG_WARNING, "rpm_update_cache: rpmReadConfigFiles failed: %d", sts);
+ rpmReadConfigFiles_p = 1;
+ }
+
+ /* Iterate through the entire list of RPMs, extract names and values */
+ mi = rpmtsInitIterator(ts, RPMDBI_PACKAGES, NULL, 0);
+ while ((h = rpmdbNextIterator(mi)) != NULL) {
+ headerGet(h, RPMTAG_NEVRA, td, HEADERGET_EXT | HEADERGET_MINMEM);
+ const char *name = rpmtdGetString(td);
+ metadata meta;
+ package *pp = NULL;
+ int sts, err = 0;
+
+ /* extract an on-stack copy of the package metadata, may do I/O */
+ rpm_extract_metadata(name, td, h, &meta);
+
+ /* update cumulative counts */
+ totalsize += meta.longsize;
+ packages++;
+
+ /* we now have our data and cannot need more I/O; lock and load */
+ pthread_mutex_lock(&indom_mutex);
+ sts = pmdaCacheLookupName(INDOM(CACHE_INDOM), name, NULL, (void **)&pp);
+ if (sts == PM_ERR_INST || (sts >= 0 && pp == NULL)) {
+ /* allocate space for new package entry for the cache */
+ if ((pp = calloc(1, sizeof(package))) == NULL)
+ err = 1;
+ } else if (sts < 0) {
+ err = 1;
+ }
+
+ if (!err) {
+ /* update values in cache entry for this package (locked) */
+ pp->refresh = refresh;
+ memcpy(&pp->values, &meta, sizeof(metadata));
+ pmdaCacheStore(INDOM(CACHE_INDOM), PMDA_CACHE_ADD, name, (void *)pp);
+ } else {
+ /* ensure the logfile isn't spammed over and over */
+ static int cache_err = 0;
+ if (cache_err++ < 10) {
+ fprintf(stderr, "rpm_refresh_cache: "
+ "pmdaCacheLookupName(%s, %s, ... %p) failed: %s\n",
+ pmInDomStr(INDOM(CACHE_INDOM)), name, pp, pmErrStr(sts));
+ }
+ }
+ pthread_mutex_unlock(&indom_mutex);
+ }
+
+ rpmdbFreeIterator(mi);
+ rpmtsFree(ts);
+
+ pthread_mutex_lock(&indom_mutex);
+ stop_timing();
+ numrefresh = refresh; /* current iteration complete */
+ packagesize = totalsize;
+ numpackages = packages;
+ pthread_mutex_unlock(&indom_mutex);
+ return NULL;
+}
+
+/*
+ * Notice when the rpm database changes and reload the instances.
+ */
+void *
+rpm_inotify(void *ptr)
+{
+ char buffer[EVENT_BUF_LEN]; /* space for lots of events */
+ int fd;
+ int sts;
+
+ /* Update it the first time. */
+ rpm_update_cache(ptr);
+
+ /*
+ * By this time, the global refresh counter should be >= 1, even
+ * if some rpm* or other api failure occurred.
+ */
+ fd = inotify_init();
+ if (fd < 0) {
+ __pmNotifyErr(LOG_ERR, "rpm_inotify: failed to create inotify fd");
+ return NULL;
+ }
+
+ sts = inotify_add_watch(fd, dbpath, IN_CLOSE_WRITE);
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "rpm_inotify: failed to inotify-watch dbpath %s", dbpath);
+ close(fd);
+ return NULL;
+ }
+
+ while (1) {
+ int read_count;
+
+ /* Wait for changes in the rpm database */
+ read_count = read(fd, buffer, EVENT_BUF_LEN);
+ if (pmDebug & DBG_TRACE_APPL1)
+ __pmNotifyErr(LOG_INFO, "rpm_inotify: read_count=%d", read_count);
+
+ /*
+ * No need to check the contents of the buffer; having
+ * received an event at all indicates need to refresh.
+ */
+ if (read_count <= 0) {
+ __pmNotifyErr(LOG_WARNING, "rpm_inotify: read_count=%d", read_count);
+ continue;
+ }
+
+ rpm_update_cache(ptr);
+
+ if (pmDebug & DBG_TRACE_APPL1)
+ __pmNotifyErr(LOG_INFO, "rpm_inotify: refresh done");
+ }
+
+ /* NOTREACHED */
+ return NULL;
+}
+
+/*
+ * Initialize the daemon/.so agent.
+ */
+
+void
+__PMDA_INIT_CALL
+rpm_init(pmdaInterface * dp)
+{
+ if (isDSO) {
+ int sep = __pmPathSeparator();
+ char helppath[MAXPATHLEN];
+
+ snprintf(helppath, sizeof(helppath), "%s%c" "rpm" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_5, "rpm DSO", helppath);
+ }
+ else {
+ __pmSetProcessIdentity(username);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.any.fetch = rpm_fetch;
+ dp->version.any.instance = rpm_instance;
+ pmdaSetFetchCallBack(dp, rpm_fetchCallBack);
+
+ pmdaInit(dp, indomtab, sizeof(indomtab) / sizeof(indomtab[0]),
+ metrictab, sizeof(metrictab) / sizeof(metrictab[0]));
+
+ pmdaCacheOp(INDOM(STRINGS_INDOM), PMDA_CACHE_STRINGS);
+
+ pthread_mutex_init(&indom_mutex, NULL);
+ /* Monitor changes to the rpm database */
+ pthread_create(&inotify_thread, NULL, rpm_inotify, NULL);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: %s [options]\n\n", pmProgname);
+ fprintf(stderr, "Options:\n"
+ " -C parse the RPM database, and exit\n"
+ " -d domain use domain (numeric) for metrics domain of PMDA\n"
+ " -l logfile write log into logfile rather than using default log name\n"
+ " -r path path to directory containing RPM database (default %s)\n"
+ " -U username user account to run under (default \"pcp\")\n"
+ "\nExactly one of the following options may appear:\n"
+ " -i port expect PMCD to connect on given inet port (number or name)\n"
+ " -p expect PMCD to supply stdin/stdout (pipe)\n"
+ " -u socket expect PMCD to connect on given unix domain socket\n"
+ " -6 port expect PMCD to connect on given ipv6 port (number or name)\n",
+ dbpath);
+ exit(1);
+}
+
+/*
+ * Set up the agent if running as a daemon.
+ */
+
+int
+main(int argc, char **argv)
+{
+ int c, err = 0;
+ int Cflag = 0, sep = __pmPathSeparator();
+ pmdaInterface dispatch;
+ char helppath[MAXPATHLEN];
+
+ isDSO = 0;
+ __pmSetProgname(argv[0]);
+ __pmProcessDataSize(NULL);
+ __pmGetUsername(&username);
+
+ snprintf(helppath, sizeof(helppath), "%s%c" "rpm" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_5, pmProgname, RPM,
+ "rpm.log", helppath);
+
+ while ((c =
+ pmdaGetOpt(argc, argv, "CD:d:i:l:pr:u:6:U:?", &dispatch,
+ &err)) != EOF) {
+ switch (c) {
+ case 'C':
+ Cflag++;
+ break;
+ case 'U':
+ username = optarg;
+ break;
+ case 'r':
+ dbpath = optarg;
+ break;
+ default:
+ err++;
+ }
+ }
+ if (err)
+ usage();
+
+ pmdaOpenLog(&dispatch);
+ rpm_init(&dispatch);
+ if (Cflag) {
+ rpm_update_cache(NULL);
+ exit(0);
+ }
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+
+ exit(0);
+}
diff --git a/src/pmdas/rpm/rpm.h b/src/pmdas/rpm/rpm.h
new file mode 100644
index 0000000..117f197
--- /dev/null
+++ b/src/pmdas/rpm/rpm.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2013-2014 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.
+ */
+#ifndef RPM_H
+#define RPM_H
+
+/*
+ * Instance domain handling
+ */
+enum {
+ RPM_INDOM = 0, /* active RPM packages */
+ CACHE_INDOM = 1, /* pseudo-indom for refreshing */
+ STRINGS_INDOM = 2, /* pseudo-indom for string sharing */
+};
+
+/*
+ * Metrics describing internals of pmdarpm operation (Cluster 0)
+ */
+enum {
+ REFRESH_COUNT_ID = 0,
+ REFRESH_TIME_USER_ID = 1,
+ REFRESH_TIME_KERNEL_ID = 2,
+ REFRESH_TIME_ELAPSED_ID = 3,
+ DATASIZE_ID = 4,
+};
+
+/*
+ * List of metrics corresponding to rpm --querytags (Cluster 1)
+ */
+enum {
+ ARCH_ID = 0,
+ BUILDHOST_ID = 1,
+ BUILDTIME_ID = 2,
+ DESCRIPTION_ID = 3,
+ EPOCH_ID = 4,
+ GROUP_ID = 5,
+ INSTALLTIME_ID = 6,
+ LICENSE_ID = 7,
+ PACKAGER_ID = 8,
+ RELEASE_ID = 9,
+ SIZE_ID = 10,
+ SOURCERPM_ID = 11,
+ SUMMARY_ID = 12,
+ URL_ID = 13,
+ VENDOR_ID = 14,
+ VERSION_ID = 15,
+ NAME_ID = 16,
+};
+
+/*
+ * Metrics describing cumulative pmdarpm totals (Cluster 2)
+ */
+enum {
+ TOTAL_COUNT_ID = 0,
+ TOTAL_BYTES_ID = 1,
+};
+
+/*
+ * Package metadata stored for each installed RPM
+ *
+ * A "refresh" count is stored to indicate whether this entry
+ * is out of date with respect to the global "refresh" count.
+ * If its value is greater-than-or-equal-to a global refresh
+ * count, the entry is current - otherwise it is out-of-date
+ * and must not be reported in the active instance domain.
+ *
+ * Note that many of the structure entries (below) are string
+ * dictionary keys (int), allowing sharing of the memory used
+ * to hold the values. It also further reduces the footprint
+ * on 64 bit systems, instead of storing 64bit pointers.
+ */
+
+typedef struct metadata {
+ int name;
+ int arch;
+ int buildhost;
+ int buildtime;
+ int description;
+ int epoch;
+ int group;
+ int installtime;
+ int license;
+ int packager;
+ int release;
+ __uint64_t longsize;
+ int sourcerpm;
+ int summary;
+ int url;
+ int vendor;
+ int version;
+} metadata;
+
+typedef struct package {
+ __uint64_t refresh;
+ metadata values;
+} package;
+
+#define EVENT_SIZE ( sizeof (struct inotify_event) )
+#define EVENT_BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )
+
+#endif /* RPM_H */
diff --git a/src/pmdas/rpm/timer.c b/src/pmdas/rpm/timer.c
new file mode 100644
index 0000000..89cd8aa
--- /dev/null
+++ b/src/pmdas/rpm/timer.c
@@ -0,0 +1,45 @@
+/*
+ * 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 <pmapi.h>
+#include <impl.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+static struct rusage start_rsrc, final_rsrc;
+static struct timeval start_time, final_time;
+static double user, kernel, elapsed;
+
+double get_user_timer() { return user; }
+double get_kernel_timer() { return kernel; }
+double get_elapsed_timer() { return elapsed; }
+
+void
+start_timing(void)
+{
+ getrusage(RUSAGE_SELF, &start_rsrc);
+ gettimeofday(&start_time, NULL);
+}
+
+void
+stop_timing(void)
+{
+ gettimeofday(&final_time, NULL);
+ getrusage(RUSAGE_SELF, &final_rsrc);
+
+ /* accumulate the totals as we go */
+ user += __pmtimevalSub(&final_rsrc.ru_utime, &start_rsrc.ru_utime);
+ kernel += __pmtimevalSub(&final_rsrc.ru_stime, &start_rsrc.ru_stime);
+ elapsed += __pmtimevalSub(&final_time, &start_time);
+}
diff --git a/src/pmdas/rpm/timer.h b/src/pmdas/rpm/timer.h
new file mode 100644
index 0000000..529ca33
--- /dev/null
+++ b/src/pmdas/rpm/timer.h
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+#ifndef TIMER_H
+#define TIMER_H
+
+extern void start_timing(void);
+extern void stop_timing(void);
+
+extern double get_user_timer(void);
+extern double get_kernel_timer(void);
+extern double get_elapsed_timer(void);
+
+#endif /* TIMER_H */