diff options
Diffstat (limited to 'src/pmdas/bash')
-rw-r--r-- | src/pmdas/bash/GNUmakefile | 62 | ||||
-rw-r--r-- | src/pmdas/bash/Install | 36 | ||||
-rw-r--r-- | src/pmdas/bash/README | 50 | ||||
-rw-r--r-- | src/pmdas/bash/Remove | 23 | ||||
-rw-r--r-- | src/pmdas/bash/bash.c | 472 | ||||
-rwxr-xr-x | src/pmdas/bash/bashproc.sh | 64 | ||||
-rw-r--r-- | src/pmdas/bash/event.c | 434 | ||||
-rw-r--r-- | src/pmdas/bash/event.h | 65 | ||||
-rw-r--r-- | src/pmdas/bash/help | 48 | ||||
-rwxr-xr-x | src/pmdas/bash/pcp.sh | 30 | ||||
-rw-r--r-- | src/pmdas/bash/pmns | 36 | ||||
-rw-r--r-- | src/pmdas/bash/root | 10 | ||||
-rwxr-xr-x | src/pmdas/bash/test-child.sh | 39 | ||||
-rwxr-xr-x | src/pmdas/bash/test-trace.sh | 41 | ||||
-rw-r--r-- | src/pmdas/bash/util.c | 84 |
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, ×tamp); + + 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, ×tamp); + 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, ×tamp); + pmdaEventQueueAppend(process->queueid, NULL, 0, ×tamp); + + 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; +} |