diff options
Diffstat (limited to 'src/pmdas/bash/bash.c')
-rw-r--r-- | src/pmdas/bash/bash.c | 472 |
1 files changed, 472 insertions, 0 deletions
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); +} |