diff options
author | Igor Pashev <pashev.igor@gmail.com> | 2014-10-26 12:33:50 +0400 |
---|---|---|
committer | Igor Pashev <pashev.igor@gmail.com> | 2014-10-26 12:33:50 +0400 |
commit | 47e6e7c84f008a53061e661f31ae96629bc694ef (patch) | |
tree | 648a07f3b5b9d67ce19b0fd72e8caa1175c98f1a /src/pmlc | |
download | pcp-debian/3.9.10.tar.gz |
Debian 3.9.10debian/3.9.10debian
Diffstat (limited to 'src/pmlc')
-rw-r--r-- | src/pmlc/GNUmakefile | 46 | ||||
-rw-r--r-- | src/pmlc/actions.c | 868 | ||||
-rw-r--r-- | src/pmlc/gram.y | 336 | ||||
-rw-r--r-- | src/pmlc/lex.l | 268 | ||||
-rw-r--r-- | src/pmlc/pmlc.c | 398 | ||||
-rw-r--r-- | src/pmlc/pmlc.h | 95 | ||||
-rw-r--r-- | src/pmlc/util.c | 546 |
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 |