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; +} | 
