summaryrefslogtreecommitdiff
path: root/src/pmdas/bash
diff options
context:
space:
mode:
Diffstat (limited to 'src/pmdas/bash')
-rw-r--r--src/pmdas/bash/GNUmakefile62
-rw-r--r--src/pmdas/bash/Install36
-rw-r--r--src/pmdas/bash/README50
-rw-r--r--src/pmdas/bash/Remove23
-rw-r--r--src/pmdas/bash/bash.c472
-rwxr-xr-xsrc/pmdas/bash/bashproc.sh64
-rw-r--r--src/pmdas/bash/event.c434
-rw-r--r--src/pmdas/bash/event.h65
-rw-r--r--src/pmdas/bash/help48
-rwxr-xr-xsrc/pmdas/bash/pcp.sh30
-rw-r--r--src/pmdas/bash/pmns36
-rw-r--r--src/pmdas/bash/root10
-rwxr-xr-xsrc/pmdas/bash/test-child.sh39
-rwxr-xr-xsrc/pmdas/bash/test-trace.sh41
-rw-r--r--src/pmdas/bash/util.c84
15 files changed, 1494 insertions, 0 deletions
diff --git a/src/pmdas/bash/GNUmakefile b/src/pmdas/bash/GNUmakefile
new file mode 100644
index 0000000..0143cac
--- /dev/null
+++ b/src/pmdas/bash/GNUmakefile
@@ -0,0 +1,62 @@
+#
+# Copyright (c) 2012 Nathan Scott. All Rights Reversed.
+#
+# 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 = bash
+DOMAIN = BASH
+
+TARGETS = $(IAM)$(EXECSUFFIX)
+LLDLIBS = $(PCP_PMDALIB)
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LDIRT = domain.h *.log *.dir *.pag so_locations $(TARGETS)
+
+CFILES = event.c bash.c util.c
+HFILES = event.h
+SCRIPTS = bashproc.sh pcp.sh
+SAMPLES = test-child.sh test-trace.sh
+LSRCFILES = Install Remove pmns help root $(SCRIPTS) $(SAMPLES)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "mingw"
+build-me:
+install:
+else
+build-me: $(TARGETS)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 $(IAM) $(PMDADIR)/pmda$(IAM)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 root help pmns $(PMDADIR)
+ $(INSTALL) -m 644 domain.h $(PMDADIR)/domain.h
+ $(INSTALL) -m 644 bashproc.sh $(PCP_SHARE_DIR)/lib/bashproc.sh
+ $(INSTALL) -m 644 pcp.sh $(PCP_ETC_DIR)/pcp.sh
+endif
+
+$(IAM)$(EXECSUFFIX): $(OBJECTS)
+
+bash.o: domain.h
+event.o bash.o util.o: event.h
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pmdas/bash/Install b/src/pmdas/bash/Install
new file mode 100644
index 0000000..5cb784d
--- /dev/null
+++ b/src/pmdas/bash/Install
@@ -0,0 +1,36 @@
+#! /bin/sh
+#
+# Copyright (c) 2012 Nathan Scott. All rights reversed.
+#
+# 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 bash PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=bash
+dso_opt=true
+socket_opt=true
+socket_inet_def=2082
+forced_restart=false
+pmda_interface=5
+
+if [ ! -e "$PCP_TMP_DIR/pmdabash" ]
+then
+ echo "creating $PCP_TMP_DIR/pmdabash"
+ mkdir -p -m 1777 "$PCP_TMP_DIR/pmdabash"
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/bash/README b/src/pmdas/bash/README
new file mode 100644
index 0000000..58ca47f
--- /dev/null
+++ b/src/pmdas/bash/README
@@ -0,0 +1,50 @@
+Bash PMDA
+=========
+
+This PMDA exports information from bash (GNU Bourne-Again SHell)
+processes which are setup to send trace information to PCP. This
+requires functionality present in bash version four or later.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT bash
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/bash
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options -- everything else is automated.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/bash
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/bash.log) should be checked for any warnings
+ or errors.
diff --git a/src/pmdas/bash/Remove b/src/pmdas/bash/Remove
new file mode 100644
index 0000000..9f87da3
--- /dev/null
+++ b/src/pmdas/bash/Remove
@@ -0,0 +1,23 @@
+#! /bin/sh
+#
+# Copyright (c) 2012 Nathan Scott. All rights reversed.
+#
+# 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 bash PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+iam=bash
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/bash/bash.c b/src/pmdas/bash/bash.c
new file mode 100644
index 0000000..e25ef2e
--- /dev/null
+++ b/src/pmdas/bash/bash.c
@@ -0,0 +1,472 @@
+/*
+ * Bash -x trace PMDA
+ *
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2012 Nathan Scott.
+ *
+ * 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 "domain.h"
+#include "event.h"
+#include "pmda.h"
+
+#define DEFAULT_MAXMEM (2 * 1024 * 1024) /* 2 megabytes */
+long bash_maxmem;
+
+static int bash_interval_expired;
+static struct timeval bash_interval = { 1, 0 };
+
+static char *username;
+
+#define BASH_INDOM 0
+static pmdaIndom indoms[] = { { BASH_INDOM, 0, NULL } };
+#define INDOM_COUNT (sizeof(indoms)/sizeof(indoms[0]))
+
+static pmdaMetric metrics[] = {
+
+#define bash_xtrace_numclients 0
+ { NULL, { PMDA_PMID(0,bash_xtrace_numclients), PM_TYPE_U32,
+ BASH_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+#define bash_xtrace_maxmem 1
+ { NULL, { PMDA_PMID(0,bash_xtrace_maxmem), PM_TYPE_U64,
+ BASH_INDOM, PM_SEM_DISCRETE, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+#define bash_xtrace_queuemem 2
+ { NULL, { PMDA_PMID(0,bash_xtrace_queuemem), PM_TYPE_U64,
+ BASH_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+#define bash_xtrace_count 3
+ { NULL, { PMDA_PMID(0,bash_xtrace_count), PM_TYPE_U32,
+ BASH_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+#define bash_xtrace_records 4
+ { NULL, { PMDA_PMID(0,bash_xtrace_records), PM_TYPE_EVENT,
+ BASH_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+#define bash_xtrace_parameters_pid 5
+ { NULL, { PMDA_PMID(0,bash_xtrace_parameters_pid), PM_TYPE_U32,
+ PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+#define bash_xtrace_parameters_parent 6
+ { NULL, { PMDA_PMID(0,bash_xtrace_parameters_parent), PM_TYPE_U32,
+ PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+#define bash_xtrace_parameters_lineno 7
+ { NULL, { PMDA_PMID(0,bash_xtrace_parameters_lineno), PM_TYPE_U32,
+ PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+#define bash_xtrace_parameters_function 8
+ { NULL, { PMDA_PMID(0,bash_xtrace_parameters_function), PM_TYPE_STRING,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+#define bash_xtrace_parameters_command 9
+ { NULL, { PMDA_PMID(0,bash_xtrace_parameters_command), PM_TYPE_STRING,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+};
+#define METRIC_COUNT (sizeof(metrics)/sizeof(metrics[0]))
+
+static pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ { "memory", 1, 'm', "SIZE", "maximum memory used per logfile (default 2MB)" },
+ { "interval", 1, 's', "DELTA", "default delay between iterations (default 1 sec)" },
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+
+static pmdaOptions opts = {
+ .short_options = "D:d:l:m:s:U:?",
+ .long_options = longopts,
+};
+
+
+static void
+check_processes(int context)
+{
+ pmdaEventNewClient(context);
+ event_refresh(indoms[BASH_INDOM].it_indom);
+}
+
+static int
+bash_instance(pmInDom indom, int inst, char *name,
+ __pmInResult **result, pmdaExt *pmda)
+{
+ check_processes(pmda->e_context);
+ return pmdaInstance(indom, inst, name, result, pmda);
+}
+
+static int
+bash_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ check_processes(pmda->e_context);
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+static int
+bash_trace_parser(bash_process_t *bash, bash_trace_t *trace,
+ struct timeval *timestamp, const char *buffer, size_t size)
+{
+ trace->flags = (PM_EVENT_FLAG_ID | PM_EVENT_FLAG_PARENT);
+
+ /* empty event inserted into queue to signal process has exited */
+ if (size <= 0) {
+ trace->flags |= PM_EVENT_FLAG_END;
+ if (fstat(bash->fd, &bash->stat) < 0 || !S_ISFIFO(bash->stat.st_mode))
+ memcpy(&trace->timestamp, timestamp, sizeof(*timestamp));
+ else
+ process_stat_timestamp(bash, &trace->timestamp);
+ close(bash->fd);
+ } else {
+ char *p = (char *)buffer, *end = (char *)buffer + size - 1;
+ int sz, time = -1;
+
+ /* version 1 format: time, line#, function, and command line */
+ p += extract_int(p, "time:", sizeof("time:")-1, &time);
+ p += extract_int(p, "line:", sizeof("line:")-1, &trace->line);
+ p += extract_str(p, end - p, "func:", sizeof("func:")-1,
+ trace->function, sizeof(trace->function));
+ sz = extract_cmd(p, end - p, "+", sizeof("+")-1,
+ trace->command, sizeof(trace->command));
+ if (sz <= 0) /* wierd trace - no command */
+ trace->command[0] = '\0';
+
+ if (strncmp(trace->command, "pcp_trace ", 10) == 0 ||
+ strncmp(trace->function, "pcp_trace", 10) == 0)
+ return 1; /* suppress tracing function, its white noise */
+
+ if (time != -1) { /* normal case */
+ trace->timestamp.tv_sec = bash->starttime.tv_sec + time;
+ trace->timestamp.tv_usec = bash->starttime.tv_usec;
+ } else { /* wierd trace */
+ memcpy(&trace->timestamp, timestamp, sizeof(*timestamp));
+ }
+
+ if (event_start(bash, &trace->timestamp))
+ trace->flags |= PM_EVENT_FLAG_START;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG,
+ "event parsed: flags: %x time: %d line: %d func: '%s' cmd: '%s'",
+ trace->flags, time, trace->line, trace->function, trace->command);
+ }
+ return 0;
+}
+
+static int
+bash_trace_decoder(int eventarray,
+ void *buffer, size_t size, struct timeval *timestamp, void *data)
+{
+ pmAtomValue atom;
+ bash_process_t *process = (bash_process_t *)data;
+ bash_trace_t trace = { 0 };
+ pmID pmid;
+ int sts, count = 0;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "bash_trace_decoder[%ld bytes]", (long)size);
+
+ if (bash_trace_parser(process, &trace, timestamp, (const char *)buffer, size))
+ return 0;
+
+ sts = pmdaEventAddRecord(eventarray, &trace.timestamp, trace.flags);
+ if (sts < 0)
+ return sts;
+
+ atom.ul = process->pid;
+ pmid = metrics[bash_xtrace_parameters_pid].m_desc.pmid;
+ sts = pmdaEventAddParam(eventarray, pmid, PM_TYPE_U32, &atom);
+ if (sts < 0)
+ return sts;
+ count++;
+
+ atom.ul = process->parent;
+ pmid = metrics[bash_xtrace_parameters_parent].m_desc.pmid;
+ sts = pmdaEventAddParam(eventarray, pmid, PM_TYPE_U32, &atom);
+ if (sts < 0)
+ return sts;
+ count++;
+
+ if (trace.line) {
+ atom.ul = trace.line;
+ pmid = metrics[bash_xtrace_parameters_lineno].m_desc.pmid;
+ sts = pmdaEventAddParam(eventarray, pmid, PM_TYPE_U32, &atom);
+ if (sts < 0)
+ return sts;
+ count++;
+ }
+
+ if (trace.function[0] != '\0') {
+ atom.cp = trace.function;
+ pmid = metrics[bash_xtrace_parameters_function].m_desc.pmid;
+ sts = pmdaEventAddParam(eventarray, pmid, PM_TYPE_STRING, &atom);
+ if (sts < 0)
+ return sts;
+ count++;
+ }
+
+ if (trace.command[0] != '\0') {
+ atom.cp = trace.command;
+ pmid = metrics[bash_xtrace_parameters_command].m_desc.pmid;
+ sts = pmdaEventAddParam(eventarray, pmid, PM_TYPE_STRING, &atom);
+ if (sts < 0)
+ return sts;
+ count++;
+ }
+
+ return count;
+}
+
+static int
+bash_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ bash_process_t *bp;
+
+ if (idp->cluster != 0)
+ return PM_ERR_PMID;
+
+ switch (idp->item) {
+ case bash_xtrace_maxmem:
+ atom->ull = (unsigned long long)bash_maxmem;
+ return PMDA_FETCH_STATIC;
+ case bash_xtrace_parameters_pid:
+ case bash_xtrace_parameters_parent:
+ case bash_xtrace_parameters_lineno:
+ case bash_xtrace_parameters_function:
+ case bash_xtrace_parameters_command:
+ return PMDA_FETCH_NOVALUES;
+ }
+
+ if (PMDA_CACHE_ACTIVE !=
+ pmdaCacheLookup(indoms[BASH_INDOM].it_indom, inst, NULL, (void **)&bp))
+ return PM_ERR_INST;
+
+ switch (idp->item) {
+ case bash_xtrace_numclients:
+ return pmdaEventQueueClients(bp->queueid, atom);
+ case bash_xtrace_queuemem:
+ return pmdaEventQueueMemory(bp->queueid, atom);
+ case bash_xtrace_count:
+ return pmdaEventQueueCounter(bp->queueid, atom);
+ case bash_xtrace_records:
+ return pmdaEventQueueRecords(bp->queueid, atom, pmdaGetContext(),
+ bash_trace_decoder, bp);
+ }
+
+ return PM_ERR_PMID;
+}
+
+static int
+bash_store_metric(pmValueSet *vsp, int context)
+{
+ __pmID_int *idp = (__pmID_int *)&vsp->pmid;
+ pmInDom processes = indoms[BASH_INDOM].it_indom;
+ int sts;
+
+ if (idp->cluster != 0 || idp->item != bash_xtrace_records)
+ return PM_ERR_PERMISSION;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "bash_store_metric walking bash set");
+
+ pmdaCacheOp(processes, PMDA_CACHE_WALK_REWIND);
+ while ((sts = pmdaCacheOp(processes, PMDA_CACHE_WALK_NEXT)) != -1) {
+ bash_process_t *bp;
+
+ if (!pmdaCacheLookup(processes, sts, NULL, (void **)&bp))
+ continue;
+ if ((sts = pmdaEventSetAccess(context, bp->queueid, 1)) < 0)
+ return sts;
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG,
+ "Access granted client=%d bash=%d queueid=%d",
+ context, bp->pid, bp->queueid);
+ }
+ return 0;
+}
+
+static int
+bash_store(pmResult *result, pmdaExt *pmda)
+{
+ int i, sts;
+ int context = pmda->e_context;
+
+ check_processes(context);
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "bash_store called (%d)", result->numpmid);
+ for (i = 0; i < result->numpmid; i++) {
+ pmValueSet *vsp = result->vset[i];
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "bash_store_metric called");
+ if ((sts = bash_store_metric(vsp, context)) < 0)
+ return sts;
+ }
+ return 0;
+}
+
+static void
+bash_end_contextCallBack(int context)
+{
+ pmdaEventEndClient(context);
+}
+
+static void
+timer_expired(int sig, void *ptr)
+{
+ bash_interval_expired = 1;
+}
+
+void
+bash_main(pmdaInterface *dispatch)
+{
+ fd_set fds, readyfds;
+ int maxfd, nready, pmcdfd;
+
+ pmcdfd = __pmdaInFd(dispatch);
+ maxfd = pmcdfd;
+
+ FD_ZERO(&fds);
+ FD_SET(pmcdfd, &fds);
+
+ /* arm interval timer */
+ if (__pmAFregister(&bash_interval, NULL, timer_expired) < 0) {
+ __pmNotifyErr(LOG_ERR, "registering event interval handler");
+ exit(1);
+ }
+
+ for (;;) {
+ memcpy(&readyfds, &fds, sizeof(readyfds));
+ nready = select(maxfd+1, &readyfds, NULL, NULL, NULL);
+ if (pmDebug & DBG_TRACE_APPL2)
+ __pmNotifyErr(LOG_DEBUG, "select: nready=%d interval=%d",
+ nready, bash_interval_expired);
+ if (nready < 0) {
+ if (neterror() != EINTR) {
+ __pmNotifyErr(LOG_ERR, "select failure: %s", netstrerror());
+ exit(1);
+ } else if (!bash_interval_expired) {
+ continue;
+ }
+ }
+
+ __pmAFblock();
+ if (nready > 0 && FD_ISSET(pmcdfd, &readyfds)) {
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "processing pmcd PDU [fd=%d]", pmcdfd);
+ if (__pmdaMainPDU(dispatch) < 0) {
+ __pmAFunblock();
+ exit(1); /* fatal if we lose pmcd */
+ }
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "completed pmcd PDU [fd=%d]", pmcdfd);
+ }
+ if (bash_interval_expired) {
+ bash_interval_expired = 0;
+ event_refresh(indoms[BASH_INDOM].it_indom);
+ }
+ __pmAFunblock();
+ }
+}
+
+void
+bash_init(pmdaInterface *dp)
+{
+ __pmSetProcessIdentity(username);
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.four.fetch = bash_fetch;
+ dp->version.four.store = bash_store;
+ dp->version.four.instance = bash_instance;
+ pmdaSetFetchCallBack(dp, bash_fetchCallBack);
+ pmdaSetEndContextCallBack(dp, bash_end_contextCallBack);
+ pmdaInit(dp, indoms, INDOM_COUNT, metrics, METRIC_COUNT);
+
+ event_init();
+}
+
+static void
+convertUnits(char **endnum, long *maxmem)
+{
+ switch ((int) **endnum) {
+ case 'b':
+ case 'B':
+ break;
+ case 'k':
+ case 'K':
+ *maxmem *= 1024;
+ break;
+ case 'm':
+ case 'M':
+ *maxmem *= 1024 * 1024;
+ break;
+ case 'g':
+ case 'G':
+ *maxmem *= 1024 * 1024 * 1024;
+ break;
+ }
+ (*endnum)++;
+}
+
+int
+main(int argc, char **argv)
+{
+ static char helppath[MAXPATHLEN];
+ char *endnum;
+ pmdaInterface desc;
+ long minmem;
+ int c, sep = __pmPathSeparator();
+
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ minmem = getpagesize();
+ bash_maxmem = (minmem > DEFAULT_MAXMEM) ? minmem : DEFAULT_MAXMEM;
+ snprintf(helppath, sizeof(helppath), "%s%c" "bash" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&desc, PMDA_INTERFACE_5, pmProgname, BASH, "bash.log", helppath);
+
+ while ((c = pmdaGetOptions(argc, argv, &opts, &desc)) != EOF) {
+ switch (c) {
+ case 'm':
+ bash_maxmem = strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0')
+ convertUnits(&endnum, &bash_maxmem);
+ if (*endnum != '\0' || bash_maxmem < minmem) {
+ pmprintf("%s: invalid max memory '%s' (min=%ld)\n",
+ pmProgname, opts.optarg, minmem);
+ opts.errors++;
+ }
+ break;
+
+ case 's':
+ if (pmParseInterval(opts.optarg, &bash_interval, &endnum) < 0) {
+ pmprintf("%s: -s requires a time interval: %s\n",
+ pmProgname, endnum);
+ free(endnum);
+ opts.errors++;
+ }
+ break;
+ }
+ }
+
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (opts.username)
+ username = opts.username;
+
+ pmdaOpenLog(&desc);
+ bash_init(&desc);
+ pmdaConnect(&desc);
+ bash_main(&desc);
+ exit(0);
+}
diff --git a/src/pmdas/bash/bashproc.sh b/src/pmdas/bash/bashproc.sh
new file mode 100755
index 0000000..4d867a7
--- /dev/null
+++ b/src/pmdas/bash/bashproc.sh
@@ -0,0 +1,64 @@
+# Common bash(1) procedures to be used in the Performance Co-Pilot
+# Bash PMDA trace instrumentation mechanism
+#
+# Copyright (c) 2012 Nathan Scott. 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_trace()
+{
+ command="$1"
+ shift
+
+ case "$command"
+ in
+ setup|init)
+ [ -z "${PCP_TMP_DIR}" ] && return 0 # incorrect call sequence
+ trap "pcp_trace off" 0
+ export PCP_TRACE_DIR="${PCP_TMP_DIR}/pmdabash"
+ export PCP_TRACE_HEADER="${PCP_TRACE_DIR}/.$$"
+ export PCP_TRACE_DATA="${PCP_TRACE_DIR}/$$"
+ ;;
+
+ stop|off)
+ exec 99>/dev/null
+ # unlink is performed by pmdabash to resolve race conditions
+ unset BASH_XTRACEFD PCP_TRACE_DIR PCP_TRACE_HEADER PCP_TRACE_DATA
+ ;;
+
+ start|on)
+ # series of sanity checks first
+ [ -n "${BASH_VERSION}" ] || return 0 # wrong shell
+ [ "${BASH_VERSINFO[0]}" -ge 4 ] || return 0 # no support
+ [ "${BASH_VERSINFO[0]}" -ne 4 -o "${BASH_VERSINFO[1]}" -ge 2 ] || \
+ return 0 # no support
+ [ -z "${PCP_TMP_DIR}" ] && return 0 # incorrect setup
+ [ -z "${PCP_TRACE_DIR}" ] && pcp_trace init $@ # not yet tracing
+ [ -d "${PCP_TRACE_DIR}" ] || return 0 # no pcp pmda yet
+
+ # is this the child of a traced shell?
+ [ -e /proc/self/fd/99 ] && pcp_trace init $@
+
+ trap "pcp_trace on" 13 # reset on sigpipe (consumer died)
+ printf -v PCP_START '%(%s)T' -2
+ mkfifo -m 600 "${PCP_TRACE_DATA}" 2>/dev/null || return 0
+ # header: version, command, parent, and start time
+ echo "version:1 ppid:${PPID} date:${PCP_START} + $@" \
+ > "${PCP_TRACE_HEADER}" || return 0
+ # setup link between xtrace & fifo
+ exec 99>"${PCP_TRACE_DATA}"
+ BASH_XTRACEFD=99 # magic bash environment variable
+ set -o xtrace # start tracing from here onward
+ # traces: time, line#, and (optionally) function
+ PS4='time:${SECONDS} line:${LINENO} func:${FUNCNAME[0]-} + '
+ ;;
+ esac
+}
diff --git a/src/pmdas/bash/event.c b/src/pmdas/bash/event.c
new file mode 100644
index 0000000..6129e5a
--- /dev/null
+++ b/src/pmdas/bash/event.c
@@ -0,0 +1,434 @@
+/*
+ * Event support for the bash tracing PMDA
+ *
+ * Copyright (c) 2012 Nathan Scott. All rights reversed.
+ *
+ * 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 <unistd.h>
+#include <ctype.h>
+#include "event.h"
+#include "pmda.h"
+
+static char *prefix = "pmdabash";
+static char *pcptmpdir; /* probably /var/tmp */
+static char pidpath[MAXPATHLEN];
+
+/*
+ * Extract time of creation of the trace files. Initially uses header file
+ * as a reference since its created initially and then never modified again.
+ * Subsequent calls will use last modification to the trace data file.
+ */
+void
+process_stat_timestamp(bash_process_t *process, struct timeval *timestamp)
+{
+#if defined(HAVE_ST_MTIME_WITH_E) && defined(HAVE_STAT_TIME_T)
+ timestamp->tv_sec = process->stat.st_mtime.tv_sec;
+ timestamp->tv_usec = process->stat.st_mtime.tv_nsec / 1000;
+#elif defined(HAVE_ST_MTIME_WITH_SPEC)
+ timestamp->tv_sec = process->stat.st_mtimespec.tv_sec;
+ timestamp->tv_usec = process->stat.st_mtimespec.tv_nsec / 1000;
+#elif defined(HAVE_STAT_TIMESTRUC) || defined(HAVE_STAT_TIMESPEC) || defined(HAVE_STAT_TIMESPEC_T)
+ timestamp->tv_sec = process->stat.st_mtim.tv_sec;
+ timestamp->tv_usec = process->stat.st_mtim.tv_nsec / 1000;
+#else
+!bozo!
+#endif
+}
+
+/*
+ * Parse the header file (/path/.pid) containing xtrace metadata.
+ * Helper routine, used during initialising of a tracked shell.
+ */
+static void
+process_head_parser(bash_process_t *verify, const char *buffer, size_t size)
+{
+ char *p = (char *)buffer, *end = (char *)buffer + size - 1;
+ char script[1024];
+ int version = 0;
+ int date = 0;
+
+ p += extract_int(p, "version:", sizeof("version:")-1, &version);
+ p += extract_int(p, "ppid:", sizeof("ppid:")-1, &verify->parent);
+ p += extract_int(p, "date:", sizeof("date:")-1, &date);
+ extract_cmd(p, end - p, "+", sizeof("+")-1, script, sizeof(script));
+
+ if (date) {
+ /* Use the given starttime of the script from the header */
+ verify->starttime.tv_sec = date;
+ verify->starttime.tv_usec = 0;
+ } else {
+ /* Use a timestamp from the header as a best-effort guess */
+#if defined(HAVE_ST_MTIME_WITH_E) && defined(HAVE_STAT_TIME_T)
+ verify->starttime.tv_sec = verify->stat.st_mtime.tv_sec;
+ verify->starttime.tv_usec = verify->stat.st_mtime.tv_nsec / 1000;
+#elif defined(HAVE_ST_MTIME_WITH_SPEC)
+ verify->starttime.tv_sec = verify->stat.st_mtimespec.tv_sec;
+ verify->starttime.tv_usec = verify->stat.st_mtimespec.tv_nsec / 1000;
+#elif defined(HAVE_STAT_TIMESTRUC) || defined(HAVE_STAT_TIMESPEC) || defined(HAVE_STAT_TIMESPEC_T)
+ verify->starttime.tv_sec = verify->stat.st_mtim.tv_sec;
+ verify->starttime.tv_usec = verify->stat.st_mtim.tv_nsec / 1000;
+#else
+!bozo!
+#endif
+ }
+ verify->version = version;
+
+ size = 16 + strlen(script); /* pid and script name */
+ verify->instance = malloc(size);
+ snprintf(verify->instance, size, "%u %s", verify->pid, script);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "process header v%d: inst='%s' ppid=%d",
+ verify->version, verify->instance, verify->parent);
+}
+
+/*
+ * Verify the header file (/path/.pid) containing xtrace metadata.
+ * Helper routine, used during initialising of a tracked shell.
+ */
+static int
+process_head_verify(const char *filename, bash_process_t *verify)
+{
+ size_t size;
+ char buffer[1024];
+ int fd = open(filename, O_RDONLY);
+
+ if (fd < 0)
+ return fd;
+ if (fstat(fd, &verify->stat) < 0 || !S_ISREG(verify->stat.st_mode)) {
+ close(fd);
+ return -1;
+ }
+
+ size = read(fd, buffer, sizeof(buffer));
+ if (size > 0)
+ process_head_parser(verify, buffer, size);
+ close(fd);
+
+ /* make sure we only parse header/trace file formats we understand */
+ if (verify->version < MINIMUM_VERSION || verify->version > MAXIMUM_VERSION)
+ return -1;
+ return 0;
+}
+
+/*
+ * Verify the data files associated with a traced bash process.
+ * Helper routine, used during initialising of a tracked shell.
+ */
+static int
+process_verify(const char *bashname, bash_process_t *verify)
+{
+ int fd;
+ char *endnum;
+ char path[MAXPATHLEN];
+ struct stat stat;
+
+ verify->pid = (pid_t) strtoul(bashname, &endnum, 10);
+ if (*endnum != '\0' || verify->pid < 1)
+ return -1;
+
+ snprintf(path, sizeof(path), "%s%c.%s", pidpath, __pmPathSeparator(), bashname);
+ if (process_head_verify(path, verify) < 0)
+ return -1;
+
+ snprintf(path, sizeof(path), "%s%c%s", pidpath, __pmPathSeparator(), bashname);
+ if ((fd = open(path, O_RDONLY | O_NONBLOCK)) < 0)
+ return -1;
+ if (fstat(fd, &stat) < 0 || !S_ISFIFO(stat.st_mode)) {
+ close(fd);
+ return -1;
+ }
+ verify->fd = fd;
+ return 0;
+}
+
+/*
+ * Finally allocate memory for a verified, traced bash process.
+ * Helper routine, used during initialising of a tracked shell.
+ */
+static bash_process_t *
+process_alloc(const char *bashname, bash_process_t *init, int numclients)
+{
+ int queueid = pmdaEventNewActiveQueue(bashname, bash_maxmem, numclients);
+ bash_process_t *bashful = malloc(sizeof(bash_process_t));
+
+ if (pmDebug & DBG_TRACE_APPL1)
+ __pmNotifyErr(LOG_DEBUG, "process_alloc: %s, queueid=%d", bashname, queueid);
+
+ if (!bashful) {
+ __pmNotifyErr(LOG_ERR, "process allocation out of memory");
+ return NULL;
+ }
+ if (queueid < 0) {
+ __pmNotifyErr(LOG_ERR, "attempt to dup queue for %s", bashname);
+ free(bashful);
+ return NULL;
+ }
+
+ /* Tough access situation - how to log without this? */
+ pmdaEventSetAccess(pmdaGetContext(), queueid, 1);
+
+ bashful->fd = init->fd;
+ bashful->pid = init->pid;
+ bashful->parent = init->parent;
+ bashful->queueid = queueid;
+ bashful->exited = 0;
+ bashful->finished = 0;
+ bashful->noaccess = 0;
+ bashful->version = init->version;
+ bashful->padding = 0;
+
+ memcpy(&bashful->starttime, &init->starttime, sizeof(struct timeval));
+ memcpy(&bashful->stat, &init->stat, sizeof(struct stat));
+ /* copy of first stat time, identifies first event */
+ process_stat_timestamp(bashful, &bashful->startstat);
+ /* copy of pointer to dynamically allocated memory */
+ bashful->instance = init->instance;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "process_alloc: %s", bashful->instance);
+
+ return bashful;
+}
+
+int
+event_start(bash_process_t *bp, struct timeval *timestamp)
+{
+ int start = memcmp(timestamp, &bp->startstat, sizeof(*timestamp));
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "check start event for %s (%d), %ld vs %ld",
+ bp->instance, start, bp->startstat.tv_sec, timestamp->tv_sec);
+
+ return start == 0;
+}
+
+/*
+ * Initialise a bash process data structure using the header and
+ * trace file. Ready to accept event traces from this shell on
+ * completion of this routine - file descriptor setup, structure
+ * filled with all metadata (exported) about this process.
+ * Note: this is using an on-stack process structure, only if it
+ * all checks out will we allocate memory for it, and keep it.
+ */
+static int
+process_init(const char *bashname, bash_process_t **bp)
+{
+ bash_process_t init = { 0 };
+
+ pmAtomValue atom;
+ pmdaEventClients(&atom);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "process_init: %s (%d clients)",
+ bashname, atom.ul);
+
+ if (process_verify(bashname, &init) < 0)
+ return -1;
+ *bp = process_alloc(bashname, &init, atom.ul);
+ if (*bp == NULL)
+ return -1;
+ return 0;
+}
+
+static int
+process_read(bash_process_t *process)
+{
+ int j;
+ char *s, *p;
+ size_t offset;
+ ssize_t bytes;
+ struct timeval timestamp;
+
+ static char *buffer;
+ static int bufsize;
+
+ /*
+ * Using a static (global) event buffer to hold initial read.
+ * The aim is to reduce memory allocation until we know we'll
+ * need to keep something.
+ */
+ if (!buffer) {
+ int sts = 0;
+ bufsize = 16 * getpagesize();
+#ifdef HAVE_POSIX_MEMALIGN
+ sts = posix_memalign((void **)&buffer, getpagesize(), bufsize);
+#else
+#ifdef HAVE_MEMALIGN
+ buffer = (char *)memalign(getpagesize(), bufsize);
+ if (buffer == NULL) sts = -1;
+#else
+ buffer = (char *)malloc(bufsize);
+ if (buffer == NULL) sts = -1;
+#endif
+#endif
+ if (sts != 0) {
+ __pmNotifyErr(LOG_ERR, "event buffer allocation failure");
+ return -1;
+ }
+ }
+
+ offset = 0;
+multiread:
+ if (process->fd < 0)
+ return 0;
+
+ bytes = read(process->fd, buffer + offset, bufsize - 1 - offset);
+
+ /*
+ * Ignore the error if:
+ * - we've got EOF (0 bytes read)
+ * - EAGAIN/EWOULDBLOCK (fd is marked nonblocking and read would block)
+ */
+ if (bytes == 0)
+ return 0;
+ if (bytes < 0 && (errno == EBADF))
+ return 0;
+ if (bytes < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
+ return 0;
+ if (bytes > bash_maxmem)
+ return 0;
+ if (bytes < 0) {
+ __pmNotifyErr(LOG_ERR, "read failure on process %s: %s",
+ process->instance, strerror(errno));
+ return -1;
+ }
+
+ process_stat_timestamp(process, &timestamp);
+
+ buffer[bufsize-1] = '\0';
+ for (s = p = buffer, j = 0; *s != '\0' && j < bufsize-1; s++, j++) {
+ if (*s != '\n')
+ continue;
+ *s = '\0';
+ bytes = (s+1) - p;
+ pmdaEventQueueAppend(process->queueid, p, bytes, &timestamp);
+ p = s + 1;
+ }
+ /* did we just do a full buffer read? */
+ if (p == buffer) {
+ char msg[64];
+ __pmNotifyErr(LOG_ERR, "Ignoring long (%d bytes) line: \"%s\"", (int)
+ bytes, __pmdaEventPrint(p, bytes, msg, sizeof(msg)));
+ } else if (j == bufsize - 1) {
+ offset = bufsize-1 - (p - buffer);
+ memmove(buffer, p, offset);
+ goto multiread; /* read rest of line */
+ }
+ return 1;
+}
+
+static void
+process_unlink(bash_process_t *process, const char *bashname)
+{
+ char path[MAXPATHLEN];
+
+ snprintf(path, sizeof(path), "%s%c%s", pidpath, __pmPathSeparator(), bashname);
+ unlink(path);
+ snprintf(path, sizeof(path), "%s%c.%s", pidpath, __pmPathSeparator(), bashname);
+ unlink(path);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "process_unlink: removed %s", bashname);
+}
+
+static int
+process_drained(bash_process_t *process)
+{
+ pmAtomValue value = { 0 };
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "process_queue_drained check on queue %d (pid %d)",
+ process->queueid, process->pid);
+ if (pmdaEventQueueMemory(process->queueid, &value) < 0)
+ return 1; /* error, consider it drained and cleanup */
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "process_queue_drained: %s (%llu)", value.ll?"n":"y", (long long)value.ull);
+ return value.ull == 0;
+}
+
+static void
+process_done(bash_process_t *process, const char *bashname)
+{
+ struct timeval timestamp;
+
+ if (process->exited == 0) {
+ process->exited = (__pmProcessExists(process->pid) == 0);
+
+ if (!process->exited)
+ return;
+ /* empty event inserted into queue to denote bash has exited */
+ if (!process->finished) {
+ process->finished = 1; /* generate no further events */
+ process_stat_timestamp(process, &timestamp);
+ pmdaEventQueueAppend(process->queueid, NULL, 0, &timestamp);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "process_done: marked queueid %d (pid %d) done",
+ process->queueid, process->pid);
+ }
+ }
+
+ if (process->finished) {
+ /* once all clients have seen final events, clean named queue */
+ if (process_drained(process))
+ process_unlink(process, bashname);
+ }
+}
+
+void
+event_refresh(pmInDom bash_indom)
+{
+ struct dirent **files;
+ bash_process_t *bp;
+ int i, id, sts, num = scandir(pidpath, &files, NULL, NULL);
+
+ if (pmDebug & DBG_TRACE_APPL0 && num > 2)
+ __pmNotifyErr(LOG_DEBUG, "event_refresh: phase1: %d files", num - 2);
+
+ pmdaCacheOp(bash_indom, PMDA_CACHE_INACTIVE);
+
+ /* (re)activate processes that are actively generating events */
+ for (i = 0; i < num; i++) {
+ char *processid = files[i]->d_name;
+
+ if (processid[0] == '.')
+ continue;
+
+ /* either create or re-activate a bash process structure */
+ sts = pmdaCacheLookupName(bash_indom, processid, &id, (void **)&bp);
+ if (sts != PMDA_CACHE_INACTIVE) {
+ if (process_init(processid, &bp) < 0)
+ continue;
+ }
+ pmdaCacheStore(bash_indom, PMDA_CACHE_ADD, bp->instance, (void *)bp);
+
+ /* read any/all new events for this bash process, enqueue 'em */
+ process_read(bp);
+
+ /* check if process is running and generate end marker if not */
+ process_done(bp, files[i]->d_name);
+ }
+
+ for (i = 0; i < num; i++)
+ free(files[i]);
+ if (num > 0)
+ free(files);
+}
+
+void
+event_init(void)
+{
+ pcptmpdir = pmGetConfig("PCP_TMP_DIR");
+ sprintf(pidpath, "%s%c%s", pcptmpdir, __pmPathSeparator(), prefix);
+}
diff --git a/src/pmdas/bash/event.h b/src/pmdas/bash/event.h
new file mode 100644
index 0000000..f36b5b4
--- /dev/null
+++ b/src/pmdas/bash/event.h
@@ -0,0 +1,65 @@
+/*
+ * Event support for the bash tracing PMDA
+ *
+ * Copyright (c) 2012 Nathan Scott. All rights reversed.
+ *
+ * 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 _EVENT_H
+#define _EVENT_H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "pmapi.h"
+#include "impl.h"
+
+typedef struct bash_process {
+ int fd;
+ pid_t pid;
+ pid_t parent;
+ int queueid;
+
+ int exited : 1; /* flag: process running? */
+ int finished: 1; /* flag: exit event sent? */
+ int noaccess: 1; /* flag: store-to-access? */
+ int version : 8; /* pmda <-> bash xtrace version */
+ int padding : 21; /* filler */
+
+ struct timeval starttime; /* timestamp trace started */
+ struct timeval startstat; /* timestamp of first stat */
+ struct stat stat; /* stat of the header file */
+
+ char *instance; /* process id, space, script */
+} bash_process_t;
+
+typedef struct bash_trace {
+ int flags;
+ struct timeval timestamp;
+ int line;
+ char function[64];
+ char command[512];
+} bash_trace_t;
+
+extern long bash_maxmem;
+
+extern void event_init(void);
+extern void event_refresh(pmInDom);
+extern int event_start(bash_process_t *, struct timeval *);
+extern void process_stat_timestamp(bash_process_t *, struct timeval *);
+
+#define MINIMUM_VERSION 1
+#define MAXIMUM_VERSION 1
+
+extern int extract_int(char *, const char *, size_t, int *);
+extern int extract_str(char *, size_t, const char *, size_t, char *, size_t);
+extern int extract_cmd(char *, size_t, const char *, size_t, char *, size_t);
+
+#endif /* _EVENT_H */
diff --git a/src/pmdas/bash/help b/src/pmdas/bash/help
new file mode 100644
index 0000000..953d214
--- /dev/null
+++ b/src/pmdas/bash/help
@@ -0,0 +1,48 @@
+#
+# Copyright (c) 2012 Nathan Scott. 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.
+#
+# bash 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
+#
+
+@ BASH.0 Instance domain of traced bash processes
+An instance domain representing all of the currently running bash
+processes which are exporting trace metrics.
+
+@ bash.xtrace.numclients
+The number of attached clients.
+
+@ bash.xtrace.maxmem
+Maximum number of queued event bytes for each log file.
+
+@ bash.xtrace.queuemem
+@ bash.xtrace.count
+@ bash.xtrace.records
+@ bash.xtrace.parameters.pid
+@ bash.xtrace.parameters.parent
+@ bash.xtrace.parameters.lineno
+@ bash.xtrace.parameters.function
+@ bash.xtrace.parameters.command
+
diff --git a/src/pmdas/bash/pcp.sh b/src/pmdas/bash/pcp.sh
new file mode 100755
index 0000000..cef1e11
--- /dev/null
+++ b/src/pmdas/bash/pcp.sh
@@ -0,0 +1,30 @@
+# Shell interface to PCP shell event tracing PMDA
+
+if [ -z "$PCP_SH_DONE" ]
+then
+ if [ -n "$PCP_CONF" ]
+ then
+ __CONF="$PCP_CONF"
+ elif [ -n "$PCP_DIR" ]
+ then
+ __CONF="$PCP_DIR/etc/pcp.conf"
+ else
+ __CONF=/etc/pcp.conf
+ fi
+ if [ ! -f "$__CONF" ]
+ then
+ echo "pcp.env: Fatal Error: \"$__CONF\" not found" >&2
+ exit 1
+ fi
+ eval `sed -e 's/"//g' $__CONF \
+ | awk -F= '
+/^PCP_/ && NF == 2 {
+ exports=exports" "$1
+ printf "%s=${%s:-\"%s\"}\n", $1, $1, $2
+} END {
+ print "export", exports
+}'`
+ export PCP_ENV_DONE=y
+fi
+
+. $PCP_SHARE_DIR/lib/bashproc.sh
diff --git a/src/pmdas/bash/pmns b/src/pmdas/bash/pmns
new file mode 100644
index 0000000..a26790c
--- /dev/null
+++ b/src/pmdas/bash/pmns
@@ -0,0 +1,36 @@
+/*
+ * Metrics for bash xtrace PMDA
+ *
+ * Copyright (c) 2012 Nathan Scott.
+ *
+ * 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.
+ */
+
+bash {
+ xtrace
+}
+
+bash.xtrace {
+ numclients BASH:0:0
+ maxmem BASH:0:1
+ queuemem BASH:0:2
+ count BASH:0:3
+ records BASH:0:4
+ parameters
+}
+
+bash.xtrace.parameters {
+ pid BASH:0:5
+ parent BASH:0:6
+ lineno BASH:0:7
+ function BASH:0:8
+ command BASH:0:9
+}
diff --git a/src/pmdas/bash/root b/src/pmdas/bash/root
new file mode 100644
index 0000000..2e66cb4
--- /dev/null
+++ b/src/pmdas/bash/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { bash }
+
+#include "pmns"
+
diff --git a/src/pmdas/bash/test-child.sh b/src/pmdas/bash/test-child.sh
new file mode 100755
index 0000000..2b5d482
--- /dev/null
+++ b/src/pmdas/bash/test-child.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2012 Nathan Scott. 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.sh
+pcp_trace on $0 $@
+
+wired()
+{
+ # burn a little CPU, then sleep
+ for i in 0 1 2 3 4 5 6 7 8 9 0
+ do
+ /bin/true && /bin/true
+ done
+ sleep $1
+}
+
+count=0
+while true
+do
+ (( count++ ))
+ echo "get busy, $count" # top level
+ wired 2 # call a shell function
+ [ $count -ge 10 ] && break
+done
+
+pcp_trace off
+exit 0
diff --git a/src/pmdas/bash/test-trace.sh b/src/pmdas/bash/test-trace.sh
new file mode 100755
index 0000000..6ff89b7
--- /dev/null
+++ b/src/pmdas/bash/test-trace.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2012 Nathan Scott. 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.sh
+pcp_trace on $0 $@
+
+tired()
+{
+ sleep $1
+}
+
+count=0
+while true
+do
+ (( count++ ))
+ echo "awoke, $count" # top level
+ tired 2 # call a shell function
+ branch=$(( count % 3 ))
+ case $branch
+ in
+ 0) ./test-child.sh $count &
+ ;;
+ 2) wait
+ ;;
+ esac
+done
+
+pcp_trace off
+exit 0
diff --git a/src/pmdas/bash/util.c b/src/pmdas/bash/util.c
new file mode 100644
index 0000000..3284579
--- /dev/null
+++ b/src/pmdas/bash/util.c
@@ -0,0 +1,84 @@
+/*
+ * Utility routines for the bash tracing PMDA
+ *
+ * Copyright (c) 2012 Nathan Scott.
+ *
+ * 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 "event.h"
+#include "pmda.h"
+#include <ctype.h>
+
+int
+extract_int(char *s, const char *field, size_t length, int *value)
+{
+ char *endnum;
+ int num;
+
+ if (strncmp(s, field, length) != 0)
+ return 0;
+ num = strtol(s + length, &endnum, 10);
+ if (*endnum != ',' && *endnum != '\0' && !isspace((int)*endnum))
+ return 0;
+ *value = num;
+ return endnum - s + 1;
+}
+
+int
+extract_str(char *s, size_t end, const char *field, size_t length, char *value, size_t vsz)
+{
+ char *p;
+
+ if (strncmp(s, field, length) != 0)
+ return 0;
+ p = s + length;
+ while (*p != ',' && *p != '\0' && !isspace((int)*p))
+ p++;
+ *p = '\0';
+ strncpy(value, s + length, vsz);
+ return p - s + 1;
+}
+
+int
+extract_cmd(char *s, size_t end, const char *field, size_t length, char *value, size_t vsz)
+{
+ char *start = NULL, *stop = NULL, *p;
+ int len;
+
+ /* find the start of the command */
+ for (p = s; p < s + end; p++) {
+ if (strncmp(p, field, length) != 0)
+ continue;
+ p++;
+ if (*p == ' ')
+ p++;
+ break;
+ }
+ if (p == s + end)
+ return 0;
+ start = p;
+
+ /* find the command terminator */
+ while (*p != '\n' && *p != '\0' && p < s + end)
+ p++;
+ stop = p;
+
+ /* truncate it if necessary */
+ len = stop - start;
+ if (len > vsz - 1)
+ len = vsz - 1;
+
+ /* copy it over to "value" */
+ start[len] = '\0';
+ strncpy(value, start, len + 1);
+ return len;
+}