diff options
Diffstat (limited to 'src/pmdas/mailq/mailq.c')
-rw-r--r-- | src/pmdas/mailq/mailq.c | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/src/pmdas/mailq/mailq.c b/src/pmdas/mailq/mailq.c new file mode 100644 index 0000000..8d82fcd --- /dev/null +++ b/src/pmdas/mailq/mailq.c @@ -0,0 +1,401 @@ +/* + * Mailq PMDA + * + * Copyright (c) 2012,2014 Red Hat. + * Copyright (c) 1997-2000,2003 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. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" +#include "domain.h" +#ifdef HAVE_REGEX_H +#include <regex.h> +#endif +#include <sys/stat.h> + +/* + * histogram for binning messages based on queue time + */ +typedef struct { + long count; /* number in this bin */ + time_t delay; /* in queue for at least this long (seconds) */ +} histo_t; + +static histo_t *histo; +static int numhisto; +static int queue; + +/* + * list of instances - indexes must match histo[] above + */ +static pmdaInstid *_delay; + +static char *queuedir = "/var/spool/mqueue"; +static char startdir[MAXPATHLEN]; +static char *username; + +static char *regexstring; +static regex_t mq_regex; + +/* + * list of instance domains + */ +static pmdaIndom indomtab[] = { +#define DELAY_INDOM 0 + { DELAY_INDOM, 0, NULL }, +}; + +/* + * all metrics supported in this PMDA - one table entry for each + */ +static pmdaMetric metrictab[] = { +/* length */ + { NULL, + { PMDA_PMID(0,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, + PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, }, +/* deferred */ + { NULL, + { PMDA_PMID(0,1), PM_TYPE_U32, DELAY_INDOM, PM_SEM_INSTANT, + PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, }, +}; + +static int +mailq_histogram(char *option) +{ + struct timeval tv; + char *errmsg; + char *q; + int sts; + + q = strtok(option, ","); + while (q != NULL) { + if ((sts = pmParseInterval((const char *)q, &tv, &errmsg)) < 0) { + pmprintf("%s: bad historgram bins argument:\n%s\n", pmProgname, errmsg); + free(errmsg); + return -EINVAL; + } + numhisto++; + histo = (histo_t *)realloc(histo, numhisto * sizeof(histo[0])); + if (histo == NULL) + __pmNoMem("histo", numhisto * sizeof(histo[0]), PM_FATAL_ERR); + histo[numhisto-1].delay = tv.tv_sec; + q = strtok(NULL, ","); + } + return 0; +} + +static int +compare_delay(const void *a, const void *b) +{ + histo_t *ha = (histo_t *)a; + histo_t *hb = (histo_t *)b; + + return hb->delay - ha->delay; +} + +/* + * callback provided to pmdaFetch + */ +static int +mailq_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom) +{ + __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid); + int b; + + if (idp->cluster == 0) { + if (idp->item == 0) { /* mailq.length */ + if (inst == PM_IN_NULL) + atom->ul = queue; + else + return PM_ERR_INST; + } + else if (idp->item == 1) { /* mailq.deferred */ + /* inst is unsigned, so always >= 0 */ + for (b = 0; b < numhisto; b++) { + if (histo[b].delay == inst) break; + } + if (b < numhisto) + atom->ul = histo[b].count; + else + return PM_ERR_INST; + } + else + return PM_ERR_PMID; + } + else + return PM_ERR_PMID; + + return 0; +} + +/* + * wrapper for pmdaFetch which refreshes the metrics + */ +static int +mailq_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda) +{ + static int warn = 0; + int num; + int i; + int b; + struct stat sbuf; + time_t now; + static time_t last_refresh = 0; + time_t waiting; + char *p; + struct dirent **list; + + time(&now); + + /* clip refresh rate to at most once per 30 seconds */ + if (now - last_refresh > 30) { + last_refresh = now; + + queue = 0; + for (b = 0; b < numhisto; b++) + histo[b].count = 0; + + if (chdir(queuedir) < 0) { + if (warn == 0) { + __pmNotifyErr(LOG_ERR, "chdir(\"%s\") failed: %s\n", + queuedir, osstrerror()); + warn = 1; + } + } + else { + if (warn == 1) { + __pmNotifyErr(LOG_INFO, "chdir(\"%s\") success\n", queuedir); + warn = 0; + } + + num = scandir(".", &list, NULL, NULL); + + for (i = 0; i < num; i++) { + p = list[i]->d_name; + /* only file names that match the regular expression */ + if (regexstring && regexec(&mq_regex, list[i]->d_name, 0, NULL, 0)) + continue; + else if (!regexstring && (*p != 'd' || *(p+1) != 'f')) + continue; + if (stat(p, &sbuf) != 0) { + /* + * ENOENT expected sometimes if sendmail is doing its job + */ + if (oserror() == ENOENT) + continue; + fprintf(stderr, "stat(\"%s\"): %s\n", p, osstrerror()); + continue; + } + if (sbuf.st_size > 0 && S_ISREG(sbuf.st_mode)) { + /* really in the queue */ +#if defined(HAVE_ST_MTIME_WITH_E) + waiting = now - sbuf.st_mtime; +#elif defined(HAVE_ST_MTIME_WITH_SPEC) + waiting = now - sbuf.st_mtimespec.tv_sec; +#else + waiting = now - sbuf.st_mtim.tv_sec; +#endif + for (b = 0; b < numhisto; b++) { + if (waiting >= histo[b].delay) { + histo[b].count++; + break; + } + } + queue++; + } + } + for (i = 0; i < num; i++) + free(list[i]); + if (num > 0) + free(list); + } + if (chdir(startdir) < 0) { + __pmNotifyErr(LOG_ERR, "chdir(\"%s\") failed: %s\n", + startdir, osstrerror()); + } + } + + return pmdaFetch(numpmid, pmidlist, resp, pmda); +} + +/* + * Initialise the agent (daemon only). + */ +void +mailq_init(pmdaInterface *dp) +{ + if (dp->status != 0) + return; + + __pmSetProcessIdentity(username); + dp->version.two.fetch = mailq_fetch; + pmdaSetFetchCallBack(dp, mailq_fetchCallBack); + pmdaInit(dp, indomtab, sizeof(indomtab)/sizeof(indomtab[0]), metrictab, + sizeof(metrictab)/sizeof(metrictab[0])); +} + +pmdaInterface dispatch; + +pmLongOptions longopts[] = { + PMDA_OPTIONS_HEADER("Options"), + PMOPT_DEBUG, + { "binlist", 1, 'b', "TIMES", "comma-separated histogram bins times" }, + PMDAOPT_DOMAIN, + PMDAOPT_LOGFILE, + { "regex", 1, 'r', "RE", "regular expression for matching mail file names" }, + PMDAOPT_USERNAME, + PMOPT_HELP, + PMDA_OPTIONS_END +}; + +pmdaOptions opts = { + .short_options = "b:D:d:l:r:U:?", + .long_options = longopts, + .short_usage = "[options] [queuedir]", +}; + +/* + * Set up the agent, running as a daemon. + */ +int +main(int argc, char **argv) +{ + int sep = __pmPathSeparator(); + int c; + int i; + char namebuf[30]; + char mypath[MAXPATHLEN]; + + __pmSetProgname(argv[0]); + __pmGetUsername(&username); + + if (getcwd(startdir, sizeof(startdir)) == NULL) { + fprintf(stderr, "%s: getcwd() failed: %s\n", + pmProgname, pmErrStr(-oserror())); + exit(1); + } + + snprintf(mypath, sizeof(mypath), "%s%c" "mailq" "%c" "help", + pmGetConfig("PCP_PMDAS_DIR"), sep, sep); + pmdaDaemon(&dispatch, PMDA_INTERFACE_2, pmProgname, MAILQ, + "mailq.log", mypath); + + while ((c = pmdaGetOptions(argc, argv, &opts, &dispatch)) != EOF) { + switch (c) { + case 'b': + if (mailq_histogram(opts.optarg) < 0) + opts.errors++; + break; + + case 'r': + regexstring = opts.optarg; + c = regcomp(&mq_regex, regexstring, REG_EXTENDED | REG_NOSUB); + if (c != 0) { + regerror(c, &mq_regex, mypath, sizeof(mypath)); + pmprintf("%s: cannot compile regular expression: %s\n", + pmProgname, mypath); + opts.errors++; + } + break; + } + } + + if (opts.optind == argc - 1) + queuedir = argv[opts.optind]; + else if (opts.optind != argc) + opts.errors++; + + if (opts.errors) { + pmdaUsageMessage(&opts); + exit(1); + } + + if (opts.username) + username = opts.username; + + if (histo == NULL) { + /* default histo bins, if not already done above ... */ + numhisto = 7; + histo = (histo_t *)malloc(numhisto * sizeof(histo[0])); + if (histo == NULL) { + __pmNoMem("histo", numhisto * sizeof(histo[0]), PM_FATAL_ERR); + } + histo[0].delay = 7 * 24 * 3600; + histo[1].delay = 3 * 24 * 3600; + histo[2].delay = 24 * 3600; + histo[3].delay = 8 * 3600; + histo[4].delay = 4 * 3600; + histo[5].delay = 1 * 3600; + histo[6].delay = 0; + } + else { + /* need to add last one and sort on descending time */ + numhisto++; + histo = (histo_t *)realloc(histo, numhisto * sizeof(histo[0])); + if (histo == NULL) { + __pmNoMem("histo", numhisto * sizeof(histo[0]), PM_FATAL_ERR); + } + histo[numhisto-1].delay = 0; + qsort(histo, numhisto, sizeof(histo[0]), compare_delay); + } + + _delay = (pmdaInstid *)malloc(numhisto * sizeof(_delay[0])); + if (_delay == NULL) + __pmNoMem("_delay", numhisto * sizeof(_delay[0]), PM_FATAL_ERR); + + for (i = 0; i < numhisto; i++) { + time_t tmp; + _delay[i].i_inst = histo[i].delay; + histo[i].count = 0; + if (histo[i].delay == 0) + sprintf(namebuf, "recent"); + else if (histo[i].delay < 60) + sprintf(namebuf, "%d-secs", (int)histo[i].delay); + else if (histo[i].delay < 60 * 60) { + tmp = histo[i].delay / 60; + if (tmp <= 1) + sprintf(namebuf, "1-min"); + else + sprintf(namebuf, "%d-mins", (int)tmp); + } + else if (histo[i].delay < 24 * 60 * 60) { + tmp = histo[i].delay / (60 * 60); + if (tmp <= 1) + sprintf(namebuf, "1-hour"); + else + sprintf(namebuf, "%d-hours", (int)tmp); + } + else { + tmp = histo[i].delay / (24 * 60 * 60); + if (tmp <= 1) + sprintf(namebuf, "1-day"); + else + sprintf(namebuf, "%d-days", (int)tmp); + } + _delay[i].i_name = strdup(namebuf); + if (_delay[i].i_name == NULL) { + __pmNoMem("_delay[i].i_name", strlen(namebuf), PM_FATAL_ERR); + } + } + + indomtab[DELAY_INDOM].it_numinst = numhisto; + indomtab[DELAY_INDOM].it_set = _delay; + + pmdaOpenLog(&dispatch); + mailq_init(&dispatch); + pmdaConnect(&dispatch); + pmdaMain(&dispatch); + + exit(0); +} |