summaryrefslogtreecommitdiff
path: root/src/pmlc
diff options
context:
space:
mode:
Diffstat (limited to 'src/pmlc')
-rw-r--r--src/pmlc/GNUmakefile46
-rw-r--r--src/pmlc/actions.c868
-rw-r--r--src/pmlc/gram.y336
-rw-r--r--src/pmlc/lex.l268
-rw-r--r--src/pmlc/pmlc.c398
-rw-r--r--src/pmlc/pmlc.h95
-rw-r--r--src/pmlc/util.c546
7 files changed, 2557 insertions, 0 deletions
diff --git a/src/pmlc/GNUmakefile b/src/pmlc/GNUmakefile
new file mode 100644
index 0000000..78b1fab
--- /dev/null
+++ b/src/pmlc/GNUmakefile
@@ -0,0 +1,46 @@
+#
+# Copyright (c) 2000,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.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CMDTARGET = pmlc$(EXECSUFFIX)
+CFILES = pmlc.c util.c actions.c
+HFILES = pmlc.h
+LFILES = lex.l
+YFILES = gram.y
+LDIRT = *.log foo.* $(YFILES:%.y=%.tab.?) $(CMDTARGET) $(LFILES:.l=.c) gram.h
+LLDLIBS = $(PCPLIB)
+
+default: $(CMDTARGET)
+
+$(CMDTARGET): $(OBJECTS)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+pmlc.o: gram.h
+
+.NOTPARALLEL:
+gram.tab.h gram.tab.c: gram.y
+ $(YACC) -d -b `basename $< .y` $<
+
+gram.h: gram.tab.h
+ rm -f $@ && $(LN_S) $< $@
+
+include $(BUILDRULES)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmlc/actions.c b/src/pmlc/actions.c
new file mode 100644
index 0000000..43ea25c
--- /dev/null
+++ b/src/pmlc/actions.c
@@ -0,0 +1,868 @@
+/*
+ * Copyright (c) 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 <inttypes.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmlc.h"
+
+/* for the pmlogger/PMCD we currently have a connection to */
+static int logger_fd = -1; /* file desc pmlogger */
+static char *lasthost; /* host that logger_ctx is for */
+static int src_ctx = -1; /* context for logged host's PMCD*/
+static char *srchost; /* host that logged_ctx is for */
+
+static time_t tmp; /* for pmCtime */
+
+/* PCP 1.x pmlogger returns one of these when a status request is made */
+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;
+
+static int
+IsLocal(const char *hostspec)
+{
+ if (strcmp(hostspec, "localhost") == 0 ||
+ strcmp(hostspec, "local:") == 0 ||
+ strcmp(hostspec, "unix:") == 0)
+ return 1;
+ return 0;
+}
+int
+ConnectPMCD(void)
+{
+ int sts;
+ __pmPDU *pb = NULL;
+
+ if (src_ctx >= 0)
+ return src_ctx;
+
+ if (__pmVersionIPC(logger_fd) >= LOG_PDU_VERSION2) {
+ __pmLoggerStatus *lsp;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ fprintf(stderr, "pmlc: sending version 2 status request\n");
+#endif
+ if ((sts = __pmSendLogRequest(logger_fd, LOG_REQUEST_STATUS)) < 0) {
+ fprintf(stderr, "Error sending request to pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return sts;
+ }
+ if ((sts = __pmGetPDU(logger_fd, ANY_SIZE, __pmLoggerTimeout(), &pb)) <= 0) {
+ if (sts == 0)
+ /* end of file! */
+ sts = PM_ERR_IPC;
+ fprintf(stderr, "Error receiving response from pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return sts;
+ }
+ if (sts == PDU_ERROR) {
+ __pmDecodeError(pb, &sts);
+ fprintf(stderr, "Error: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ sts = 0;
+ goto done;
+ }
+ if (sts != PDU_LOG_STATUS) {
+ fprintf(stderr, "Error PDU response from pmlogger %s", __pmPDUTypeStr(sts));
+ fprintf(stderr, " not %s as expected\n", __pmPDUTypeStr(PDU_LOG_STATUS));
+ sts = 0;
+ goto done;
+ }
+ sts = __pmDecodeLogStatus(pb, &lsp);
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0) {
+ fprintf(stderr, "Error decoding response from pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ goto done;
+ }
+ if (IsLocal(lsp->ls_fqdn)) {
+ /*
+ * if pmcd host is "localhost"-alike then use hostname that
+ * was used to contact pmlogger, as from here (where pmlc is
+ * running) "localhost" is likely to connect us to the wrong
+ * pmcd or no pmcd at all.
+ */
+ srchost = strdup(lasthost);
+ if (srchost == NULL)
+ __pmNoMem("Error copying host name", strlen(lasthost), PM_FATAL_ERR);
+ }
+ else {
+ srchost = strdup(lsp->ls_fqdn);
+ if (srchost == NULL)
+ __pmNoMem("Error copying host name", strlen(lsp->ls_fqdn), PM_FATAL_ERR);
+ }
+ }
+
+ if ((sts = pmNewContext(PM_CONTEXT_HOST, srchost)) < 0) {
+ /* no PMCD connection, we can't do anything, give up */
+ fprintf(stderr, "Error trying to connect to PMCD on %s: %s\n",
+ srchost, pmErrStr(sts));
+ }
+ else
+ src_ctx = sts;
+
+done:
+ if (pb)
+ __pmUnpinPDUBuf(pb);
+ return sts;
+}
+
+int
+ConnectLogger(char *hostname, int *pid, int *port)
+{
+ int sts;
+
+ if (lasthost != NULL) {
+ free(lasthost);
+ lasthost = NULL;
+ }
+ DisconnectLogger();
+
+ if (src_ctx != -1) {
+ if ((sts = pmDestroyContext(src_ctx)) < 0)
+ fprintf(stderr, "Error deleting PMCD connection to %s: %s\n",
+ srchost, pmErrStr(sts));
+ src_ctx = -1;
+ }
+ if (srchost != NULL) {
+ free(srchost);
+ srchost = NULL;
+ }
+
+ if ((sts = __pmConnectLogger(hostname, pid, port)) < 0) {
+ logger_fd = -1;
+ return sts;
+ }
+ else {
+ logger_fd = sts;
+ if ((lasthost = strdup(hostname)) == NULL) {
+ __pmNoMem("Error copying host name", strlen(hostname), PM_FATAL_ERR);
+ }
+ return 0;
+ }
+}
+
+void
+DisconnectLogger(void)
+{
+ if (logger_fd != -1) {
+ __pmCloseSocket(logger_fd);
+ logger_fd = -1;
+ sleep(1);
+ }
+}
+
+void
+ShowLoggers(char *hostname)
+{
+ int i, n;
+ int ctx;
+ int primary = -1; /* ports[] index for primary logger */
+ int pport = -1; /* port for primary logger */
+ __pmLogPort *ports;
+
+ if ((n = __pmIsLocalhost(hostname)) == 0) {
+ /* remote, need PMCD's help for __pmLogFindPort */
+ if ((ctx = pmNewContext(PM_CONTEXT_HOST, hostname)) < 0) {
+ fprintf(stderr, "Error trying to connect to PMCD on %s: %s\n",
+ hostname, pmErrStr(ctx));
+ return;
+ }
+ }
+ else
+ ctx = -1;
+
+ if ((n = __pmLogFindPort(hostname, PM_LOG_ALL_PIDS, &ports)) < 0) {
+ fprintf(stderr, "Error finding pmloggers on %s: ",
+ hostname);
+ if (still_connected(n))
+ fprintf(stderr, "%s\n", pmErrStr(n));
+ }
+ else if (n == 0)
+ printf("No pmloggers running on %s\n", hostname);
+ else {
+ /* find the position of the primary logger */
+ for (i = 0; i < n; i++) {
+ if (ports[i].pid == PM_LOG_PRIMARY_PID) {
+ pport = ports[i].port;
+ break;
+ }
+ }
+ for (i = 0; i < n; i++) {
+ if (ports[i].port == pport) {
+ primary = i;
+ break;
+ }
+ }
+ printf("The following pmloggers are running on %s:\n ", hostname);
+ /* print any primary logger first, with its pid alias in parentheses) */
+ if (primary != -1) {
+ printf("primary");
+ printf(" (%d)", ports[primary].pid);
+ }
+ /* now print everything except the primary logger */
+ for (i = 0; i < n; i++) {
+ if (i != primary &&
+ ports[i].pid != PM_LOG_PRIMARY_PID) {
+ printf(" %d", ports[i].pid);
+ }
+ }
+ putchar('\n');
+
+ /* Note: Don't free ports, it's storage is managed by __pmLogFindPort() */
+ }
+
+ if (ctx >= 0)
+ pmDestroyContext(ctx);
+}
+
+static void
+PrintState(int state)
+{
+ static char *units[] = {"msec", "sec ", "min ", "hour"};
+ static int factor[] = {1000, 60, 60, 24};
+ int nfactors = sizeof(factor) / sizeof(factor[0]);
+ int i, j, is_on;
+ int delta = PMLC_GET_DELTA(state);
+ float t = delta;
+ int frac;
+
+ fputs(PMLC_GET_MAND(state) ? "mand " : "adv ", stdout);
+ is_on = PMLC_GET_ON(state);
+ fputs(is_on ? "on " : "off ", stdout);
+ if (PMLC_GET_INLOG(state))
+ fputs(PMLC_GET_AVAIL(state) ? " " : "na ", stdout);
+ else
+ fputs("nl ", stdout);
+
+ /* don't display time unless logging on */
+ if (!is_on) {
+ fputs(" ", stdout);
+ return;
+ }
+
+ if (delta == 0) {
+ fputs(" once", stdout);
+ return;
+ }
+
+ for (i = 0; i < nfactors; i++) {
+ if (t < factor[i])
+ break;
+ t /= factor[i];
+ }
+ if (i >= nfactors)
+ i = nfactors - 1;
+
+ frac = (int) ((t - (int)t) * 1000); /* get 3 decimal places */
+ if (frac % 10)
+ j = 3;
+ else
+ if (frac % 100)
+ j = 2;
+ else
+ if (frac % 1000)
+ j = 1;
+ else
+ j = 0;
+ fprintf(stdout, "%*.*f %s", 7 - j, j, t, units[i]);
+ return;
+}
+
+
+/* this pmResult is built during parsing of each pmlc statement.
+ * the metrics and indoms likewise
+ */
+extern pmResult *logreq;
+extern metric_t *metric;
+extern int n_metrics;
+extern indom_t *indom;
+extern int n_indoms;
+
+void
+Query(void)
+{
+ int i, j, k, inst;
+ metric_t *mp;
+ pmValueSet *vsp;
+ pmResult *res;
+
+ if (!connected())
+ return;
+
+ if ((i = __pmControlLog(logger_fd, logreq, PM_LOG_ENQUIRE, 0, 0, &res)) < 0) {
+ fprintf(stderr, "Error receiving response from pmlogger: ");
+ if (still_connected(i))
+ fprintf(stderr, "%s\n", pmErrStr(i));
+ return;
+ }
+ if (res == NULL) {
+ fprintf(stderr, "Error: NULL result from __pmControlLog\n");
+ return;
+ }
+
+ for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
+ vsp = res->vset[i];
+ if (mp->pmid != vsp->pmid) {
+ fprintf(stderr, "GAK! %s not found in returned result\n", mp->name);
+ return;
+ }
+ puts(mp->name);
+ if (vsp->numval < 0) {
+ fputs(" ", stdout);
+ puts(pmErrStr(vsp->numval));
+ }
+ else if (mp->indom == -1) {
+ fputs(" ", stdout);
+ PrintState(vsp->vlist[0].value.lval);
+ putchar('\n');
+ }
+ else if (mp->status.has_insts)
+ for (j = 0; j < mp->n_insts; j++) {
+ inst = mp->inst[j];
+ for (k = 0; k < vsp->numval; k++)
+ if (inst == vsp->vlist[k].inst)
+ break;
+ if (k >= vsp->numval) {
+ printf(" [%d] (not found)\n", inst);
+ continue;
+ }
+ fputs(" ", stdout);
+ PrintState(vsp->vlist[k].value.lval);
+ for (k = 0; k < indom[mp->indom].n_insts; k++)
+ if (inst == indom[mp->indom].inst[k])
+ break;
+ printf(" [%d or \"%s\"]\n", inst,
+ (k < indom[mp->indom].n_insts) ? indom[mp->indom].name[k] : "???");
+ }
+ else {
+ if (vsp->numval <= 0)
+ puts(" (no instances)");
+ else
+ for (j = 0; j < vsp->numval; j++) {
+ fputs(" ", stdout);
+ PrintState(vsp->vlist[j].value.lval);
+ inst = vsp->vlist[j].inst;
+ for (k = 0; k < indom[mp->indom].n_insts; k++)
+ if (inst == indom[mp->indom].inst[k])
+ break;
+ printf(" [%d or \"%s\"]\n",
+ inst,
+ (k < indom[mp->indom].n_insts) ? indom[mp->indom].name[k] : "???");
+ }
+ }
+ putchar('\n');
+ }
+ pmFreeResult(res);
+}
+
+void LogCtl(int control, int state, int delta)
+{
+ int i;
+ metric_t *mp;
+ pmValueSet *vsp;
+ pmResult *res;
+ int newstate, newdelta; /* from pmlogger */
+ int expstate = 0; /* expected from pmlogger */
+ int expdelta;
+ int firsterr = 1;
+ unsigned statemask;
+ static char *heading = "Warning: unable to change logging state for:";
+
+ if (!connected())
+ return;
+
+ i = __pmControlLog(logger_fd, logreq, control, state, delta, &res);
+ if (i < 0 && i != PM_ERR_GENERIC) {
+ fprintf(stderr, "Error receiving response from pmlogger: ");
+ if (still_connected(i))
+ fprintf(stderr, "%s\n", pmErrStr(i));
+ return;
+ }
+ if (res == NULL) {
+ fprintf(stderr, "Error: NULL result from __pmControlLog\n");
+ return;
+ }
+
+ /* Set up the state that we expect pmlogger to return. The encoding for
+ * control and state passed to __pmControlLog differs from that returned
+ * in the result.
+ */
+ statemask = 0;
+ 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 {
+ /* only mandatory+maybe is allowed by parser, which should return
+ * advisory+off OR advisory+on from pmlogger
+ */
+ PMLC_SET_MAND(expstate, 0);
+ PMLC_SET_MAND(statemask, 1);
+ /* don't set ON bit in statemask; ignore returned on/off status */
+ }
+
+ expdelta = PMLC_GET_ON(expstate) ? delta : 0;
+
+ for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
+ int j, k, inst;
+ int hadinstmsg;
+ char *name;
+
+ vsp = res->vset[i];
+ if (mp->pmid != vsp->pmid) {
+ fprintf(stderr, "GAK! %s not found in returned result\n", mp->name);
+ return;
+ }
+ if (vsp->numval < 0) {
+ if (firsterr) {
+ firsterr = 0;
+ puts(heading);
+ }
+ printf("%s:%s\n", mp->name, pmErrStr(vsp->numval));
+ continue;
+ }
+
+ if (mp->indom == -1) {
+ newstate = PMLC_GET_STATE(vsp->vlist[0].value.lval) & statemask;
+ newdelta = PMLC_GET_DELTA(vsp->vlist[0].value.lval);
+ if (expstate != newstate || expdelta != newdelta) {
+ if (firsterr) {
+ firsterr = 0;
+ puts(heading);
+ }
+ printf("%s\n\n", mp->name);
+ }
+ }
+ else if (mp->status.has_insts) {
+ long val;
+
+ hadinstmsg = 0;
+ for (j = 0; j < mp->n_insts; j++) {
+ inst = mp->inst[j];
+ /* find inst name via mp->indom */
+ for (k = 0; k < indom[mp->indom].n_insts; k++)
+ if (inst == indom[mp->indom].inst[k])
+ break;
+ if (k < indom[mp->indom].n_insts)
+ name = indom[mp->indom].name[k];
+ else
+ name = "???";
+ /* look for inst in pmValueSet from pmlogger */
+ for (k = 0; k < vsp->numval; k++)
+ if (inst == vsp->vlist[k].inst)
+ break;
+ if (k >= vsp->numval) {
+ if (firsterr) {
+ firsterr = 0;
+ puts(heading);
+ }
+ if (!hadinstmsg) {
+ hadinstmsg = 1;
+ printf("%s\n", mp->name);
+ }
+ printf(" [%d or \"%s\"] instance not found\n", inst, name);
+ continue;
+ }
+ val = vsp->vlist[k].value.lval;
+ newstate = (int)PMLC_GET_STATE(val) & statemask;
+ newdelta = PMLC_GET_DELTA(val);
+ if (expstate != newstate || expdelta != newdelta) {
+ if (firsterr) {
+ firsterr = 0;
+ puts(heading);
+ }
+ if (!hadinstmsg) {
+ hadinstmsg = 1;
+ printf("%s instance(s):\n", mp->name);
+ }
+ printf(" [%d or \"%s\"]\n", inst, name);
+ }
+ }
+ if (hadinstmsg)
+ putchar('\n');
+ }
+ else {
+ hadinstmsg = 0;
+ for (j = 0; j < vsp->numval; j++) {
+ newstate = PMLC_GET_STATE(vsp->vlist[j].value.lval) & statemask;
+ newdelta = PMLC_GET_DELTA(vsp->vlist[j].value.lval);
+ if (expstate != newstate || expdelta != newdelta) {
+ if (firsterr) {
+ firsterr = 0;
+ puts(heading);
+ }
+ if (!hadinstmsg) {
+ hadinstmsg = 1;
+ printf("%s instance(s):\n", mp->name);
+ }
+ inst = vsp->vlist[j].inst;
+ for (k = 0; k < indom[mp->indom].n_insts; k++)
+ if (inst == indom[mp->indom].inst[k])
+ break;
+ if (k < indom[mp->indom].n_insts)
+ name = indom[mp->indom].name[k];
+ else
+ name = "???";
+ printf(" [%d or \"%s\"]\n", inst, name);
+ }
+ }
+ if (hadinstmsg)
+ putchar('\n');
+ }
+ }
+ pmFreeResult(res);
+}
+
+/*
+ * Used to flag timezone changes.
+ * These changes are only relevant to Status() so they are made here.
+ */
+int tzchange = 0;
+
+#define TZBUFSZ 30 /* for pmCtime buffers */
+
+void Status(int pid, int primary)
+{
+ static int localtz = -1;
+ static char *ltzstr = ""; /* pmNewZone doesn't like null pointers */
+ char *str;
+ __pmLoggerStatus *lsp;
+ static char localzone[] = "local";
+ static char *zonename = localzone;
+ char *tzlogger;
+ __pmTimeval *start;
+ __pmTimeval *last;
+ __pmTimeval *timenow;
+ char *hostname;
+ int state;
+ int vol;
+ __int64_t size;
+ char startbuf[TZBUFSZ];
+ char lastbuf[TZBUFSZ];
+ char timenowbuf[TZBUFSZ];
+ int sts;
+ __pmPDU *pb;
+
+ if (!connected())
+ return;
+
+ if (__pmVersionIPC(logger_fd) >= LOG_PDU_VERSION2) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ fprintf(stderr, "pmlc: sending version 2 status request\n");
+#endif
+ if ((sts = __pmSendLogRequest(logger_fd, LOG_REQUEST_STATUS)) < 0) {
+ fprintf(stderr, "Error sending status request to pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ if ((sts = __pmGetPDU(logger_fd, ANY_SIZE, __pmLoggerTimeout(), &pb)) <= 0) {
+ if (sts == 0)
+ /* end of file! */
+ sts = PM_ERR_IPC;
+ fprintf(stderr, "Error receiving response from pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ if (sts == PDU_ERROR) {
+ __pmDecodeError(pb, &sts);
+ fprintf(stderr, "Error: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ sts = 0;
+ goto done;
+ }
+ if (sts != PDU_LOG_STATUS) {
+ fprintf(stderr, "Error PDU response from pmlogger %s", __pmPDUTypeStr(sts));
+ fprintf(stderr, " not %s as expected\n", __pmPDUTypeStr(PDU_LOG_STATUS));
+ __pmUnpinPDUBuf(pb);
+ return;
+ }
+ sts = __pmDecodeLogStatus(pb, &lsp);
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0) {
+ fprintf(stderr, "Error decoding response from pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ tzlogger = lsp->ls_tzlogger;
+ start = &lsp->ls_start;
+ last = &lsp->ls_last;
+ timenow = &lsp->ls_timenow;
+ hostname = lsp->ls_hostname;
+ state = lsp->ls_state;
+ vol = lsp->ls_vol;
+ size = lsp->ls_size;
+ }
+ else {
+ fprintf(stderr, "Error: logger IPC version < LOG_PDU_VERSION2, not supported\n");
+ return;
+ }
+
+ if (tzchange) {
+ switch (tztype) {
+ case TZ_LOCAL:
+ if (localtz == -1) {
+ str = __pmTimezone();
+ if (str != NULL)
+ ltzstr = str;
+ localtz = pmNewZone(ltzstr);
+ /* (exits if it fails) */
+ }
+ else
+ pmUseZone(localtz);
+ zonename = localzone;
+ break;
+
+ case TZ_LOGGER:
+ if (tzlogger)
+ pmNewZone(tzlogger); /* but keep me! */
+ zonename = "pmlogger";
+ break;
+
+ case TZ_OTHER:
+ pmNewZone(tz);
+ zonename = tz;
+ break;
+ }
+ }
+ tmp = start->tv_sec;
+ pmCtime(&tmp, startbuf);
+ startbuf[strlen(startbuf)-1] = '\0'; /* zap the '\n' at the end */
+ tmp = last->tv_sec;
+ pmCtime(&tmp, lastbuf);
+ lastbuf[strlen(lastbuf)-1] = '\0';
+ tmp = timenow->tv_sec;
+ pmCtime(&tmp, timenowbuf);
+ timenowbuf[strlen(timenowbuf)-1] = '\0';
+ printf("pmlogger ");
+ if (primary)
+ printf("[primary]");
+ else
+ printf("[%d]", pid);
+ printf(" on host %s is logging metrics from host %s\n",
+ lasthost, hostname);
+ /* NB: FQDN cleanup: note that this is not 'the fqdn' of the
+ pmlogger host or that of its target. */
+ if (__pmVersionIPC(logger_fd) >= LOG_PDU_VERSION2)
+ printf("PMCD host %s\n",
+ IsLocal(lsp->ls_fqdn) ? hostname : lsp->ls_fqdn);
+ if (state == PM_LOG_STATE_NEW) {
+ puts("logging hasn't started yet");
+ goto done;
+ }
+ printf("log started %s (times in %s time)\n"
+ "last log entry %s\n"
+ "current time %s\n"
+ "log volume %d\n"
+ "log size %" PRIi64 "\n",
+ startbuf, zonename, lastbuf, timenowbuf, vol, size);
+
+done:
+ return;
+
+}
+
+void
+Sync(void)
+{
+ int sts;
+ __pmPDU *pb;
+
+ if (!connected())
+ return;
+
+ if (__pmVersionIPC(logger_fd) >= LOG_PDU_VERSION2) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ fprintf(stderr, "pmlc: sending version 2 sync request\n");
+#endif
+ if ((sts = __pmSendLogRequest(logger_fd, LOG_REQUEST_SYNC)) < 0) {
+ fprintf(stderr, "Error sending sync request to pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ }
+
+ if ((sts = __pmGetPDU(logger_fd, ANY_SIZE, __pmLoggerTimeout(), &pb)) != PDU_ERROR) {
+ if (sts > 0)
+ __pmUnpinPDUBuf(pb);
+ if (sts == 0)
+ /* end of file! */
+ sts = PM_ERR_IPC;
+ fprintf(stderr, "Error receiving response from pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ __pmDecodeError(pb, &sts);
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0) {
+ fprintf(stderr, "Error: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+
+ return;
+}
+
+void
+Qa(void)
+{
+ int sts;
+ __pmPDU *pb;
+
+ if (!connected())
+ return;
+
+ if (__pmVersionIPC(logger_fd) >= LOG_PDU_VERSION2) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ fprintf(stderr, "pmlc: sending version 2 qa request\n");
+#endif
+ if ((sts = __pmSendLogRequest(logger_fd, 100+qa_case)) < 0) {
+ fprintf(stderr, "Error sending qa request to pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ }
+
+ if ((sts = __pmGetPDU(logger_fd, ANY_SIZE, __pmLoggerTimeout(), &pb)) != PDU_ERROR) {
+ if (sts > 0)
+ __pmUnpinPDUBuf(pb);
+ if (sts == 0)
+ /* end of file! */
+ sts = PM_ERR_IPC;
+ fprintf(stderr, "Error receiving response from pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ __pmDecodeError(pb, &sts);
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0) {
+ fprintf(stderr, "Error: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+
+ return;
+}
+
+void
+NewVolume(void)
+{
+ int sts;
+ __pmPDU *pb;
+
+ if (!connected())
+ return;
+
+ if (__pmVersionIPC(logger_fd) >= LOG_PDU_VERSION2) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ fprintf(stderr, "pmlc: sending version 2 newvol request\n");
+#endif
+ if ((sts = __pmSendLogRequest(logger_fd, LOG_REQUEST_NEWVOLUME)) < 0) {
+ fprintf(stderr, "Error sending newvolume request to pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ }
+
+ if ((sts = __pmGetPDU(logger_fd, ANY_SIZE, __pmLoggerTimeout(), &pb)) != PDU_ERROR) {
+ if (sts > 0)
+ __pmUnpinPDUBuf(pb);
+ if (sts == 0)
+ /* end of file! */
+ sts = PM_ERR_IPC;
+ fprintf(stderr, "Error receiving response from pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ __pmDecodeError(pb, &sts);
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0) {
+ fprintf(stderr, "Error: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ else
+ fprintf(stderr, "New log volume %d\n", sts);
+
+ __pmUnpinPDUBuf(pb);
+ return;
+}
+
+int
+connected(void)
+{
+ if (logger_fd == -1) {
+ yyerror("Not connected to any pmlogger instance");
+ return 0;
+ }
+ else
+ return 1;
+}
+
+int
+still_connected(int sts)
+{
+ if (sts == PM_ERR_IPC || sts == -EPIPE) {
+ fprintf(stderr, "Lost connection to the pmlogger instance\n");
+ DisconnectLogger();
+ return 0;
+ }
+ if (sts == PM_ERR_TIMEOUT) {
+ fprintf(stderr, "Timeout, closed connection to the pmlogger instance\n");
+ DisconnectLogger();
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/src/pmlc/gram.y b/src/pmlc/gram.y
new file mode 100644
index 0000000..1455329
--- /dev/null
+++ b/src/pmlc/gram.y
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2000-2002 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+%{
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include "pmapi.h"
+#include "impl.h"
+#include "./pmlc.h"
+
+#ifdef YYDEBUG
+int yydebug=1;
+#endif
+
+int mystate = GLOBAL;
+int logfreq;
+int parse_stmt;
+char emess[160];
+char *hostname;
+int state;
+int control;
+int qa_case;
+
+static int sts;
+
+extern int port;
+extern int pid;
+extern int is_local;
+extern int is_unix;
+extern int is_socket_path;
+
+%}
+
+%union {
+ long lval;
+ char * str;
+}
+
+%term LSQB
+ RSQB
+ COMMA
+ LBRACE
+ RBRACE
+ AT
+ EOL
+
+ LOG
+ MANDATORY ADVISORY
+ ON OFF MAYBE
+ EVERY ONCE
+ MSEC SECOND MINUTE HOUR
+
+ QUERY SHOW LOGGER CONNECT PRIMARY QUIT STATUS HELP
+ TIMEZONE LOCAL PORT SOCKET
+ NEW VOLUME
+
+ SYNC
+ QA
+
+%token<str> NAME HOSTNAME STRING URL
+%token<lval> NUMBER
+
+%type<lval> timeunits
+%%
+
+stmt : dowhat
+ {
+ mystate |= INSPEC;
+ if (!connected()) {
+ metric_cnt = -1;
+ return 0;
+ }
+ if (ConnectPMCD()) {
+ yyerror("");
+ metric_cnt = -1;
+ return 0;
+ }
+ beginmetrics();
+ }
+ somemetrics
+ {
+ mystate = GLOBAL;
+ endmetrics();
+ }
+ EOL
+ {
+ parse_stmt = LOG;
+ YYACCEPT;
+ }
+ | SHOW loggersopt hostopt EOL
+ {
+ parse_stmt = SHOW;
+ YYACCEPT;
+ }
+ | CONNECT towhom hostopt EOL
+ {
+ parse_stmt = CONNECT;
+ YYACCEPT;
+ }
+ | HELP EOL
+ {
+ parse_stmt = HELP;
+ YYACCEPT;
+ }
+ | QUIT EOL
+ {
+ parse_stmt = QUIT;
+ YYACCEPT;
+ }
+ | STATUS EOL
+ {
+ parse_stmt = STATUS;
+ YYACCEPT;
+ }
+ | NEW VOLUME EOL
+ {
+ parse_stmt = NEW;
+ YYACCEPT;
+ }
+ | TIMEZONE tzspec EOL
+ {
+ parse_stmt = TIMEZONE;
+ YYACCEPT;
+ }
+ | SYNC EOL
+ {
+ parse_stmt = SYNC;
+ YYACCEPT;
+ }
+ | QA NUMBER EOL
+ {
+ parse_stmt = QA;
+ qa_case = $2;
+ YYACCEPT;
+ }
+ | EOL
+ {
+ parse_stmt = 0;
+ YYACCEPT;
+ }
+ | { YYERROR; }
+ ;
+
+dowhat : action
+ ;
+
+action : QUERY { state = PM_LOG_ENQUIRE; }
+ | logopt cntrl ON frequency { state = PM_LOG_ON; }
+ | logopt cntrl OFF { state = PM_LOG_OFF; }
+ | logopt MANDATORY MAYBE
+ {
+ control = PM_LOG_MANDATORY;
+ state = PM_LOG_MAYBE;
+ }
+ ;
+
+logopt : LOG
+ | /* nothing */
+ ;
+
+cntrl : MANDATORY { control = PM_LOG_MANDATORY; }
+ | ADVISORY { control = PM_LOG_ADVISORY; }
+ ;
+
+frequency : everyopt NUMBER timeunits { logfreq = $2 * $3; }
+ | ONCE { logfreq = -1; }
+ ;
+
+everyopt : EVERY
+ | /* nothing */
+ ;
+
+timeunits : MSEC { $$ = 1; }
+ | SECOND { $$ = 1000; }
+ | MINUTE { $$ = 60000; }
+ | HOUR { $$ = 3600000; }
+ ;
+
+somemetrics : LBRACE { mystate |= INSPECLIST; } metriclist RBRACE
+ | metricspec
+ ;
+
+metriclist : metricspec
+ | metriclist metricspec
+ | metriclist COMMA metricspec
+ ;
+
+metricspec : NAME
+ {
+ beginmetgrp();
+ if ((sts = pmTraversePMNS($1, addmetric)) < 0)
+ /* metric_cnt is set by addmetric but if
+ * traversePMNS fails, set it so that the bad
+ * news is visible to other routines
+ */
+ metric_cnt = sts;
+ else if (metric_cnt < 0) /* addmetric failed */
+ sts = metric_cnt;
+
+ if (sts < 0 || metric_cnt == 0) {
+ sprintf(emess,
+ "Problem with lookup for metric \"%s\" ...",$1);
+ yywarn(emess);
+ if (sts < 0) {
+ fprintf(stderr, "Reason: ");
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ }
+ }
+ }
+ optinst { endmetgrp(); }
+ ;
+
+optinst : LSQB instancelist RSQB
+ | /* nothing */
+ ;
+
+instancelist : instance
+ | instance instancelist
+ | instance COMMA instancelist
+ ;
+
+instance : NAME { addinst($1, 0); }
+ | NUMBER { addinst(NULL, $1); }
+ | STRING { addinst($1, 0); }
+ ;
+
+loggersopt : LOGGER
+ | /* nothing */
+ ;
+
+hostopt : AT NAME { hostname = strdup($2); }
+ | AT HOSTNAME { hostname = strdup($2); }
+ | AT URL
+ {
+ char *prefix_end;
+ size_t prefix_len;
+ hostname = strdup($2);
+ prefix_end = strchr(hostname, ':');
+ if (prefix_end != NULL) {
+ prefix_len = prefix_end - hostname + 1;
+ if (prefix_len == 6 && strncmp(hostname, "local:", prefix_len) == 0)
+ is_local = 1;
+ else if (prefix_len == 5 && strncmp(hostname, "unix:", prefix_len) == 0)
+ is_unix = 1;
+ if (is_local || is_unix) {
+ const char *p;
+ /*
+ * Find out is a path was specified.
+ * Skip any initial path separators.
+ */
+ for (p = hostname + prefix_len; *p == __pmPathSeparator(); ++p)
+ ;
+ if (*p != '\0')
+ is_socket_path = 1;
+ }
+ }
+ }
+ | AT NUMBER
+ {
+ /* That MUST be a mistake! */
+ char tb[64];
+ sprintf (tb, "%d", (int)$2);
+ hostname = strdup(tb);
+ }
+ | AT STRING { hostname = strdup($2); }
+ | /* nothing */
+ ;
+
+towhom : PRIMARY { pid = PM_LOG_PRIMARY_PID; port = PM_LOG_NO_PORT; }
+ | NUMBER { pid = $1; port = PM_LOG_NO_PORT; }
+ | PORT NUMBER { pid = PM_LOG_NO_PID; port = $2; }
+ | SOCKET { pid = PM_LOG_NO_PID; port = PM_LOG_NO_PORT; }
+ ;
+
+tzspec : LOCAL { tztype = TZ_LOCAL; }
+ | LOGGER { tztype = TZ_LOGGER; }
+ | STRING
+ {
+ tztype = TZ_OTHER;
+ /* ignore the quotes: skip the leading one and
+ * clobber the trailing one with a null to
+ * terminate the string really required.
+ */
+ if (tz != NULL)
+ free(tz);
+ if ((tz = strdup($1)) == NULL) {
+ __pmNoMem("setting up timezone",
+ strlen($1), PM_FATAL_ERR);
+ }
+ }
+ ;
+
+%%
+
+extern char *configfile;
+extern int lineno;
+
+void
+yywarn(char *s)
+{
+ fprintf(stderr, "Warning [%s, line %d]\n",
+ configfile == NULL ? "<stdin>" : configfile, lineno);
+ if (s != NULL && s[0] != '\0')
+ fprintf(stderr, "%s\n", s);
+}
+
+void
+yyerror(char *s)
+{
+ fprintf(stderr, "Error [%s, line %d]\n",
+ configfile == NULL ? "<stdin>" : configfile, lineno);
+ if (s != NULL && s[0] != '\0')
+ fprintf(stderr, "%s\n", s);
+
+ skipAhead ();
+ yyclearin;
+ mystate = GLOBAL;
+}
diff --git a/src/pmlc/lex.l b/src/pmlc/lex.l
new file mode 100644
index 0000000..c523a36
--- /dev/null
+++ b/src/pmlc/lex.l
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2000-2002 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+%{
+#include "pmapi.h"
+#include "./pmlc.h"
+
+extern int eflag;
+
+#ifdef FLEX_SCANNER
+#include "gram.tab.h"
+int pmlcFlexInput (char *, int);
+int lineno = 0;
+#else /* AT&T lex */
+#include "gram.h"
+int lineno = 1;
+#endif
+
+static int
+ctx(int type)
+{
+ if (mystate == GLOBAL)
+ return type;
+ else {
+ yylval.str = yytext;
+ return NAME;
+ }
+}
+
+%}
+
+%option noinput
+%option nounput
+
+%{
+#ifdef FLEX_SCANNER
+#ifndef YY_NO_UNPUT
+#define YY_NO_UNPUT
+#endif
+#undef YY_INPUT
+#define YY_INPUT(b,r,ms) (r=pmlcFlexInput(b, ms))
+#else /* AT&T Lex */
+#undef input
+#undef unput
+#undef yywrap
+#endif
+%}
+
+%%
+"[" { return LSQB; }
+"]" { return RSQB; }
+"," { return COMMA; }
+"{" { return LBRACE; }
+"}" { return RBRACE; }
+"@" { return AT; }
+
+milliseconds? { return ctx(MSEC); }
+mandatory { return ctx(MANDATORY); }
+advisory { return ctx(ADVISORY); }
+timezone { return ctx(TIMEZONE); }
+loggers? { return ctx(LOGGER); }
+minutes? { return ctx(MINUTE); }
+seconds? { return ctx(SECOND); }
+connect { return ctx(CONNECT); }
+primary { return ctx(PRIMARY); }
+status { return ctx(STATUS); }
+volume { return ctx(VOLUME); }
+flush { return ctx(SYNC); }
+every { return ctx(EVERY); }
+local { return ctx(LOCAL); }
+maybe { return ctx(MAYBE); }
+query { return ctx(QUERY); }
+hours? { return ctx(HOUR); }
+msecs? { return ctx(MSEC); }
+mins? { return ctx(MINUTE); }
+secs? { return ctx(SECOND); }
+help { return ctx(HELP); }
+once { return ctx(ONCE); }
+port { return ctx(PORT); }
+quit { return ctx(QUIT); }
+socket { return ctx(SOCKET); }
+show { return ctx(SHOW); }
+sync { return ctx(SYNC); }
+log { return ctx(LOG); }
+new { return ctx(NEW); }
+off { return ctx(OFF); }
+on { return ctx(ON); }
+qa { return ctx(QA); }
+at { return ctx(AT); }
+h { return ctx(HELP); }
+q { return ctx(QUIT); }
+\? { return ctx(HELP); }
+
+unix:[A-Za-z0-9_.-/]*\* { yylval.str = yytext; return URL; }
+unix:[A-Za-z0-9_.-/]* { yylval.str = yytext; return URL; }
+local:[A-Za-z0-9_.-/]*\* { yylval.str = yytext; return URL; }
+local:[A-Za-z0-9_.-/]* { yylval.str = yytext; return URL; }
+
+[A-Za-z][A-Za-z0-9_.]* { yylval.str = yytext; return NAME; }
+
+[A-Za-z][A-Za-z0-9_.-]* { yylval.str = yytext; return HOSTNAME; }
+
+\"[^\"\n][^\"\n]*\" { /* strip quotes before returing */
+ yytext[strlen(yytext)-1] = '\0';
+ yylval.str = yytext+1;
+ return STRING;
+ }
+
+[0-9]+ { yylval.lval = atol(yytext); return NUMBER; }
+
+\#[^\n]* { }
+\n { if (!(mystate & INSPECLIST)) return EOL; }
+
+[ \r\t]+ { }
+
+. {
+ char emess[160];
+ sprintf(emess, "Unexpected character '%c'",
+ yytext[0]);
+ yyerror(emess);
+ }
+%%
+
+#ifdef FLEX_SCANNER
+static int in = '\n';
+
+int yywrap(void)
+{
+ return in == EOF;
+}
+
+int
+pmlcFlexInput (char * buf, int ms)
+{
+ extern int iflag;
+
+ if ( in == '\n' ) {
+ lineno++;
+ if ( iflag ) {
+ if ( mystate == GLOBAL )
+ printf ("pmlc> ");
+ else
+ printf ("? ");
+ fflush (stdout);
+ }
+ }
+
+ if (ms > 0 ) {
+ if ( (in = fgetc (yyin)) != EOF ) {
+ buf[0] = in & 0xFFU;
+ ms = 1;
+ if ( eflag ) {
+ putchar (in);
+ fflush (stdout);
+ }
+ } else {
+ ms = 0;
+ }
+ }
+
+ return (ms);
+}
+
+void
+skipAhead (void)
+{
+ if ( mystate & INSPECLIST ) {
+ while ( in != '}' && in != EOF ) {
+ char c;
+ pmlcFlexInput (&c, 1);
+ }
+ mystate = GLOBAL;
+ }
+
+ while ( (in != '\n') && (in != EOF) ) {
+ char c;
+ pmlcFlexInput (&c, 1);
+ }
+}
+
+#else
+static char lastc;
+static char peekc = '\0';
+
+char input(void)
+{
+ int get;
+ static int first = 1;
+
+ if (peekc) {
+ lastc = peekc;
+ peekc = '\0';
+ return lastc;
+ }
+
+ if (lastc == '\n' || first) {
+ extern int iflag;
+ if (iflag) {
+ if (mystate == GLOBAL)
+ printf("pmlc> ");
+ else
+ printf("? ");
+ fflush(stdout);
+ }
+ if (first)
+ first = 0;
+ else
+ lineno++;
+ }
+ else if (lastc == '\0')
+ return lastc;
+
+ get = getchar();
+ if (get == EOF)
+ lastc = '\0';
+ else {
+ lastc = get;
+ if (eflag) {
+ putchar(lastc);
+ fflush(stdout);
+ }
+ }
+
+ return lastc;
+}
+
+void unput(char c)
+{
+ peekc = c;
+}
+
+void
+skipAhead (void)
+{
+ int c = lastc;
+
+ for ( ; ; ) {
+ if (c == '\0')
+ break;
+ if ((mystate == GLOBAL || (mystate & INSPEC)) && c == '\n')
+ break;
+ if ((mystate & INSPECLIST) && c == '}')
+ break;
+ c = input();
+ }
+}
+
+int yywrap(void)
+{
+ return lastc == '\0';
+}
+#endif
diff --git a/src/pmlc/pmlc.c b/src/pmlc/pmlc.c
new file mode 100644
index 0000000..cb477fb
--- /dev/null
+++ b/src/pmlc/pmlc.c
@@ -0,0 +1,398 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2005 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 "pmlc.h"
+#include "gram.h"
+
+char *configfile;
+__pmLogCtl logctl;
+int parse_done;
+int pid = PM_LOG_NO_PID;
+int port = PM_LOG_NO_PORT;
+int is_unix; /* host spec is a unix: url. */
+int is_local; /* host spec is a local: url. */
+int is_socket_path; /* host spec is a url with a path. */
+char *tz; /* for -Z timezone */
+int tztype = TZ_LOCAL; /* timezone for status cmd */
+int eflag;
+int iflag;
+
+extern int parse_stmt;
+extern int tzchange;
+
+static char title[] = "Performance Co-Pilot Logger Control (pmlc), Version %s\n\n%s\n";
+static char menu[] =
+"pmlc commands\n\n"
+" show loggers [@<host>] display <pid>s of running pmloggers\n"
+" connect _logger_id [@<host>] connect to designated pmlogger\n"
+" status information about connected pmlogger\n"
+" query metric-list show logging state of metrics\n"
+" new volume start a new log volume\n"
+"\n"
+" log { mandatory | advisory } on <interval> _metric-list\n"
+" log { mandatory | advisory } off _metric-list\n"
+" log mandatory maybe _metric-list\n"
+"\n"
+" timezone local|logger|'<timezone>' change reporting timezone\n"
+" help print this help message\n"
+" quit exit from pmlc\n"
+"\n"
+" _logger_id is primary | <pid> | port <n>\n"
+" _metric-list is _metric-spec | { _metric-spec ... }\n"
+" _metric-spec is <metric-name> | <metric-name> [ <instance> ... ]\n";
+
+static int overrides(int, pmOptions *);
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ { "echo", 0, 'e', 0, "echo input" },
+ { "host", 1, 'h', "HOST", "connect to pmlogger using host specification" },
+ { "interactive", 0, 'i', 0, "be interactive and prompt" },
+ PMOPT_NAMESPACE,
+ { "primary", 0, 'P', 0, "connect to primary pmlogger" },
+ { "port", 1, 'p', "N", "connect to pmlogger on this TCP/IP port" },
+ PMOPT_TIMEZONE,
+ { "logzone", 0, 'z', 0, "set reporting timezone to local time for pmlogger" },
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "D:eh:in:Pp:zZ:?",
+ .long_options = longopts,
+ .short_usage = "[options] [pid]",
+ .override = overrides,
+};
+
+static int
+overrides(int opt, pmOptions *opts)
+{
+ if (opt == 'h' || opt == 'p')
+ return 1;
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int sts = 0; /* initialize to pander to gcc */
+ char *host = NULL;
+ char *endnum;
+ int primary;
+ size_t prefix_len;
+ char *prefix_end;
+
+ iflag = isatty(0);
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'e': /* echo input */
+ eflag++;
+ break;
+
+ case 'h': /* hostspec */
+ /*
+ * We need to know if a socket path has been specified.
+ */
+ host = opts.optarg;
+ prefix_end = strchr(host, ':');
+ if (prefix_end != NULL) {
+ prefix_len = prefix_end - host + 1;
+ if (prefix_len == 6 && strncmp(host, "local:", prefix_len) == 0)
+ is_local = 1;
+ else if (prefix_len == 5 && strncmp(host, "unix:", prefix_len) == 0)
+ is_unix = 1;
+ if (is_local || is_unix) {
+ const char *p;
+ /*
+ * Find out is a path was specified.
+ * Skip any initial path separators.
+ */
+ for (p = host + prefix_len; *p == __pmPathSeparator(); ++p)
+ ;
+ if (*p != '\0')
+ is_socket_path = 1;
+ }
+ }
+ break;
+
+ case 'i': /* be interactive */
+ iflag++;
+ break;
+
+ case 'P': /* connect to primary logger */
+ if (port != PM_LOG_NO_PORT || (is_unix && is_socket_path)) {
+ pmprintf("%s: at most one of -P, -p, unix socket, or PID may be specified\n",
+ pmProgname);
+ opts.errors++;
+ } else {
+ port = PM_LOG_PRIMARY_PORT;
+ }
+ break;
+
+ case 'p': /* connect via port */
+ if (port != PM_LOG_NO_PORT || is_unix) {
+ pmprintf("%s: at most one of -P, -p, unix socket, or PID may be specified\n",
+ pmProgname);
+ opts.errors++;
+ } else {
+ port = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0' || port <= PM_LOG_PRIMARY_PORT) {
+ pmprintf("%s: port must be numeric and greater than %d\n",
+ pmProgname, PM_LOG_PRIMARY_PORT);
+ opts.errors++;
+ }
+ }
+ break;
+ }
+ }
+
+ if (opts.optind < argc - 1)
+ opts.errors++;
+ else if (opts.optind == argc - 1) {
+ /* pid was specified */
+ if (port != PM_LOG_NO_PORT || (is_unix && is_socket_path)) {
+ pmprintf("%s: at most one of -P, -p, unix socket, or PID may be specified\n",
+ pmProgname);
+ opts.errors++;
+ }
+ else {
+ pid = (int)strtol(argv[opts.optind], &endnum, 10);
+ if (*endnum != '\0' || pid <= PM_LOG_PRIMARY_PID) {
+ pmprintf("%s: PID must be a numeric process ID and greater than %d\n",
+ pmProgname, PM_LOG_PRIMARY_PID);
+ opts.errors++;
+ }
+ }
+ }
+
+ if (!opts.errors && host && pid == PM_LOG_NO_PID &&
+ port == PM_LOG_NO_PORT && !is_socket_path) {
+ pmprintf("%s: -h may not be used without -P or -p or a socket path or a PID\n",
+ pmProgname);
+ opts.errors++;
+ }
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (opts.tzflag) {
+ tztype = TZ_LOGGER;
+ tzchange = 1;
+ } else if (opts.timezone) {
+ tz = opts.timezone;
+ tztype = TZ_OTHER;
+ tzchange = 1;
+ }
+
+ if (host == NULL)
+ host = "local:";
+
+ primary = 0;
+ if (port == PM_LOG_PRIMARY_PORT || pid == PM_LOG_PRIMARY_PID)
+ primary = 1;
+
+ if (pid != PM_LOG_NO_PID || port != PM_LOG_NO_PORT || is_socket_path)
+ sts = ConnectLogger(host, &pid, &port);
+
+ if (iflag)
+ printf(title, PCP_VERSION, menu);
+
+ if (pid != PM_LOG_NO_PID || port != PM_LOG_NO_PORT || is_socket_path) {
+ if (sts < 0) {
+ if (primary) {
+ fprintf(stderr, "Unable to connect to primary pmlogger at %s: ",
+ host);
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ }
+ else if (is_socket_path) {
+ fprintf(stderr, "Unable to connect to pmlogger via the local socket at %s: ",
+ host);
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ }
+ else if (port != PM_LOG_NO_PORT) {
+ fprintf(stderr, "Unable to connect to pmlogger on port %d at %s: ",
+ port, host);
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ }
+ else {
+ fprintf(stderr, "Unable to connect to pmlogger pid %d at %s: ",
+ pid, host);
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ }
+ }
+ else {
+ if (primary)
+ printf("Connected to primary pmlogger at %s\n", host);
+ else if (is_socket_path)
+ printf("Connected to pmlogger via local socket at %s\n", host);
+ else if (port != PM_LOG_NO_PORT)
+ printf("Connected to pmlogger on port %d at %s\n", port, host);
+ else
+ printf("Connected to pmlogger pid %d at %s\n", pid, host);
+ }
+ }
+
+ for ( ; ; ) {
+ char *realhost;
+
+ is_local = 0;
+ is_unix = 0;
+ is_socket_path = 0;
+ parse_stmt = -1;
+ metric_cnt = 0;
+ yyparse();
+ if (yywrap()) {
+ if (iflag)
+ putchar('\n');
+ break;
+ }
+ if (metric_cnt < 0)
+ continue;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ printf("stmt=%d, state=%d, control=%d, hostspec=%s, pid=%d, port=%d\n",
+ parse_stmt, state, control, hostname, pid, port);
+#endif
+
+ realhost = (hostname == NULL) ? host : hostname;
+ switch (parse_stmt) {
+
+ case SHOW:
+ ShowLoggers(realhost);
+ break;
+
+ case CONNECT:
+ /* The unix: url requres either 'primary', a pid or a socket path. */
+ if (is_unix && pid == PM_LOG_NO_PID && ! is_socket_path) {
+ fprintf(stderr, "The 'unix:' url requires either 'primary', a pid or a socket path");
+ if (still_connected(sts))
+ fprintf(stderr, "\n");
+ break;
+ }
+ /* The local: url requres either 'primary', a pid a port or a socket path. */
+ if (is_local && pid == PM_LOG_NO_PID && port == PM_LOG_NO_PORT && ! is_socket_path) {
+ fprintf(stderr, "The 'local:' url requires either 'primary', a pid, a port or a socket path");
+ if (still_connected(sts))
+ fprintf(stderr, "\n");
+ break;
+ }
+ primary = 0;
+ if (port == PM_LOG_PRIMARY_PORT || pid == PM_LOG_PRIMARY_PID)
+ primary = 1;
+ if ((sts = ConnectLogger(realhost, &pid, &port)) < 0) {
+ if (primary) {
+ fprintf(stderr, "Unable to connect to primary pmlogger at %s: ",
+ realhost);
+ }
+ else if (is_socket_path) {
+ fprintf(stderr, "Unable to connect to pmlogger via local socket at %s: ",
+ realhost);
+ }
+ else if (port != PM_LOG_NO_PORT) {
+ fprintf(stderr, "Unable to connect to pmlogger on port %d at %s: ",
+ port, realhost);
+ }
+ else {
+ fprintf(stderr, "Unable to connect to pmlogger pid %d at %s: ",
+ pid, realhost);
+ }
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ }
+ else
+ /* if the timezone is "logger time", it has changed
+ * because the logger may be in a different zone
+ * (note that tzchange may already be set (e.g. -Z and
+ * this connect is the first).
+ */
+ tzchange |= (tztype == TZ_LOGGER);
+ break;
+
+ case HELP:
+ puts(menu);
+ break;
+
+ case LOG:
+ if (state == PM_LOG_ENQUIRE) {
+ Query();
+ break;
+ }
+ if (logfreq == -1)
+ logfreq = 0;
+ if (state == PM_LOG_ON) {
+ if (logfreq < 0) {
+fprintf(stderr, "Logging delta (%d msec) must be positive\n", logfreq);
+ break;
+ }
+ else if (logfreq > PMLC_MAX_DELTA) {
+fprintf(stderr, "Logging delta (%d msec) cannot be bigger than %d msec\n", logfreq, PMLC_MAX_DELTA);
+ break;
+ }
+ }
+ LogCtl(control, state, logfreq);
+ break;
+
+ case QUIT:
+ printf("Goodbye\n");
+ DisconnectLogger();
+ exit(0);
+ break;
+
+ case STATUS:
+ Status(pid, primary);
+ break;
+
+ case NEW:
+ NewVolume();
+ break;
+
+ case TIMEZONE:
+ tzchange = 1;
+ break;
+
+ case SYNC:
+ Sync();
+ break;
+
+ case QA:
+ if (qa_case == 0)
+ fprintf(stderr, "QA Test Case deactivated\n");
+ else
+ fprintf(stderr, "QA Test Case #%d activated\n", qa_case);
+ Qa();
+ }
+
+ if (hostname != NULL) {
+ free(hostname);
+ hostname = NULL;
+ }
+
+ }
+
+ DisconnectLogger();
+ exit(0);
+}
diff --git a/src/pmlc/pmlc.h b/src/pmlc/pmlc.h
new file mode 100644
index 0000000..83fd38a
--- /dev/null
+++ b/src/pmlc/pmlc.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2000,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.
+ */
+#ifndef _PMLC_H
+#define _PMLC_H
+
+/* config file parser states (bit field values) */
+#define GLOBAL 0
+#define INSPEC 1
+#define INSPECLIST 2
+
+/* timezone to use when printing status */
+#define TZ_LOCAL 0
+#define TZ_LOGGER 1
+#define TZ_OTHER 2
+
+/* timezone variables */
+extern char *tz; /* for -Z timezone */
+extern int tztype; /* timezone for status cmd */
+
+/* parse summary back from yacc to main */
+extern char *hostname;
+extern int state;
+extern int control;
+extern int mystate;
+extern int qa_case;
+
+extern void yyerror(char *);
+extern void yywarn(char *);
+extern int yywrap(void);
+extern int yylex(void);
+extern void skipAhead (void);
+extern int yyparse(void);
+extern void beginmetrics(void);
+extern void endmetrics(void);
+extern void beginmetgrp(void);
+extern void endmetgrp(void);
+extern void addmetric(const char *);
+extern void addinst(char *, int);
+extern int connected(void);
+extern int still_connected(int);
+extern int metric_cnt;
+#ifdef PCP_DEBUG
+extern void dumpmetrics(FILE *);
+#endif
+
+/* connection routines */
+extern int ConnectLogger(char *, int *, int *);
+extern void DisconnectLogger(void);
+extern int ConnectPMCD(void);
+
+/* command routines */
+extern int logfreq;
+extern void ShowLoggers(char *);
+extern void Query(void);
+extern void LogCtl(int, int, int);
+extern void Status(int, int);
+extern void NewVolume(void);
+extern void Sync(void);
+extern void Qa(void);
+
+/* information about an instance domain */
+typedef struct {
+ pmInDom indom;
+ int n_insts;
+ int *inst;
+ char **name;
+} indom_t;
+
+/* a metric plus an optional list of instances */
+typedef struct {
+ char *name;
+ pmID pmid;
+ int indom; /* index of indom (or -1) */
+ int n_insts; /* number of insts for this metric */
+ int *inst; /* list of insts for this metric */
+ struct {
+ unsigned selected : 1, /* apply instances to metric? */
+ new : 1, /* new in current PMNS subtree */
+ has_insts : 1; /* free inst list? */
+ } status;
+} metric_t;
+
+#endif /* _PMLC_H */
diff --git a/src/pmlc/util.c b/src/pmlc/util.c
new file mode 100644
index 0000000..30e3881
--- /dev/null
+++ b/src/pmlc/util.c
@@ -0,0 +1,546 @@
+/*
+ * Copyright (c) 1995-2002 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmlc.h"
+
+/* this pmResult is built after parsing the current statement. The action
+ * routines (Status, LogReq) send it to the logger as a request.
+ */
+pmResult *logreq = NULL;
+int sz_logreq = 0;
+int n_logreq = 0;
+
+int n_metrics = 0;
+int sz_metrics = 0;
+metric_t *metric = NULL;
+
+int n_indoms = 0;
+int sz_indoms = 0;
+indom_t *indom = NULL;
+
+void
+freemetrics(void)
+{
+ int i;
+ metric_t *mp;
+
+ for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
+ free(mp->name);
+ if (mp->status.has_insts && mp->inst != NULL)
+ free(mp->inst);
+ }
+ n_metrics = 0;
+ /* keep the array around for re-use */
+}
+
+void
+freeindoms(void)
+{
+ int i;
+ indom_t *ip;
+
+ for (i = 0, ip = indom; i < n_indoms; i++, ip++) {
+ free(ip->inst);
+ free(ip->name);
+ }
+ free(indom);
+ sz_indoms = n_indoms = 0;
+ indom = NULL;
+}
+
+/* Return a pointer to the specified instance domain, adding it to the list if
+ * it is not already present. Returns < 0 on error.
+ */
+int
+addindom(pmInDom newindom, int *resptr)
+{
+ int i;
+ indom_t *ip;
+
+ for (i = 0; i < n_indoms; i++)
+ if (newindom == indom[i].indom) {
+ *resptr = i;
+ return 0;
+ }
+
+ *resptr = -1; /* in case of errors */
+ if (n_indoms >= sz_indoms) {
+ sz_indoms += 4;
+ i = sz_indoms * sizeof(indom_t);
+ if ((indom = (indom_t *)realloc(indom, i)) == NULL) {
+ __pmNoMem("expanding instance domain array", i, PM_FATAL_ERR);
+ }
+ }
+ ip = &indom[n_indoms];
+ ip->inst = NULL;
+ ip->name = NULL;
+
+ if ((i = pmGetInDom(newindom, &ip->inst, &ip->name)) < 0) {
+ *resptr = -1;
+ return i;
+ }
+ ip->indom = newindom;
+ ip->n_insts = i;
+ *resptr = n_indoms;
+ n_indoms++;
+ return 0;
+}
+
+int metric_cnt; /* status of addmetric operation(s) */
+static int had_insts; /* new metric(s) had instances specified */
+static int n_selected; /* number of metrics selected */
+static int first_inst; /* check consistency of new metrics' InDoms */
+
+void
+beginmetrics(void)
+{
+ metric_cnt = 0;
+ fflush(stdout);
+ fflush(stderr);
+ freemetrics();
+}
+
+void
+beginmetgrp(void)
+{
+ int i;
+ metric_t *mp;
+
+ had_insts = 0;
+ n_selected = 0;
+ first_inst = 1;
+ for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
+ mp->status.selected = 0;
+ mp->status.new = 0;
+ }
+}
+
+/* Perform any instance domain fixups and error checks required for the latest
+ * metrics added.
+ */
+void
+endmetgrp(void)
+{
+ int i;
+ metric_t *mp;
+
+ if (n_metrics <= 0) {
+ if (metric_cnt >= 0)
+ metric_cnt = PM_ERR_PMID;
+ return;
+ }
+
+ for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
+ if (!mp->status.selected)
+ continue;
+
+ /* just added metric, no instances => use all instances */
+ if (mp->status.new) {
+ if (mp->indom != -1 && !had_insts) {
+ mp->n_insts = indom[mp->indom].n_insts;
+ mp->inst = indom[mp->indom].inst;
+ mp->status.has_insts = 0;
+ }
+ }
+ /* metric was there already */
+ else
+ if (mp->indom == -1)
+ fprintf(stderr, "Warning: %s has already been specified\n", mp->name);
+ else
+ /* if metric had specific instances */
+ if (mp->status.has_insts) {
+ if (!had_insts) {
+ fprintf(stderr,
+ "Warning: %s had instance(s) specified previously.\n"
+ " Using all instances since none specified this time\n",
+ mp->name);
+ if (mp->inst != NULL)
+ free(mp->inst);
+ mp->n_insts = indom[mp->indom].n_insts;
+ mp->inst = indom[mp->indom].inst;
+ mp->status.has_insts = 0;
+ }
+ }
+ /* metric had "use all instances" */
+ else
+ if (!had_insts)
+ fprintf(stderr,
+ "Warning: already using all instances for %s\n",
+ mp->name);
+ else
+ fprintf(stderr,
+ "Warning: instance(s) specified for %s\n"
+ " (already using all instances)\n",
+ mp->name);
+ }
+}
+
+void
+endmetrics(void)
+{
+ int i, j, need;
+ metric_t *mp;
+ pmValueSet *vsp;
+
+ if (metric_cnt < 0) {
+ if (connected())
+ fputs("Logging statement ignored due to error(s)\n", stderr);
+ goto done;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ dumpmetrics(stdout);
+#endif
+
+ /* free any old values in the reusable pmResult skeleton */
+ if (n_logreq) {
+ for (i = 0; i < n_logreq; i++)
+ free(logreq->vset[i]);
+ n_logreq = 0;
+ }
+
+ /* build a result from the metrics and instances in the metric array */
+ if (n_metrics > sz_logreq) {
+ if (sz_logreq)
+ free(logreq);
+ need = sizeof(pmResult) + (n_metrics - 1) * sizeof(pmValueSet *);
+ /* - 1 because a pmResult already contains one pmValueSet ptr */
+ if ((logreq = (pmResult *)malloc(need)) == NULL) {
+ __pmNoMem("building result to send", need, PM_FATAL_ERR);
+ }
+ sz_logreq = n_metrics;
+ }
+ for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
+ need = sizeof(pmValueSet);
+ /* pmValueSet contains one pmValue, allow for more if > 1 inst */
+ if (mp->status.has_insts && mp->n_insts > 1)
+ need += (mp->n_insts - 1) * sizeof(pmValue);
+ if ((vsp = (pmValueSet *)malloc(need)) == NULL) {
+ __pmNoMem("building result value set", need , PM_FATAL_ERR);
+ }
+ logreq->vset[i] = vsp;
+ vsp->pmid = mp->pmid;
+ if (mp->indom == -1)
+ vsp->numval = 1;
+ else
+ vsp->numval = mp->status.has_insts ? mp->n_insts : 0;
+ vsp->valfmt = PM_VAL_INSITU;
+ if (mp->status.has_insts)
+ for (j = 0; j < vsp->numval; j++)
+ vsp->vlist[j].inst = mp->inst[j];
+ else
+ vsp->vlist[0].inst = PM_IN_NULL;
+ }
+ logreq->numpmid = n_metrics;
+ n_logreq = n_metrics;
+
+done:
+ fflush(stderr);
+ fflush(stdout);
+}
+
+/* Add a metric to the metric list. Sets metric_cnt to < 0 on fatal error. */
+
+void
+addmetric(const char *name)
+{
+ int i;
+ pmID pmid;
+ int need;
+ metric_t *mp;
+ pmDesc desc;
+ int sts;
+
+ if (metric_cnt < 0)
+ return;
+
+ /* Cast const away as pmLookUpName should not modify name */
+ if ((sts = pmLookupName(1, (char **)&name, &pmid)) < 0) {
+ metric_cnt = sts;
+ return;
+ }
+
+ for (i = 0, mp = metric; i < n_metrics; i++, mp++)
+ if (pmid == mp->pmid)
+ break;
+
+ if (i >= n_metrics) {
+ /* expand metric array if necessary */
+ if (n_metrics >= sz_metrics) {
+ sz_metrics += 4;
+ need = sz_metrics * sizeof(metric_t);
+ if ((metric = (metric_t *)realloc(metric, need)) == NULL) {
+ __pmNoMem("expanding metric array", need, PM_FATAL_ERR);
+ }
+ }
+ mp = &metric[i];
+ mp->status.new = 1;
+ }
+ mp->status.selected = 1;
+ n_selected++;
+ if (i < n_metrics) /* already have this metric */
+ return;
+
+ if ((sts = pmLookupDesc(pmid, &desc)) == PM_ERR_PMID
+ || desc.type == PM_TYPE_NOSUPPORT) {
+ /*
+ * Name is bad or descriptor is not available ...
+ * this is not fatal, but need to back off a little
+ */
+ n_selected--;
+ return;
+ }
+ if (sts < 0) {
+ /* other errors are more serious */
+ metric_cnt = sts;
+ return;
+ }
+ if (desc.indom == PM_INDOM_NULL)
+ i = -1;
+ else
+ if ((metric_cnt = addindom(desc.indom, &i)) < 0)
+ return;
+
+ mp->name = strdup(name);
+ mp->pmid = pmid;
+ mp->indom = i;
+ mp->n_insts = 0;
+ mp->inst = NULL;
+ mp->status.has_insts = 0;
+ metric_cnt = ++n_metrics;
+ return;
+}
+
+/* Add an instance to the selected metric(s) on the metric list.
+ * If name is NULL, use instance number in inst.
+ * Check that
+ * - the last group of metrics added have the same pmInDom
+ * - the specified instance exists in the metrics' instance domain.
+ */
+void
+addinst(char *name, int instid)
+{
+ metric_t *mp, *first_mp;
+ indom_t *ip;
+ int m, i, j, need;
+ int inst;
+ static int done_inst_msg = 0;
+
+ /* don't try to add instances if one more metrics were erroneous */
+ if (metric_cnt < 0)
+ return;
+
+ had_insts = 1;
+
+ /* Find the first selected metric */
+ for (m = 0, mp = metric; m < n_metrics; m++, mp++)
+ if (mp->status.selected)
+ break;
+#ifdef PCP_DEBUG
+ if (m >= n_metrics) {
+ fprintf(stderr, "Ark! No metrics selected for addinst(%s)\n", name);
+ abort();
+ }
+#endif
+ first_mp = mp;
+
+ if (first_inst) {
+ /* check that the first metric doesn't have PM_INDOM_NULL */
+ if (mp->indom == -1) {
+ metric_cnt = PM_ERR_INDOM;
+ fprintf(stderr, "%s has no instance domain but an instance was specified\n",
+ mp->name);
+ return;
+ }
+
+ /* check that all of the metrics have the same instance domain */
+ if (n_selected > 1) {
+ i = 0;
+ for (i++, mp++; m < n_metrics; m++, mp++) {
+ if (!mp->status.selected)
+ continue;
+
+ /* check pointers to indoms; PM_INDOM_NULL has -1 */
+ if (first_mp->indom != mp->indom) {
+ if (i++ == 0) {
+ fprintf(stderr,
+ "The instance domains for the following metrics clash with %s:\n",
+ first_mp->name);
+ metric_cnt = PM_ERR_INDOM;
+ }
+ if (i < 5) { /* elide any domain errors after this */
+ fputs(mp->name, stderr);
+ putc('\n', stderr);
+ }
+ else {
+ fputs("(etc.)\n", stderr);
+ break;
+ }
+ }
+ }
+ if (i)
+ return;
+ }
+ first_inst = 0;
+ }
+
+ mp = first_mp; /* go back to first selected metric */
+ ip = &indom[mp->indom];
+ for (i = 0; i < ip->n_insts; i++)
+ if (name != NULL) {
+ if (strcasecmp(name, ip->name[i]) == 0)
+ break;
+ }
+ else
+ if (instid == ip->inst[i])
+ break;
+
+ for ( ; m < n_metrics; m++, mp++) {
+ if (!mp->status.selected)
+ continue;
+
+ /* check that metric with explicit instances has the specified inst.
+ * For those metrics that specify "all instances" an instance not there yet
+ * is OK (but generates a warning) since we don't need to give pmlogger any
+ * instance ids.
+ * For metrics with specific instances, an unknown instance is an error
+ * since the instance id can't be given to pmlogger.
+ */
+ if (i >= ip->n_insts) {
+ if (mp->status.has_insts || mp->status.new) {
+ metric_cnt = PM_ERR_INST;
+ if (name != NULL)
+ fprintf(stderr, "%s currently has no instance named %s\n", mp->name, name);
+ else
+ fprintf(stderr, "%s currently has no instance number %d\n", mp->name, instid);
+ if (!done_inst_msg) {
+ fputs(" - you may only specify metric instances active now. However if no\n"
+ " instances are specified, pmlogger will use all of the instances\n"
+ " available at the time the metric is logged\n",
+ stderr);
+ done_inst_msg = 1;
+ }
+ }
+ /* for an old metric specifying all instances warn if the specified
+ * instance is not currently in the instance domain.
+ */
+ else {
+ if (name != NULL)
+ fprintf(stderr,
+ "Warning: instance %s not currently in %s's instance domain\n",
+ name, mp->name);
+ else
+ fprintf(stderr,
+ "Warning: instance %d not currently in %s's instance domain\n",
+ instid, mp->name);
+ fputs(" (getting all instances anyway)\n", stderr);
+ }
+ continue;
+ }
+ inst = ip->inst[i];
+
+ /* check that we don't already have the same instance */
+ for (j = 0; j < mp->n_insts; j++)
+ if (inst == mp->inst[j])
+ break;
+
+ if (j < mp->n_insts) { /* already have inst */
+ if (mp->status.has_insts) {
+ if (name != NULL) {
+ fprintf(stderr,
+ "Warning: instance %s already specified for %s\n",
+ name, mp->name);
+ }
+ else {
+ fprintf(stderr,
+ "Warning: instance %d already specified for %s\n",
+ instid, mp->name);
+ }
+ }
+
+ /* if the metric had no insts of its own, (it specifies all insts)
+ * just do nothing. endmetgrp() will print a single message which
+ * is better than having, one printed for each instance specified.
+ */
+ continue;
+ }
+
+ else { /* don't have this inst */
+ /* add inst for new metric or old metric with explicit insts */
+ if (mp->status.new || mp->status.has_insts) {
+ j = mp->n_insts++;
+ if (j == 0)
+ mp->status.has_insts = 1;
+ need = mp->n_insts * sizeof(int);
+ if ((mp->inst = (int *)realloc(mp->inst, need)) == NULL) {
+ if (name != NULL)
+ fprintf(stderr, "%s inst %s: ", mp->name, name);
+ else
+ fprintf(stderr, "%s inst %d: ", mp->name, instid);
+ __pmNoMem("expanding instance array", need, PM_FATAL_ERR);
+ }
+ mp->inst[j] = inst;
+ }
+ }
+ }
+}
+
+#ifdef PCP_DEBUG
+void
+dumpmetrics(FILE *f)
+{
+ int i, j, k;
+ metric_t *mp;
+
+ fprintf(f, " Inst Inst Name\n");
+ fprintf(f, " ====== =========\n");
+
+ for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
+ fprintf(f, "%s\n", mp->name);
+ if (mp->indom == -1)
+ fprintf(f, " singular instance\n");
+ else {
+ indom_t *ip = &indom[mp->indom];
+ int *inst = ip->inst;
+ char **name = ip->name;
+ int n_insts = ip->n_insts;
+
+ /* No instances specified, use them all */
+ if (mp->n_insts == 0)
+ for (j = 0; j < n_insts; j++)
+ fprintf(f, " %6d %s\n", inst[j], name[j]);
+ else
+ for (j = 0; j < mp->n_insts; j++) {
+ int m_inst = mp->inst[j];;
+
+ for (k = 0; k < n_insts; k++)
+ if (m_inst == inst[k])
+ break;
+ if (k < n_insts)
+ fprintf(f, " %6d %s\n", inst[k], name[k]);
+ else
+ fprintf(f, " KAPOWIE! inst %d not found\n", m_inst);
+ }
+ }
+ putc('\n', f);
+ }
+}
+#endif