/* * Logger, a configurable log file monitoring PMDA * * Copyright (c) 2011-2012 Red Hat. * Copyright (c) 2011 Nathan Scott. All Rights Reserved. * Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * Debug options * APPL0 configfile processing and PMNS setup * APPL1 loading event data from the log files * APPL2 interaction with PMCD */ #include "domain.h" #include "event.h" #include "util.h" #include "pmda.h" /* * Logger PMDA * * Metrics * logger.numclients - number of attached clients * logger.numlogfiles - number of monitored logfiles * logger.param_string - string event data * logger.perfile.{LOGFILE}.count - observed event count * logger.perfile.{LOGFILE}.bytes - observed events size * logger.perfile.{LOGFILE}.size - logfile size * logger.perfile.{LOGFILE}.path - logfile path * logger.perfile.{LOGFILE}.numclients - number of attached * clients/logfile * logger.perfile.{LOGFILE}.records - event records/logfile */ #define DEFAULT_MAXMEM (2 * 1024 * 1024) /* 2 megabytes */ long maxmem; int maxfd; fd_set fds; static int interval_expired; static struct timeval interval = { 2, 0 }; static char *username; static int nummetrics; static __pmnsTree *pmns; typedef struct dynamic_metric_info { int handle; int pmid_index; const char *help_text; } dynamic_metric_info_t; static dynamic_metric_info_t *dynamic_metric_infotab; static pmdaMetric dynamic_metrictab[] = { /* perfile.{LOGFILE}.count */ { NULL, /* m_user gets filled in later */ { 0 /* pmid gets filled in later */, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, }, /* perfile.{LOGFILE}.bytes */ { NULL, /* m_user gets filled in later */ { 0 /* pmid gets filled in later */, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, }, /* perfile.{LOGFILE}.size */ { NULL, /* m_user gets filled in later */ { 0 /* pmid gets filled in later */, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, }, /* perfile.{LOGFILE}.path */ { NULL, /* m_user gets filled in later */ { 0 /* pmid gets filled in later */, PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) }, }, /* perfile.{LOGFILE}.numclients */ { NULL, /* m_user gets filled in later */ { 0 /* pmid gets filled in later */, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, }, /* perfile.{LOGFILE}.records */ { NULL, /* m_user gets filled in later */ { 0 /* pmid gets filled in later */, PM_TYPE_EVENT, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, }, /* perfile.{LOGFILE}.queuemem */ { NULL, /* m_user gets filled in later */ { 0 /* pmid gets filled in later */, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, }, }; static char *dynamic_nametab[] = { /* perfile.{LOGFILE}.count */ "count", /* perfile.{LOGFILE}.bytes */ "bytes", /* perfile.{LOGFILE}.size */ "size", /* perfile.{LOGFILE}.path */ "path", /* perfile.{LOGFILE}.numclients */ "numclients", /* perfile.{LOGFILE}.records */ "records", /* perfile.{LOGFILE}.queuemem */ "queuemem", }; static const char *dynamic_helptab[] = { /* perfile.{LOGFILE}.count */ "The cumulative number of events seen for this logfile.", /* perfile.{LOGFILE}.bytes */ "Cumulative number of bytes in events seen for this logfile.", /* perfile.{LOGFILE}.size */ "The current size of this logfile.", /* perfile.{LOGFILE}.path */ "The path for this logfile.", /* perfile.{LOGFILE}.numclients */ "The number of attached clients for this logfile.", /* perfile.{LOGFILE}.records */ "Event records for this logfile.", /* perfile.{LOGFILE}.queuemem */ "Amount of memory used for event data.", }; static pmdaMetric static_metrictab[] = { /* numclients */ { NULL, { PMDA_PMID(0,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, }, /* numlogfiles */ { NULL, { PMDA_PMID(0,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, }, /* param_string */ { NULL, { PMDA_PMID(0,2), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, }, /* perfile.maxmem */ { NULL, /* m_user gets filled in later */ { PMDA_PMID(0,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, }, }; static pmdaMetric *metrictab; static int logger_profile(__pmProfile *prof, pmdaExt *pmda) { pmdaEventNewClient(pmda->e_context); return 0; } static int logger_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda) { pmdaEventNewClient(pmda->e_context); return pmdaFetch(numpmid, pmidlist, resp, pmda); } static int valid_pmid(unsigned int cluster, unsigned int item) { if (cluster != 0 || item > nummetrics) return PM_ERR_PMID; return 0; } static int logger_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom) { __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid); int sts; if ((sts = valid_pmid(idp->cluster, idp->item)) < 0) return sts; sts = PMDA_FETCH_STATIC; if (idp->item < 4) { switch (idp->item) { case 0: /* logger.numclients */ sts = pmdaEventClients(atom); break; case 1: /* logger.numlogfiles */ atom->ul = event_logcount(); break; case 2: /* logger.param_string */ sts = PMDA_FETCH_NOVALUES; break; case 3: /* logger.maxmem */ atom->ull = (unsigned long long)maxmem; break; default: return PM_ERR_PMID; } } else { dynamic_metric_info_t *pinfo; int queue; if ((pinfo = ((mdesc != NULL) ? mdesc->m_user : NULL)) == NULL) return PM_ERR_PMID; queue = event_queueid(pinfo->handle); switch (pinfo->pmid_index) { case 0: /* perfile.{LOGFILE}.count */ sts = pmdaEventQueueCounter(queue, atom); break; case 1: /* perfile.{LOGFILE}.bytes */ sts = pmdaEventQueueBytes(queue, atom); break; case 2: /* perfile.{LOGFILE}.size */ atom->ull = event_pathsize(pinfo->handle); break; case 3: /* perfile.{LOGFILE}.path */ atom->cp = (char *)event_pathname(pinfo->handle); break; case 4: /* perfile.{LOGFILE}.numclients */ sts = pmdaEventQueueClients(queue, atom); break; case 5: /* perfile.{LOGFILE}.records */ sts = pmdaEventQueueRecords(queue, atom, pmdaGetContext(), event_decoder, &pinfo->handle); break; case 6: /* perfile.{LOGFILE}.queuemem */ sts = pmdaEventQueueMemory(queue, atom); break; default: return PM_ERR_PMID; } } return sts; } static int logger_store(pmResult *result, pmdaExt *pmda) { int i, j, sts; pmdaEventNewClient(pmda->e_context); for (i = 0; i < result->numpmid; i++) { pmValueSet *vsp = result->vset[i]; __pmID_int *idp = (__pmID_int *)&vsp->pmid; dynamic_metric_info_t *pinfo = NULL; void *filter; int queueid; if ((sts = valid_pmid(idp->cluster, idp->item)) < 0) return sts; for (j = 0; j < pmda->e_nmetrics; j++) { if (vsp->pmid == pmda->e_metrics[j].m_desc.pmid) { pinfo = pmda->e_metrics[j].m_user; break; } } if (pinfo == NULL) return PM_ERR_PMID; if (pinfo->pmid_index != 5) return PM_ERR_PERMISSION; queueid = event_queueid(pinfo->handle); if (vsp->numval != 1 || vsp->valfmt != PM_VAL_SPTR) return PM_ERR_CONV; sts = event_regex_alloc(vsp->vlist[0].value.pval->vbuf, &filter); if (sts < 0) return sts; sts = pmdaEventSetFilter(pmda->e_context, queueid, filter, event_regex_apply, event_regex_release); if (sts < 0 ) return sts; } return 0; } static void logger_end_contextCallBack(int context) { pmdaEventEndClient(context); } static int logger_pmid(const char *name, pmID *pmid, pmdaExt *pmda) { pmdaEventNewClient(pmda->e_context); return pmdaTreePMID(pmns, name, pmid); } static int logger_name(pmID pmid, char ***nameset, pmdaExt *pmda) { pmdaEventNewClient(pmda->e_context); return pmdaTreeName(pmns, pmid, nameset); } static int logger_children(const char *name, int traverse, char ***kids, int **sts, pmdaExt *pmda) { pmdaEventNewClient(pmda->e_context); return pmdaTreeChildren(pmns, name, traverse, kids, sts); } static int logger_text(int ident, int type, char **buffer, pmdaExt *pmda) { int numstatics = sizeof(static_metrictab)/sizeof(static_metrictab[0]); pmdaEventNewClient(pmda->e_context); if ((type & PM_TEXT_PMID) == PM_TEXT_PMID) { /* Lookup pmid in the metric table. */ int item = pmid_item(ident); /* If the PMID item was for a dynamic metric... */ if (item >= numstatics && item < nummetrics /* and the PMID matches... */ && metrictab[item].m_desc.pmid == (pmID)ident /* and we've got user data... */ && metrictab[item].m_user != NULL) { dynamic_metric_info_t *pinfo = metrictab[item].m_user; /* Return the correct help text. */ *buffer = (char *)pinfo->help_text; return 0; } } return pmdaText(ident, type, buffer, pmda); } void logger_init(pmdaInterface *dp, const char *configfile) { size_t size; int i, j, sts, item, numloggers; int numstatics = sizeof(static_metrictab)/sizeof(static_metrictab[0]); int numdynamics = sizeof(dynamic_metrictab)/sizeof(dynamic_metrictab[0]); pmdaMetric *pmetric; char name[MAXPATHLEN * 2]; dynamic_metric_info_t *pinfo; __pmSetProcessIdentity(username); /* Read and parse config file. */ if ((numloggers = event_config(configfile)) < 0) return; /* Create the dynamic metric info table based on the logfile table */ size = sizeof(struct dynamic_metric_info) * numdynamics * numloggers; if ((dynamic_metric_infotab = malloc(size)) == NULL) { __pmNoMem("logger_init(dynamic)", size, PM_FATAL_ERR); return; } pinfo = dynamic_metric_infotab; for (i = 0; i < numloggers; i++) { for (j = 0; j < numdynamics; j++) { pinfo->handle = i; pinfo->pmid_index = j; pinfo->help_text = dynamic_helptab[j]; pinfo++; } } /* Create the metric table based on the static and dynamic metric tables */ nummetrics = numstatics + (numloggers * numdynamics); size = sizeof(pmdaMetric) * nummetrics; if ((metrictab = malloc(size)) == NULL) { free(dynamic_metric_infotab); __pmNoMem("logger_init(static)", size, PM_FATAL_ERR); return; } memcpy(metrictab, static_metrictab, sizeof(static_metrictab)); pmetric = &metrictab[numstatics]; pinfo = dynamic_metric_infotab; item = numstatics; for (i = 0; i < numloggers; i++) { memcpy(pmetric, dynamic_metrictab, sizeof(dynamic_metrictab)); for (j = 0; j < numdynamics; j++) { pmetric[j].m_desc.pmid = PMDA_PMID(0, item++); pmetric[j].m_user = pinfo++; } pmetric += numdynamics; } if (dp->status != 0) return; dp->version.four.fetch = logger_fetch; dp->version.four.store = logger_store; dp->version.four.profile = logger_profile; dp->version.four.pmid = logger_pmid; dp->version.four.name = logger_name; dp->version.four.children = logger_children; dp->version.four.text = logger_text; pmdaSetFetchCallBack(dp, logger_fetchCallBack); pmdaSetEndContextCallBack(dp, logger_end_contextCallBack); pmdaInit(dp, NULL, 0, metrictab, nummetrics); /* Create the dynamic PMNS tree and populate it. */ if ((sts = __pmNewPMNS(&pmns)) < 0) { __pmNotifyErr(LOG_ERR, "%s: failed to create new pmns: %s\n", pmProgname, pmErrStr(sts)); pmns = NULL; return; } pmetric = &metrictab[numstatics]; for (i = 0; i < numloggers; i++) { const char *id = event_pmnsname(i); for (j = 0; j < numdynamics; j++) { snprintf(name, sizeof(name), "logger.perfile.%s.%s", id, dynamic_nametab[j]); __pmAddPMNSNode(pmns, pmetric[j].m_desc.pmid, name); } pmetric += numdynamics; } /* for reverse (pmid->name) lookups */ pmdaTreeRebuildHash(pmns, (numloggers * numdynamics)); /* initialise the event and client tracking code */ event_init(metrictab[2].m_desc.pmid); } static void logger_timer(int sig, void *ptr) { interval_expired = 1; } void loggerMain(pmdaInterface *dispatch) { fd_set readyfds; int nready, pmcdfd; pmcdfd = __pmdaInFd(dispatch); if (pmcdfd > maxfd) maxfd = pmcdfd; FD_ZERO(&fds); FD_SET(pmcdfd, &fds); /* arm interval timer */ if (__pmAFregister(&interval, NULL, logger_timer) < 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, interval_expired); if (nready < 0) { if (neterror() != EINTR) { __pmNotifyErr(LOG_ERR, "select failure: %s", netstrerror()); exit(1); } else if (!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 (interval_expired) { interval_expired = 0; event_refresh(); } __pmAFunblock(); } } 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)++; } static void usage(void) { fprintf(stderr, "Usage: %s [options] configfile\n\n" "Options:\n" " -d domain use domain (numeric) for metrics domain of PMDA\n" " -l logfile write log into logfile rather than the default\n" " -m memory maximum memory used per logfile (default %ld bytes)\n" " -s interval default delay between iterations (default %d sec)\n" " -U username user account to run under (default \"pcp\")\n", pmProgname, maxmem, (int)interval.tv_sec); exit(1); } int main(int argc, char **argv) { static char helppath[MAXPATHLEN]; char *endnum; pmdaInterface desc; long minmem; int c, err = 0, sep = __pmPathSeparator(); __pmSetProgname(argv[0]); __pmGetUsername(&username); minmem = getpagesize(); maxmem = (minmem > DEFAULT_MAXMEM) ? minmem : DEFAULT_MAXMEM; snprintf(helppath, sizeof(helppath), "%s%c" "logger" "%c" "help", pmGetConfig("PCP_PMDAS_DIR"), sep, sep); pmdaDaemon(&desc, PMDA_INTERFACE_5, pmProgname, LOGGER, "logger.log", helppath); while ((c = pmdaGetOpt(argc, argv, "D:d:l:m:s:U:?", &desc, &err)) != EOF) { switch (c) { case 'm': maxmem = strtol(optarg, &endnum, 10); if (*endnum != '\0') convertUnits(&endnum, &maxmem); if (*endnum != '\0' || maxmem < minmem) { fprintf(stderr, "%s: invalid max memory '%s' (min=%ld)\n", pmProgname, optarg, minmem); err++; } break; case 's': if (pmParseInterval(optarg, &interval, &endnum) < 0) { fprintf(stderr, "%s: -s requires a time interval: %s\n", pmProgname, endnum); free(endnum); err++; } break; case 'U': username = optarg; break; default: err++; break; } } if (err || optind != argc -1) usage(); pmdaOpenLog(&desc); logger_init(&desc, argv[optind]); pmdaConnect(&desc); loggerMain(&desc); event_shutdown(); exit(0); }