diff options
Diffstat (limited to 'src/pmlogger/src/dopdu.c')
-rw-r--r-- | src/pmlogger/src/dopdu.c | 1491 |
1 files changed, 1491 insertions, 0 deletions
diff --git a/src/pmlogger/src/dopdu.c b/src/pmlogger/src/dopdu.c new file mode 100644 index 0000000..d3c35c4 --- /dev/null +++ b/src/pmlogger/src/dopdu.c @@ -0,0 +1,1491 @@ +/* + * Copyright (c) 2012-2014 Red Hat. + * Copyright (c) 1995-2001 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 "logger.h" + + +/* return one of these when a status request is made from a PCP 1.x pmlc */ +typedef struct { + __pmTimeval ls_start; /* start time for log */ + __pmTimeval ls_last; /* last time log written */ + __pmTimeval ls_timenow; /* current time */ + int ls_state; /* state of log (from __pmLogCtl) */ + int ls_vol; /* current volume number of log */ + __int64_t ls_size; /* size of current volume */ + char ls_hostname[PM_LOG_MAXHOSTLEN]; + /* name of pmcd host */ + char ls_tz[40]; /* $TZ at collection host */ + char ls_tzlogger[40]; /* $TZ at pmlogger */ +} __pmLoggerStatus_v1; + +#ifdef PCP_DEBUG +/* This crawls over the data structure looking for weirdness */ +void +reality_check(void) +{ + __pmHashNode *hp; + task_t *tp; + task_t *tp2; + fetchctl_t *fp; + optreq_t *rqp; + pmID pmid; + int i = 0, j, k; + + /* check that all fetch_t's f_aux point back to their parent task */ + for (tp = tasklist; tp != NULL; tp = tp->t_next, i++) { + if (tp->t_fetch == NULL) + fprintf(stderr, "task[%d] @" PRINTF_P_PFX "%p has no fetch group\n", i, tp); + j = 0; + for (fp = tp->t_fetch; fp != NULL; fp = fp->f_next) { + if (fp->f_aux != (void *)tp) + fprintf(stderr, "task[%d] fetch group[%d] has invalid task pointer\n", + i, j); + j++; + } + + /* check that all optreq_t's in hash list have valid r_fetch->f_aux + * pointing to a task in the task list. + */ + for (j = 0; j < tp->t_numpmid; j++) { + pmid = tp->t_pmidlist[j]; + for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL; hp = hp->next) { + if (pmid != (pmID)hp->key) + continue; + rqp = (optreq_t *)hp->data; + for (tp2 = tasklist; tp2 != NULL; tp2 = tp2->t_next) + if (rqp->r_fetch->f_aux == (void *)tp2) + break; + if (tp2 == NULL) { + fprintf(stderr, "task[%d] pmid %s optreq " PRINTF_P_PFX "%p for [", + i, pmIDStr(pmid), rqp); + if (rqp->r_numinst == 0) + fputs("`all instances' ", stderr); + else + for (k = 0; k < rqp->r_numinst; k++) + fprintf(stderr, "%d ", rqp->r_instlist[k]); + fputs("] bad task pointer\n", stderr); + } + } + } + } +} + +void +dumpit(void) +{ + int i; + task_t *tp; + + reality_check(); + for (tp = tasklist, i = 0; tp != NULL; tp = tp->t_next, i++) { + fprintf(stderr, + "\ntask[%d] @" PRINTF_P_PFX "%p: %s %s \ndelta = %f\n", i, tp, + PMLC_GET_MAND(tp->t_state) ? "mandatory " : "advisory ", + PMLC_GET_ON(tp->t_state) ? "on " : "off ", + tp->t_delta.tv_sec + (float)tp->t_delta.tv_usec / 1.0e6); + __pmOptFetchDump(stderr, tp->t_fetch); + } +} + +/* + * stolen from __pmDumpResult + */ +static void +dumpcontrol(FILE *f, const pmResult *resp, int dovalue) +{ + int i; + int j; + + fprintf(f,"LogControl dump from " PRINTF_P_PFX "%p", resp); + fprintf(f, " numpmid: %d\n", resp->numpmid); + for (i = 0; i < resp->numpmid; i++) { + pmValueSet *vsp = resp->vset[i]; + fprintf(f," %s :", pmIDStr(vsp->pmid)); + if (vsp->numval == 0) { + fprintf(f, " No values!\n"); + continue; + } + else if (vsp->numval < 0) { + fprintf(f, " %s\n", pmErrStr(vsp->numval)); + continue; + } + fprintf(f, " numval: %d", vsp->numval); + fprintf(f, " valfmt: %d", vsp->valfmt); + for (j = 0; j < vsp->numval; j++) { + pmValue *vp = &vsp->vlist[j]; + if (vsp->numval > 1 || vp->inst != PM_INDOM_NULL) { + fprintf(f," inst [%d]", vp->inst); + } + else + fprintf(f, " singular"); + if (dovalue) { + fprintf(f, " value "); + pmPrintValue(f, vsp->valfmt, PM_TYPE_U32, vp, 1); + } + fputc('\n', f); + } + } +} + +#endif + +/* Called when optFetch or _pmHash routines fail. This is terminal. */ +void +die(char *name, int sts) +{ + __pmNotifyErr(LOG_ERR, "%s error unrecoverable: %s\n", name, pmErrStr(sts)); + exit(1); +} + +optreq_t * +findoptreq(pmID pmid, int inst) +{ + __pmHashNode *hp; + optreq_t *rqp; + optreq_t *all_rqp = NULL; + int j; + + /* + * Note: + * The logic here assumes that for each metric-inst pair, there is + * at most one optreq_t structure, corresponding to the logging + * state of ON (mandatory or advisory) else OFF (mandatory). Other + * requests change the data structures, but do not leave optreq_t + * structures lying about, i.e. MAYBE (mandatory) is the default, + * and does not have to be explicitly stored, while OFF (advisory) + * reverts to MAYBE (mandatory). + * There is one exception to the above assumption, namely for + * cases where the initial specification includes "all" instances, + * then some later concurrent specification may refer to specific + * instances ... in this case, the specific optreq_t structure is + * the one that applies. + */ + + for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL; hp = hp->next) { + if (pmid != (pmID)hp->key) + continue; + rqp = (optreq_t *)hp->data; + if (rqp->r_numinst == 0) { + all_rqp = rqp; + continue; + } + for (j = 0; j < rqp->r_numinst; j++) + if (inst == rqp->r_instlist[j]) + return rqp; + } + + if (all_rqp != NULL) + return all_rqp; + else + return NULL; +} + +/* Determine whether a metric is currently known. Returns + * -1 if metric not known + * inclusive OR of the flags below if it is known + */ +#define MF_HAS_INDOM 0x1 /* has an instance domain */ +#define MF_HAS_ALL 0x2 /* has an "all instances" */ +#define MF_HAS_INST 0x4 /* has specific instance(s) */ +#define MF_HAS_MAND 0x8 /* has at least one inst with mandatory */ + /* logging (or is mandatory if no indom) */ +static int +find_metric(pmID pmid) +{ + __pmHashNode *hp; + optreq_t *rqp; + int result = 0; + int found = 0; + + for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL; hp = hp->next) { + if (pmid != (pmID)hp->key) + continue; + rqp = (optreq_t *)hp->data; + if (found++ == 0) + if (rqp->r_desc->indom != PM_INDOM_NULL) { + result |= MF_HAS_INDOM; + if (rqp->r_numinst == 0) + result |= MF_HAS_ALL; + else + result |= MF_HAS_INST; + } + if (PMLC_GET_MAND(((task_t *)(rqp->r_fetch->f_aux))->t_state)) + result |= MF_HAS_MAND; + } + return found ? result : -1; +} + +/* Find an optreq_t suitable for adding a new instance */ + +/* Add a new metric (given a pmValueSet and a pmDesc) to the specified task. + * Allocate and return a new task_t if the specified task pointer is nil. + * + * Note that this should only be called for metrics not currently in the + * logging data structure. All instances in the pmValueSet are added! + */ +static int +add_metric(pmValueSet *vsp, task_t **result) +{ + pmID pmid = vsp->pmid; + task_t *tp = *result; + optreq_t *rqp; + pmDesc *dp; + char *name; + int sts, i, need = 0; + + dp = (pmDesc *)malloc(sizeof(pmDesc)); + if (dp == NULL) { + __pmNoMem("add_metric: new pmDesc malloc", sizeof(pmDesc), PM_FATAL_ERR); + } + if ((sts = pmLookupDesc(pmid, dp)) < 0) + die("add_metric: lookup desc", sts); + if ((sts = pmNameID(pmid, &name)) < 0) + die("add_metric: lookup name", sts); + + /* allocate a new task if null task pointer passed in */ + if (tp == NULL) { + tp = calloc(1, sizeof(task_t)); + if (tp == NULL) { + __pmNoMem("add_metric: new task calloc", sizeof(task_t), PM_FATAL_ERR); + } + *result = tp; + } + + /* add metric (and any instances specified) to task */ + i = tp->t_numpmid++; + need = tp->t_numpmid * sizeof(pmID); + if (!(tp->t_pmidlist = (pmID *)realloc(tp->t_pmidlist, need))) + __pmNoMem("add_metric: new task pmidlist realloc", need, PM_FATAL_ERR); + need = tp->t_numpmid * sizeof(char *); + if (!(tp->t_namelist = (char **)realloc(tp->t_namelist, need))) + __pmNoMem("add_metric: new task namelist realloc", need, PM_FATAL_ERR); + need = tp->t_numpmid * sizeof(pmDesc); + if (!(tp->t_desclist = (pmDesc *)realloc(tp->t_desclist, need))) + __pmNoMem("add_metric: new task desclist realloc", need, PM_FATAL_ERR); + tp->t_pmidlist[i] = pmid; + tp->t_namelist[i] = name; + tp->t_desclist[i] = *dp; /* struct assignment */ + + rqp = (optreq_t *)calloc(1, sizeof(optreq_t)); + if (rqp == NULL) { + __pmNoMem("add_metric: new task optreq calloc", need, PM_FATAL_ERR); + } + rqp->r_desc = dp; + + /* Now copy instances if required. Remember that metrics with singular + * values actually have one instance specified to distinguish them from the + * "all instances" case (which has no instances). Use the pmDesc to check + * for this. + */ + if (dp->indom != PM_INDOM_NULL) + need = rqp->r_numinst = vsp->numval; + if (need) { + need *= sizeof(rqp->r_instlist[0]); + rqp->r_instlist = (int *)malloc(need); + if (rqp->r_instlist == NULL) { + __pmNoMem("add_metric: new task optreq instlist malloc", need, + PM_FATAL_ERR); + } + for (i = 0; i < vsp->numval; i++) + rqp->r_instlist[i] = vsp->vlist[i].inst; + } + + /* Add new metric to task's fetchgroup(s) and global hash table */ + __pmOptFetchAdd(&tp->t_fetch, rqp); + linkback(tp); + if ((sts = __pmHashAdd(pmid, (void *)rqp, &pm_hash)) < 0) + die("add_metric: __pmHashAdd", sts); + return 0; +} + +/* Return true if a request for a new logging state (newstate) will be honoured + * when current state is curstate. + */ +static int +update_ok(int curstate, int newstate) +{ + /* If new state is advisory and current is mandatory, reject request. + * Any new mandatory state is accepted. If the new state is advisory + * and the current state is advisory, it is accepted. + * Note that a new state of maybe (mandatory maybe) counts as mandatory + */ + if (PMLC_GET_MAND(newstate) == 0 && PMLC_GET_MAYBE(newstate) == 0 && + PMLC_GET_MAND(curstate)) + return 0; + else + return 1; +} + +/* Given a task and a pmID, find an optreq_t associated with the task suitable + * for inserting a new instance into. + * The one with the smallest number of instances is chosen. We could also + * have just used the first, but smallest-first gives a more even distribution. + */ +static optreq_t * +find_instoptreq(task_t *tp, pmID pmid) +{ + optreq_t *result = NULL; + optreq_t *rqp; + int ni = 0; + __pmHashNode *hp; + + for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL; + hp = hp->next) { + if (pmid != (pmID)hp->key) + continue; + rqp = (optreq_t *)hp->data; + if ((task_t *)rqp->r_fetch->f_aux != tp) + continue; + if (rqp->r_numinst == 0) + continue; /* don't want "all instances" cases */ + if (ni == 0 || rqp->r_numinst < ni) { + result = rqp; + ni = rqp->r_numinst; + } + } + return result; +} + +/* Delete an optreq_t from its task, free it and remove it from the hash list. + */ +static void +del_optreq(optreq_t *rqp) +{ + int sts; + task_t *tp = (task_t *)rqp->r_fetch->f_aux; + + if ((sts = __pmOptFetchDel(&tp->t_fetch, rqp)) < 0) + die("del_optreq: __pmOptFetchDel", sts); + if ((sts = __pmHashDel(rqp->r_desc->pmid, (void *)rqp, &pm_hash)) < 0) + die("del_optreq: __pmHashDel", sts); + free(rqp->r_desc); + if (rqp->r_numinst) + free(rqp->r_instlist); + free(rqp); + /* TODO: remove pmid from task if that was the last optreq_t for it */ + /* TODO: remove task if last pmid removed */ +} + +/* Delete every instance of a given metric from the data structure. + * The pmid is deleted from the pmidlist of every task containing an instance. + * Return a pointer to the first pmDesc found (the only thing salvaged from the + * smoking ruins), or nil if no instances were found. + */ +static pmDesc * +del_insts(pmID pmid) +{ + optreq_t *rqp; + __pmHashNode *hp; + task_t *tp; + pmDesc *dp = NULL; + int i, sts, keep; + + for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL; ) { + /* Do that BEFORE we nuke the node */ + __pmHashNode * nextnode = hp->next; + + if (pmid == (pmID)hp->key) { + rqp = (optreq_t *)hp->data; + tp = (task_t *)rqp->r_fetch->f_aux; + if ((sts = __pmOptFetchDel(&tp->t_fetch, rqp)) < 0) + die("del_insts: __pmOptFetchDel", sts); + if ((sts = __pmHashDel(pmid, (void *)rqp, &pm_hash)) < 0) + die("del_insts: __pmHashDel", sts); + + /* save the first pmDesc pointer for return and subsequent + * re-use, but free all the others + */ + if (dp != NULL) + free(rqp->r_desc); + else + dp = rqp->r_desc; + + if (rqp->r_numinst) + free(rqp->r_instlist); + free(rqp); + + /* remove pmid from the task's pmid list */ + for (i = 0; i < tp->t_numpmid; i++) + if (tp->t_pmidlist[i] == pmid) + break; + keep = (tp->t_numpmid - 1 - i) * sizeof(tp->t_pmidlist[0]); + if (keep) { + memmove(&tp->t_pmidlist[i], &tp->t_pmidlist[i+1], keep); + memmove(&tp->t_desclist[i], &tp->t_desclist[i+1], keep); + memmove(&tp->t_namelist[i], &tp->t_namelist[i+1], keep); + } + + /* don't bother shrinking the pmidlist */ + tp->t_numpmid--; + if (tp->t_numpmid == 0) { + /* TODO: nuke the task if that was the last pmID */ + } + } + hp = nextnode; + } + + return dp; +} + +/* Update an existing metric (given a pmValueSet) adding it to the specified + * task. Allocate and return a new task_t if the specified task pointer is nil. + */ +static int +update_metric(pmValueSet *vsp, int reqstate, int mflags, task_t **result) +{ + pmID pmid = vsp->pmid; + task_t *ntp = *result; /* pointer to new task */ + task_t *ctp; /* pointer to current task */ + optreq_t *rqp; + pmDesc *dp; + int i, j, inst; + int sts, need = 0; + int addpmid = 0; + int freedp; + + /* allocate a new task if null task pointer passed in */ + if (ntp == NULL) { + ntp = calloc(1, sizeof(task_t)); + if (ntp == NULL) { + __pmNoMem("update_metric: new task calloc", sizeof(task_t), + PM_FATAL_ERR); + } + *result = ntp; + } + + if ((mflags & MF_HAS_INDOM) == 0) { + rqp = findoptreq(pmid, 0); + ctp = (task_t *)(rqp->r_fetch->f_aux); + if (!update_ok(ctp->t_state, reqstate)) + return 1; + + /* if the new state is advisory off, just remove the metric */ + if ((PMLC_GET_MAYBE(reqstate)) || + (PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_ON(reqstate) == 0)) + del_optreq(rqp); + else { + /* update the optreq. For single valued metrics there are no + * instances involved so the sole optreq can just be re-used. + */ + if ((sts = __pmOptFetchDel(&ctp->t_fetch, rqp)) < 0) + die("update_metric: 1 metric __pmOptFetchDel", sts); + __pmOptFetchAdd(&ntp->t_fetch, rqp); + linkback(ntp); + addpmid = 1; + } + } + else { + /* metric has an instance domain */ + if (vsp->numval > 0) { + /* tricky: since optFetch can't handle instance profiles of the + * form "all except these specific instances", and managing it + * manually is just too hard, reject requests for specific + * metric instances if "all instances" of the metric are already + * being logged. + * Note: advisory off "all instances" is excepted since ANY request + * overrides and advisory off. E.g. "advisory off all" followed by + * "advisory on someinsts" turns on advisory logging for + * "someinsts". mflags will be zero for "advisory off" metrics. + */ + if (mflags & MF_HAS_ALL) + return 1; /* can't turn "all" into specific insts */ + + for (i = 0; i < vsp->numval; i++) { + dp = NULL; + freedp = 0; + inst = vsp->vlist[i].inst; + rqp = findoptreq(pmid, inst); + if (rqp != NULL) { + dp = rqp->r_desc; + ctp = (task_t *)(rqp->r_fetch->f_aux); + /* no work required if new task and current are the same */ + if (ntp == ctp) + continue; + if (!update_ok(ctp->t_state, reqstate)) + continue; + + /* remove inst's group from current task */ + if ((sts = __pmOptFetchDel(&ctp->t_fetch, rqp)) < 0) + die("update_metric: instance add __pmOptFetchDel", sts); + + /* put group back if there are any instances left */ + if (rqp->r_numinst > 1) { + /* remove inst from group */ + for (j = 0; j < rqp->r_numinst; j++) + if (inst == rqp->r_instlist[j]) + break; + /* don't call memmove to move zero bytes */ + if (j < rqp->r_numinst - 1) + memmove(&rqp->r_instlist[j], &rqp->r_instlist[j+1], + (rqp->r_numinst - 1 - j) * + sizeof(rqp->r_instlist[0])); + rqp->r_numinst--; + /* (don't bother realloc-ing the instlist to a smaller size) */ + + __pmOptFetchAdd(&ctp->t_fetch, rqp); + linkback(ctp); + /* no need to update hash list, rqp already there */ + } + /* if that was the last instance, free the group */ + else { + if (( sts = __pmHashDel(pmid, (void *)rqp, &pm_hash)) < 0) + die("update_metric: instance __pmHashDel", sts); + freedp = 1; + free(rqp->r_instlist); + free(rqp); + } + } + + /* advisory off (mandatory maybe) metrics don't get put into + * the data structure + */ + if (PMLC_GET_MAYBE(reqstate) || + (PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_ON(reqstate) == 0)) { + if (freedp) + free(dp); + continue; + } + addpmid = 1; + + /* try to find an existing optreq_t for the instance */ + rqp = find_instoptreq(ntp, pmid); + if (rqp != NULL) { + if ((sts = __pmOptFetchDel(&ntp->t_fetch, rqp)) < 0) + die("update_metric: instance add __pmOptFetchDel", sts); + } + /* no existing optreq_t found, allocate & populate a new one */ + else { + rqp = (optreq_t *)calloc(1, sizeof(optreq_t)); + if (rqp == NULL) { + __pmNoMem("update_metric: optreq calloc", + sizeof(optreq_t), PM_FATAL_ERR); + } + /* if the metric existed but the instance didn't, we don't + * have a valid pmDesc (dp), so find one. + */ + if (dp == NULL) { + /* find metric and associated pmDesc */ + __pmHashNode *hp; + + for (hp = __pmHashSearch(pmid, &pm_hash); + hp != NULL; hp = hp->next) { + if (pmid == (pmID)hp->key) + break; + } + assert(hp != NULL); + dp = ((optreq_t *)hp->data)->r_desc; + } + /* recycle pmDesc from the old group, if possible */ + if (freedp) { + rqp->r_desc = dp; + freedp = 0; + } + /* otherwise allocate & copy a new pmDesc via dp */ + else { + need = sizeof(pmDesc); + rqp->r_desc = (pmDesc *)malloc(need); + if (rqp->r_desc == NULL) { + __pmNoMem("update_metric: new inst pmDesc malloc", + need, PM_FATAL_ERR); + } + memcpy(rqp->r_desc, dp, need); + } + if ((sts = __pmHashAdd(pmid, (void *)rqp, &pm_hash)) < 0) + die("update_metric: __pmHashAdd", sts); + } + + need = (rqp->r_numinst + 1) * sizeof(rqp->r_instlist[0]); + rqp->r_instlist = (int *)realloc(rqp->r_instlist, need); + if (rqp->r_instlist == NULL) { + __pmNoMem("update_metric: inst list resize", need, + PM_FATAL_ERR); + } + rqp->r_instlist[rqp->r_numinst++] = inst; + __pmOptFetchAdd(&ntp->t_fetch, rqp); + linkback(ntp); + if (freedp) + free(dp); + } + } + /* the vset has numval == 0, a request for "all instances" */ + else { + /* if the metric is a singular instance that has mandatory logging + * or has at least one instance with mandatory logging on, a + * request for advisory logging cannot be honoured + */ + if ((mflags & MF_HAS_MAND) && + PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_MAYBE(reqstate) == 0) + return 1; + + if (mflags & MF_HAS_ALL) { + /* if there is an "all instances" for the metric, it will be + * the only optreq_t for the metric + */ + rqp = findoptreq(pmid, 0); + ctp = (task_t *)rqp->r_fetch->f_aux; + + /* if the metric is "advisory on, all instances" and the + * request is for "mandatory maybe, all instances" the current + * advisory logging state of the metric is retained + */ + if (PMLC_GET_MAND(ctp->t_state) == 0 && PMLC_GET_MAYBE(reqstate)) + return 0; + + /* advisory off & mandatory maybe metrics don't get put into + * the data structure + */ + if (PMLC_GET_MAYBE(reqstate) || + (PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_ON(reqstate) == 0)) { + del_optreq(rqp); + return 0; + } + + addpmid = 1; + if ((sts = __pmOptFetchDel(&ctp->t_fetch, rqp)) < 0) + die("update_metric: all inst __pmOptFetchDel", sts); + /* don't delete from hash list, rqp re-used */ + __pmOptFetchAdd(&ntp->t_fetch, rqp); + linkback(ntp); + } + else { + /* there are one or more specific instances for the metric. + * The metric cannot have an "all instances" at the same time. + * + * if the request is for "mandatory maybe, all instances" and + * the only instances of the metric all have advisory logging + * on, retain the current advisory semantics. + */ + if (PMLC_GET_MAYBE(reqstate) && + (mflags & MF_HAS_INST) && !(mflags & MF_HAS_MAND)) + return 0; + + dp = del_insts(pmid); + + /* advisory off (mandatory maybe) metrics don't get put into + * the data structure + */ + if (PMLC_GET_MAYBE(reqstate) || + (PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_ON(reqstate) == 0)) { + free(dp); + return 0; + } + + addpmid = 1; + rqp = (optreq_t *)calloc(1, sizeof(optreq_t)); + if (rqp == NULL) { + __pmNoMem("update_metric: all inst calloc", + sizeof(optreq_t), PM_FATAL_ERR); + } + rqp->r_desc = dp; + __pmOptFetchAdd(&ntp->t_fetch, rqp); + linkback(ntp); + if ((sts = __pmHashAdd(pmid, (void *)rqp, &pm_hash)) < 0) + die("update_metric: all inst __pmHashAdd", sts); + } + } + } + + if (!addpmid) + return 0; + + /* add pmid to new task if not already there */ + for (i = 0; i < ntp->t_numpmid; i++) + if (pmid == ntp->t_pmidlist[i]) + break; + if (i >= ntp->t_numpmid) { + pmDesc desc; + char *name; + int need; + + if ((sts = pmLookupDesc(pmid, &desc)) < 0) + die("update_metric: cannot lookup desc", sts); + if ((sts = pmNameID(pmid, &name)) < 0) + die("update_metric: cannot lookup name", sts); + + need = (ntp->t_numpmid + 1) * sizeof(pmID); + if (!(ntp->t_pmidlist = (pmID *)realloc(ntp->t_pmidlist, need))) + __pmNoMem("update_metric: grow task pmidlist", need, PM_FATAL_ERR); + need = (ntp->t_numpmid + 1) * sizeof(char *); + if (!(ntp->t_namelist = (char **)realloc(ntp->t_namelist, need))) + __pmNoMem("update_metric: grow task namelist", need, PM_FATAL_ERR); + need = (ntp->t_numpmid + 1) * sizeof(pmDesc); + if (!(ntp->t_desclist = (pmDesc *)realloc(ntp->t_desclist, need))) + __pmNoMem("update_metric: grow task desclist", need, PM_FATAL_ERR); + i = ntp->t_numpmid; + ntp->t_pmidlist[i] = pmid; + ntp->t_namelist[i] = name; + ntp->t_desclist[i] = desc; + ntp->t_numpmid++; + } + return 0; +} + +/* Given a state and a delta, return the first matching task. + * Return NULL if a matching task was not found. + */ +task_t * +find_task(int state, struct timeval *delta) +{ + task_t *tp; + + for (tp = tasklist; tp != NULL; tp = tp->t_next) { + if (state == (tp->t_state & 0x3) && /* MAND|ON */ + delta->tv_sec == tp->t_delta.tv_sec && + delta->tv_usec == tp->t_delta.tv_usec) + break; + } + return tp; +} + +/* Return a mask containing the history flags for a given metric/instance. + * the history flags indicate whether the metric/instance is in the log at all + * and whether the last fetch of the metric/instance was successful. + * + * The result is suitable for ORing into the result returned by a control log + * request. + */ +static int +gethistflags(pmID pmid, int inst) +{ + __pmHashNode *hp; + pmidhist_t *php; + insthist_t *ihp; + int i, found; + int val; + + for (hp = __pmHashSearch(pmid, &hist_hash); hp != NULL; hp = hp->next) + if ((pmID)hp->key == pmid) + break; + if (hp == NULL) + return 0; + php = (pmidhist_t *)hp->data; + ihp = &php->ph_instlist[0]; + val = 0; + if (php->ph_indom != PM_INDOM_NULL) { + for (i = 0; i < php->ph_numinst; i++, ihp++) + if (ihp->ih_inst == inst) + break; + found = i < php->ph_numinst; + } + else + found = php->ph_numinst > 0; + if (found) { + PMLC_SET_INLOG(val, 1); + val |= ihp->ih_flags; /* only "available flag" is ever set */ + } + return val; +} + +/* take a pmResult (from a control log request) and half-clone it: return a + * pointer to a new pmResult struct which shares the pmValueSets in the + * original that have numval > 0, and has null pointers for the pmValueSets + * in the original with numval <= 0 + */ +static pmResult * +siamise_request(pmResult *request) +{ + int i, need; + pmValueSet *vsp; + pmResult *result; + + need = sizeof(pmResult) + (request->numpmid - 1) * sizeof(pmValueSet *); + result = (pmResult *)malloc(need); + if (result == NULL) { + __pmNoMem("siamise_request: malloc pmResult", need, PM_FATAL_ERR); + } + for (i = 0; i < request->numpmid; i++) { + vsp = request->vset[i]; + if (vsp->numval > 0) + result->vset[i] = request->vset[i]; + else + result->vset[i] = NULL; + } + result->timestamp = request->timestamp; /* structure assignment */ + result->numpmid = request->numpmid; + + return result; +} + +/* Temporarily borrow a bit in the metric/instance history to indicate that + * the instance currently exists in the instance domain. The macros below + * set and get the bit, which is cleared after we are finished with it here. + */ + +#define PMLC_SET_USEINDOM(val, flag) (val = (val & ~0x1000) | (flag << 12 )) +#define PMLC_GET_USEINDOM(val) ((val & 0x1000) >> 12) + +/* create a pmValueSet large enough to contain the union of the current + * instance domain of the specified metric and any previous instances from + * the history list. + */ +static pmValueSet * +build_vset(pmID pmid, int usehist) +{ + __pmHashNode *hp; + pmidhist_t *php = NULL; + insthist_t *ihp; + int need = 0; + int i, numindom = 0; + pmDesc desc; + int have_desc; + int *instlist = NULL; + char **namelist = NULL; + pmValueSet *vsp; + + if (usehist) { + /* find the number of instances of the metric in the history (1 if + * single-valued metric) + */ + for (hp = __pmHashSearch(pmid, &hist_hash); hp != NULL; hp = hp->next) + if ((pmID)hp->key == pmid) + break; + if (hp != NULL) { + php = (pmidhist_t *)hp->data; + need = php->ph_numinst; + } + } + /* + * get the current instance domain, so that if the metric hasn't been + * logged yet a sensible result is returned. + */ + if ((have_desc = pmLookupDesc(pmid, &desc)) < 0) + goto no_info; + if (desc.indom == PM_INDOM_NULL) + need = 1; /* will be same in history */ + else { + int j; + + if ((numindom = pmGetInDom(desc.indom, &instlist, &namelist)) < 0) { + have_desc = numindom; + goto no_info; + } + /* php will be null if usehist is false or there is no history yet */ + if (php == NULL) + need = numindom; /* no history => use indom */ + else + for (i = 0; i < numindom; i++) { + int inst = instlist[i]; + + for (j = 0; j < php->ph_numinst; j++) + if (inst == php->ph_instlist[j].ih_inst) + break; + /* + * if instance is in history but not instance domain, leave + * extra space for it in vset, otherwise use the USEINDOM + * flag to avoid another NxM comparison when building the vset + * instances later. + */ + if (j >= php->ph_numinst) + need++; + else + PMLC_SET_USEINDOM(php->ph_instlist[j].ih_flags, 1); + } + } + +no_info: + + need = sizeof(pmValueSet) + (need - 1) * sizeof(pmValue); + vsp = (pmValueSet *)malloc(need); + if (vsp == NULL) { + __pmNoMem("build_vset for control/enquire", need, PM_FATAL_ERR); + } + vsp->pmid = pmid; + if (have_desc < 0) { + vsp->numval = have_desc; + } + else if (desc.indom == PM_INDOM_NULL) { + vsp->vlist[0].inst = PM_IN_NULL; + vsp->numval = 1; + } + else { + int j; + + i = 0; + /* get instances out of instance domain first */ + if (numindom > 0) + for (j = 0; j < numindom; j++) + vsp->vlist[i++].inst = instlist[j]; + + /* then any not in instance domain from history */ + if (php != NULL) { + ihp = &php->ph_instlist[0]; + for (j = 0; j < php->ph_numinst; j++, ihp++) + if (PMLC_GET_USEINDOM(ihp->ih_flags)) + /* it's already in the indom */ + PMLC_SET_USEINDOM(ihp->ih_flags, 0); + else + vsp->vlist[i++].inst = ihp->ih_inst; + } + vsp->numval = i; + } + if (instlist) + free(instlist); + if (namelist) + free(namelist); + + return vsp; +} + +static int +do_control(__pmPDU *pb) +{ + int sts; + int control; + int state; + int delta; + pmResult *request; + pmResult *result; + int siamised = 0; /* the verb from siamese (as in twins) */ + int i; + int j; + int val; + pmValueSet *vsp; + optreq_t *rqp; + task_t *tp; + time_t now; + int reqstate = 0; + + /* + * TODO - encoding for logging interval in requests and results? + */ + if ((sts = __pmDecodeLogControl(pb, &request, &control, &state, &delta)) < 0) + return sts; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, "do_control: control=%d state=%d delta=%d request ...\n", + control, state, delta); + dumpcontrol(stderr, request, 0); + } +#endif + + if (control == PM_LOG_MANDATORY || control == PM_LOG_ADVISORY) { + time(&now); + fprintf(stderr, "\n%s", ctime(&now)); + fprintf(stderr, "pmlc request from %s: %s", + pmlc_host, control == PM_LOG_MANDATORY ? "mandatory" : "advisory"); + if (state == PM_LOG_ON) { + if (delta == 0) + fprintf(stderr, " on once\n"); + else + fprintf(stderr, " on %.1f sec\n", (float)delta/1000); + } + else if (state == PM_LOG_OFF) + fprintf(stderr, " off\n"); + else + fprintf(stderr, " maybe\n"); + } + + /* + * access control checks + */ + sts = 0; + switch (control) { + case PM_LOG_MANDATORY: + if (denyops & PM_OP_LOG_MAND) + sts = PM_ERR_PERMISSION; + break; + + case PM_LOG_ADVISORY: + if (denyops & PM_OP_LOG_ADV) + sts = PM_ERR_PERMISSION; + break; + + case PM_LOG_ENQUIRE: + /* + * Don't need to check [access] as you have to have _some_ + * permission (at least one of PM_OP_LOG_ADV or PM_OP_LOG_MAND + * and PM_OP_LOG_ENQ) to make a connection ... and if you + * have either PM_OP_LOG_ADV or PM_OP_LOG_MAND it makes no + * sense to deny PM_OP_LOG_ENQ operations. + */ + break; + + default: + fprintf(stderr, "Bad control PDU type %d\n", control); + sts = PM_ERR_IPC; + break; + } + if (sts < 0) { + fprintf(stderr, "Error: %s\n", pmErrStr(sts)); + if ((sts = __pmSendError(clientfd, FROM_ANON, sts)) < 0) + __pmNotifyErr(LOG_ERR, + "do_control: error sending Error PDU to client: %s\n", + pmErrStr(sts)); + pmFreeResult(request); + return sts; + } + + /* handle everything except PM_LOG_ENQUIRE */ + if (control == PM_LOG_MANDATORY || control == PM_LOG_ADVISORY) { + /* update the logging status of metrics */ + + task_t *newtp = NULL; /* task for metrics/insts in request */ + struct timeval tdelta = { 0 }; + int newtask; + int mflags; + + /* convert state and control to the bitmask used in pmlogger and values + * returned in results. Remember that reqstate starts with nothing on. + */ + if (state == PM_LOG_ON) + PMLC_SET_ON(reqstate, 1); + else + PMLC_SET_ON(reqstate, 0); + if (control == PM_LOG_MANDATORY) { + if (state == PM_LOG_MAYBE) + /* mandatory+maybe => maybe+advisory+off */ + PMLC_SET_MAYBE(reqstate, 1); + else + PMLC_SET_MAND(reqstate, 1); + } + + /* try to find an existing task for the request + * Never return a "once only" task, it may have gone off already and just + * be hanging around like a bad smell. + */ + if (delta != 0) { + tdelta.tv_sec = delta / 1000; + tdelta.tv_usec = (delta % 1000) * 1000; + newtp = find_task(reqstate, &tdelta); + } + newtask = (newtp == NULL); + + for (i = 0; i < request->numpmid; i++) { + vsp = request->vset[i]; + if (vsp->numval < 0) + /* + * request is malformed, as we cannot control logging + * for an undefined instance ... there is no way to + * return an error from here, so simply ignore this + * metric + */ + continue; + mflags = find_metric(vsp->pmid); + if (mflags < 0) { + /* only add new metrics if they are ON or MANDATORY OFF + * Careful: mandatory+maybe is mandatory+maybe+off + */ + if (PMLC_GET_ON(reqstate) || + (PMLC_GET_MAND(reqstate) && !PMLC_GET_MAYBE(reqstate))) + add_metric(vsp, &newtp); + } + else + /* already a specification for this metric */ + update_metric(vsp, reqstate, mflags, &newtp); + } + + /* schedule new logging task if new metric(s) specified */ + if (newtask && newtp != NULL) { + if (newtp->t_fetch == NULL) { + /* the new task ended up with no fetch groups, throw it away */ + if (newtp->t_pmidlist != NULL) + free(newtp->t_pmidlist); + free(newtp); + } + else { + /* link new task into tasklist */ + newtp->t_next = tasklist; + tasklist = newtp; + + /* use only the MAND/ADV and ON/OFF bits of reqstate */ + newtp->t_state = PMLC_GET_STATE(reqstate); + if (PMLC_GET_ON(reqstate)) { + newtp->t_delta = tdelta; + newtp->t_afid = __pmAFregister(&tdelta, (void *)newtp, + log_callback); + } + else + newtp->t_delta.tv_sec = newtp->t_delta.tv_usec = 0; + linkback(newtp); + } + } + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + dumpit(); +#endif + + /* just ignore advisory+maybe---the returned pmResult will have the metrics + * in their original state indicating that the request could not be + * satisfied. + */ + + result = request; + result->timestamp.tv_sec = result->timestamp.tv_usec = 0; /* for purify */ + /* write the current state of affairs into the result _pmResult */ + for (i = 0; i < request->numpmid; i++) { + + if (control == PM_LOG_MANDATORY || control == PM_LOG_ADVISORY) { + char *p; + + sts = pmNameID(request->vset[i]->pmid, &p); + if (sts < 0) + fprintf(stderr, " metric: %s", pmIDStr(request->vset[i]->pmid)); + else { + fprintf(stderr, " metric: %s", p); + free(p); + } + } + + if (request->vset[i]->numval <= 0 && !siamised) { + result = siamise_request(request); + siamised = 1; + } + /* + * pmids with numval <= 0 in the request have a null vset ptr in the + * in the corresponding place in the siamised result. + */ + if (result->vset[i] != NULL) + vsp = result->vset[i]; + else { + /* the result should also contain the history for an all instances + * enquire request. Control requests just get the current indom + * since the user of pmlc really wants to see what's being logged + * now rather than in the past. + */ + vsp = build_vset(request->vset[i]->pmid, control == PM_LOG_ENQUIRE); + result->vset[i] = vsp; + } + vsp->valfmt = PM_VAL_INSITU; + for (j = 0; j < vsp->numval; j++) { + rqp = findoptreq(vsp->pmid, vsp->vlist[j].inst); + val = 0; + if (rqp == NULL) { + PMLC_SET_STATE(val, 0); + PMLC_SET_DELTA(val, 0); + } + else { + tp = rqp->r_fetch->f_aux; + PMLC_SET_STATE(val, tp->t_state); + PMLC_SET_DELTA(val, (tp->t_delta.tv_sec*1000 + tp->t_delta.tv_usec/1000)); + } + + val |= gethistflags(vsp->pmid, vsp->vlist[j].inst); + vsp->vlist[j].value.lval = val; + + if (control == PM_LOG_MANDATORY || control == PM_LOG_ADVISORY) { + int expstate = 0; + int statemask = 0; + int expdelta; + if (rqp != NULL && rqp->r_desc->indom != PM_INDOM_NULL) { + char *p; + if (j == 0) + fputc('\n', stderr); + if (pmNameInDom(rqp->r_desc->indom, vsp->vlist[j].inst, &p) >= 0) { + fprintf(stderr, " instance: %s", p); + free(p); + } + else + fprintf(stderr, " instance: #%d", vsp->vlist[j].inst); + } + else { + /* no pmDesc ... punt */ + if (vsp->numval > 1 || vsp->vlist[j].inst != PM_IN_NULL) { + if (j == 0) + fputc('\n', stderr); + fprintf(stderr, " instance: #%d", vsp->vlist[j].inst); + } + } + if (state != PM_LOG_MAYBE) { + if (control == PM_LOG_MANDATORY) + PMLC_SET_MAND(expstate, 1); + else + PMLC_SET_MAND(expstate, 0); + if (state == PM_LOG_ON) + PMLC_SET_ON(expstate, 1); + else + PMLC_SET_ON(expstate, 0); + PMLC_SET_MAND(statemask, 1); + PMLC_SET_ON(statemask, 1); + } + else { + PMLC_SET_MAND(expstate, 0); + PMLC_SET_MAND(statemask, 1); + } + expdelta = PMLC_GET_ON(expstate) ? delta : 0; + if ((PMLC_GET_STATE(val) & statemask) != expstate || + PMLC_GET_DELTA(val) != expdelta) + fprintf(stderr, " [request failed]"); + fputc('\n', stderr); + } + } + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + __pmDumpResult(stderr, result); + } +#endif + + if ((sts = __pmSendResult(clientfd, FROM_ANON, result)) < 0) + __pmNotifyErr(LOG_ERR, + "do_control: error sending Error PDU to client: %s\n", + pmErrStr(sts)); + + if (siamised) { + for (i = 0; i < request->numpmid; i++) + if (request->vset[i]->numval <= 0) + free(result->vset[i]); + free(result); + } + pmFreeResult(request); + + return 0; +} + +/* + * sendstatus + */ +static int +sendstatus(void) +{ + int rv; + int end; + int version; + static int firsttime = 1; + static char *tzlogger; + struct timeval now; + + if (firsttime) { + tzlogger = __pmTimezone(); + firsttime = 0; + } + + if ((version = __pmVersionIPC(clientfd)) < 0) + return version; + + if (version >= LOG_PDU_VERSION2) { + __pmLoggerStatus ls; + + if ((ls.ls_state = logctl.l_state) == PM_LOG_STATE_NEW) + ls.ls_start.tv_sec = ls.ls_start.tv_usec = 0; + else + memcpy(&ls.ls_start, &logctl.l_label.ill_start, sizeof(ls.ls_start)); + memcpy(&ls.ls_last, &last_stamp, sizeof(ls.ls_last)); + __pmtimevalNow(&now); + ls.ls_timenow.tv_sec = (__int32_t)now.tv_sec; + ls.ls_timenow.tv_usec = (__int32_t)now.tv_usec; + ls.ls_vol = logctl.l_curvol; + ls.ls_size = ftell(logctl.l_mfp); + assert(ls.ls_size >= 0); + + /* be careful of buffer size mismatches when copying strings */ + end = sizeof(ls.ls_hostname) - 1; + strncpy(ls.ls_hostname, logctl.l_label.ill_hostname, end); + ls.ls_hostname[end] = '\0'; + /* BTW, that string should equal pmcd_host[]. */ + + /* NB: FQDN cleanup: there is no such thing as 'the fully + qualified domain name' of a server: it may have several or + none; the set may have changed since the time the log + archive was collected. Now that we store the then-current + pmcd.hostname in the ill_hostname (and thus get it reported + in ls_hostname), we could pass something else informative + in the ls_fqdn slot. Namely, pmcd_host_conn[], which is the + access path pmlogger's using to get to the pmcd. */ + end = sizeof(ls.ls_fqdn) - 1; + strncpy(ls.ls_fqdn, pmcd_host_conn, end); + ls.ls_fqdn[end] = '\0'; + + end = sizeof(ls.ls_tz) - 1; + strncpy(ls.ls_tz, logctl.l_label.ill_tz, end); + ls.ls_tz[end] = '\0'; + end = sizeof(ls.ls_tzlogger) - 1; + if (tzlogger != NULL) + strncpy(ls.ls_tzlogger, tzlogger, end); + else + end = 0; + ls.ls_tzlogger[end] = '\0'; + + rv = __pmSendLogStatus(clientfd, &ls); + } + else + rv = PM_ERR_IPC; + return rv; +} + +static int +do_request(__pmPDU *pb) +{ + int sts; + int type; + + if ((sts = __pmDecodeLogRequest(pb, &type)) < 0) { + __pmNotifyErr(LOG_ERR, "do_request: error decoding PDU: %s\n", pmErrStr(sts)); + return PM_ERR_IPC; + } + + switch (type) { + case LOG_REQUEST_STATUS: + /* + * Don't need to check [access] as you have to have _some_ + * permission (at least one of PM_OP_LOG_ADV or PM_OP_LOG_MAND + * and PM_OP_LOG_ENQ) to make a connection ... and if you + * have either PM_OP_LOG_ADV or PM_OP_LOG_MAND it makes no + * sense to deny LOG_REQUEST_STATUS operations. + * Also, this is needed internally by pmlc to discover pmcd's + * hostname. + */ + sts = sendstatus(); + break; + + case LOG_REQUEST_NEWVOLUME: + if (denyops & PM_OP_LOG_MAND) + sts = __pmSendError(clientfd, FROM_ANON, PM_ERR_PERMISSION); + else { + sts = newvolume(VOL_SW_PMLC); + if (sts >= 0) + sts = logctl.l_label.ill_vol; + sts = __pmSendError(clientfd, FROM_ANON, sts); + } + break; + + case LOG_REQUEST_SYNC: + /* + * Don't need to check access controls, as this is now + * a no-op with unbuffered I/O from pmlogger. + * + * Do nothing, simply send status 0 back to pmlc. + */ + sts = __pmSendError(clientfd, FROM_ANON, 0); + break; + + /* + * QA support ... intended for error injection + * If the request is > QA_OFF then this is a code to enable + * a specific style of error behaviour. If the request + * is QA_OFF, this disables the error behaviour. + * + * Supported behaviours. + * QA_SLEEPY + * After this exchange with pmlc, sleep for 5 seconds + * after each incoming pmlc request ... allows testing + * of timeout logic in pmlc + */ + + case QA_OFF: + if (denyops & PM_OP_LOG_MAND) + sts = __pmSendError(clientfd, FROM_ANON, PM_ERR_PERMISSION); + else { + qa_case = 0; + sts = __pmSendError(clientfd, FROM_ANON, 0); + } + break; + + case QA_SLEEPY: + if (denyops & PM_OP_LOG_MAND) + sts = __pmSendError(clientfd, FROM_ANON, PM_ERR_PERMISSION); + else { + qa_case = type; + sts = __pmSendError(clientfd, FROM_ANON, 0); + } + break; + + default: + fprintf(stderr, "do_request: bad request type %d\n", type); + sts = PM_ERR_IPC; + break; + } + return sts; +} + +static int +do_creds(__pmPDU *pb) +{ + int i; + int sts; + int version = UNKNOWN_VERSION; + int credcount; + int sender; + __pmCred *credlist = NULL; + + if ((sts = __pmDecodeCreds(pb, &sender, &credcount, &credlist)) < 0) { + __pmNotifyErr(LOG_ERR, "do_creds: error decoding PDU: %s\n", pmErrStr(sts)); + return PM_ERR_IPC; + } + + for (i = 0; i < credcount; i++) { + if (credlist[i].c_type == CVERSION) { + version = credlist[i].c_vala; + if ((sts = __pmSetVersionIPC(clientfd, version)) < 0) { + free(credlist); + return sts; + } + } + } + + if (credlist) + free(credlist); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL1) + fprintf(stderr, "do_creds: pmlc version=%d\n", version); +#endif + + return sts; +} + +/* + * Service a request from the pmlogger client. + * Return non-zero if the client has closed the connection. + */ +int +client_req(void) +{ + int sts; + __pmPDU *pb; + __pmPDUHdr *php; + int pinpdu; + + if ((pinpdu = sts = __pmGetPDU(clientfd, ANY_SIZE, TIMEOUT_DEFAULT, &pb)) <= 0) { + if (sts != 0) + fprintf(stderr, "client_req: %s\n", pmErrStr(sts)); + return 1; + } + if (qa_case == QA_SLEEPY) { + /* error injection - delay before processing and responding */ + sleep(5); + } + php = (__pmPDUHdr *)pb; + sts = 0; + + switch (php->type) { + case PDU_CREDS: /* version 2 PDU */ + sts = do_creds(pb); + break; + case PDU_LOG_REQUEST: /* version 2 PDU */ + sts = do_request(pb); + break; + case PDU_LOG_CONTROL: /* version 2 PDU */ + sts = do_control(pb); + break; + default: /* unknown PDU */ + fprintf(stderr, "client_req: bad PDU type 0x%x\n", php->type); + sts = PM_ERR_IPC; + break; + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + + if (sts >= 0) + return 0; + else { + /* the client isn't playing by the rules */ + __pmSendError(clientfd, FROM_ANON, sts); + return 1; + } +} |