diff options
Diffstat (limited to 'src/pmdas/logger')
-rw-r--r-- | src/pmdas/logger/GNUmakefile | 56 | ||||
-rw-r--r-- | src/pmdas/logger/Install | 164 | ||||
-rw-r--r-- | src/pmdas/logger/README | 51 | ||||
-rw-r--r-- | src/pmdas/logger/Remove | 24 | ||||
-rw-r--r-- | src/pmdas/logger/event.c | 493 | ||||
-rw-r--r-- | src/pmdas/logger/event.h | 56 | ||||
-rw-r--r-- | src/pmdas/logger/help | 37 | ||||
-rw-r--r-- | src/pmdas/logger/logger.c | 587 | ||||
-rw-r--r-- | src/pmdas/logger/pmns | 23 | ||||
-rw-r--r-- | src/pmdas/logger/root | 10 | ||||
-rw-r--r-- | src/pmdas/logger/util.c | 181 | ||||
-rw-r--r-- | src/pmdas/logger/util.h | 25 |
12 files changed, 1707 insertions, 0 deletions
diff --git a/src/pmdas/logger/GNUmakefile b/src/pmdas/logger/GNUmakefile new file mode 100644 index 0000000..e5b52f1 --- /dev/null +++ b/src/pmdas/logger/GNUmakefile @@ -0,0 +1,56 @@ +# +# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved. +# Copyright (c) 2011 Nathan Scott. All Rights Reversed. +# Copyright (c) 2011-2012 Red Hat Inc. +# +# 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 = pmdalogger$(EXECSUFFIX) +DFILES = README +CFILES = event.c util.c logger.c +HFILES = event.h util.h +LLDLIBS = $(PCP_PMDALIB) +LSRCFILES = Install Remove pmns help $(DFILES) root + +IAM = logger +DOMAIN = LOGGER +PMDADIR = $(PCP_PMDAS_DIR)/$(IAM) + +LDIRT = domain.h *.o $(IAM).log $(CMDTARGET) + +default: domain.h $(CMDTARGET) + +include $(BUILDRULES) + +install: default + $(INSTALL) -m 755 -d $(PMDADIR) + $(INSTALL) -m 755 Install Remove $(PMDADIR) + $(INSTALL) -m 644 $(DFILES) root help pmns $(PMDADIR) + $(INSTALL) -m 644 domain.h $(PMDADIR)/domain.h + $(INSTALL) -m 755 $(CMDTARGET) $(PMDADIR)/$(CMDTARGET) + +logger.o: domain.h +event.o logger.o: event.h +util.o event.o logger.o: util.h + +.NOTPARALLEL: +.ORDER: domain.h $(OBJECTS) + +default_pcp : default + +install_pcp : install + +domain.h: ../../pmns/stdpmid + $(DOMAIN_MAKERULE) diff --git a/src/pmdas/logger/Install b/src/pmdas/logger/Install new file mode 100644 index 0000000..10deb91 --- /dev/null +++ b/src/pmdas/logger/Install @@ -0,0 +1,164 @@ +#! /bin/sh +# +# Copyright (c) 2011-2012 Red Hat. +# Copyright (c) 1997 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. +# +# Install the logger PMDA and/or PMNS +# + +. $PCP_DIR/etc/pcp.env +. $PCP_SHARE_DIR/lib/pmdaproc.sh + +iam=logger +pmda_interface=5 +forced_restart=false + +pmdaSetup + +# be careful that mortals cannot write any configuration files, as +# these would present a security problem +# +umask 022 + +# PMDA variables +# +configfile="" + +_parsedefaults() +{ + echo "Extracting options from current installation ..." + while getopts D:d:l c + do + case $c in + \?) echo "Warning: Unrecognized option in $PCP_PMCDCONF_PATH" + echo " Remove line for the $iam PMDA in $PCP_PMCDCONF_PATH and re-run ./Install" + exit 2;; + * ) ;; + esac + done + eval configfile='$'$OPTIND +} + +# Get logfile(s) to monitor +if $do_pmda +then + # set options from $PCP_PMCDCONF_PATH, if possible + # + ans=`$PCP_AWK_PROG <$PCP_PMCDCONF_PATH ' +$1 == "'$iam'" { printf "%s",$6 + for (i=7;i<=NF;i++) printf " %s",$i + print "" + }'` + if [ ! -z "$ans" ] + then + _parsedefaults $ans + fi + + # go figure out which configuration file to use ... + # + #default_configfile=./sample.conf + default_configfile='' + pmdaChooseConfigFile + if [ ! -f "$configfile" ] + then + $PCP_ECHO_PROG $PCP_ECHO_N "Do you wish to enter logfile names and paths manually? [y] ""$PCP_ECHO_C" + read ans + if [ "X$ans" = "Xy" -o "X$ans" = "XY" -o -z "$ans" ] + then + configfile="$configdir/$iam.conf" + if [ -f "$configfile" ] + then + echo "Removing old configuration file \"$configfile\"" + rm -f "$configfile" + if [ -f "$configfile" ] + then + echo "Cannot remove \"$configfile\"" + exit 1 + fi + fi + + echo + echo \ +'Enter the PMNS name and logfile path. If the path ends in "|", the +filename is interpreted as a command which will output data. + +An empty line terminates the logfile selection process and there must +be at least one logfile specified. ' + + args="" + touch "$configfile" + if [ ! -f "$configfile" ] + then + echo "Installation aborted." + exit 1 + fi + + while [ ! -s "$configfile" ] + do + while true + do + $PCP_ECHO_PROG $PCP_ECHO_N "Logfile PMNS name: ""$PCP_ECHO_C" + read name + [ -z "$name" ] && break + + # Check name for invalid chars. + if ! echo $name | grep "^[A-Za-z][_A-Za-z0-9]*$" > /dev/null + then + echo \ +"Invalid characters in PMNS name: \"$name\". +Names must start with an alphabetic character ([a-zA-Z]). The rest of +the characters in the name must be alphanumeric ([a-zA-Z0-9]) or an +underscore ('_')." + continue + fi + + # Make sure name isn't already in the logfile. + if grep "^${name}[ \t]" "$configfile" >/dev/null + then + echo "Sorry, logfile PMNS name \"$name\" already specified. Please try again." + continue + fi + + $PCP_ECHO_PROG $PCP_ECHO_N "Logfile pathname: ""$PCP_ECHO_C" + read pathname + [ -z "$pathname" ] && break + if grep "[ \t]${pathname}$" "$configfile" >/dev/null + then + echo "Sorry, pathname \"$pathname\" already specified. Please try again." + continue + fi + + $PCP_ECHO_PROG $PCP_ECHO_N "Restricted access [n]: ""$PCP_ECHO_C" + read restrict + if [ "$restrict" = "y" -o "$restrict" = "yes" ]; then + restrict="y" + else + restrict="n" + fi + + echo "$name $restrict $pathname" >>"$configfile" + done + done + else + echo "" + echo "Error: Abandoning installation as no configuration file was specified." + exit 1 + fi + fi + + args="$configfile" +fi + +pmdaInstall + +exit 0 diff --git a/src/pmdas/logger/README b/src/pmdas/logger/README new file mode 100644 index 0000000..a19eeaf --- /dev/null +++ b/src/pmdas/logger/README @@ -0,0 +1,51 @@ +Logger PMDA +=========== + +This PMDA exports information about the event status of log files +specified in a config file. The default configuration file is +$PCP_VAR_DIR/config/logger/logger.conf, which should contain one +line for each file you wish to monitor. + +Metrics +======= + +The file ./help contains descriptions for all of the metrics exported +by this PMDA. + +Once the PMDA has been installed, the following command will list all +the available metrics and their explanatory "help" text: + + $ pminfo -fT logger + +Installation +============ + + + # cd $PCP_PMDAS_DIR/logger + + + Check that there is no clash in the Performance Metrics Domain + defined in ./domain.h and the other PMDAs currently in use (see + $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another + domain number. + + + Then simply use + + # ./Install + + and choose both the "collector" and "monitor" installation + configuration options -- everything else is automated. + +De-installation +=============== + + + Simply use + + # cd $PCP_PMDAS_DIR/logger + # ./Remove + +Troubleshooting +=============== + + + After installing or restarting the agent, the PMCD log file + ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file + ($PCP_LOG_DIR/pmcd/logger.log) should be checked for any warnings + or errors. diff --git a/src/pmdas/logger/Remove b/src/pmdas/logger/Remove new file mode 100644 index 0000000..6c014ef --- /dev/null +++ b/src/pmdas/logger/Remove @@ -0,0 +1,24 @@ +#! /bin/sh +# +# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved. +# Copyright (c) 2011 Red Hat Inc. +# +# 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. +# +# Remove the logger PMDA +# + +. $PCP_DIR/etc/pcp.env +. $PCP_SHARE_DIR/lib/pmdaproc.sh +iam=logger +pmdaSetup +pmdaRemove +exit 0 diff --git a/src/pmdas/logger/event.c b/src/pmdas/logger/event.c new file mode 100644 index 0000000..19ffda9 --- /dev/null +++ b/src/pmdas/logger/event.c @@ -0,0 +1,493 @@ +/* + * Event support for the Logger PMDA + * + * Copyright (c) 2011-2012 Red Hat. + * Copyright (c) 2011 Nathan Scott. 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 "event.h" +#include "pmda.h" +#include "util.h" +#include <ctype.h> +#ifdef HAVE_REGEX_H +#include <regex.h> +#endif + +static int numlogfiles; +static event_logfile_t *logfiles; + +void +event_init(pmID pmid) +{ + char cmd[MAXPATHLEN]; + int i, fd; + + for (i = 0; i < numlogfiles; i++) { + size_t pathlen = strlen(logfiles[i].pathname); + + /* + * We support 2 kinds of PATHNAMEs: + * (1) Regular paths. These paths are opened normally. + * (2) Pipes. If the path ends in '|', the filename is + * interpreted as a command which pipes input to us. + */ + if (logfiles[i].pathname[pathlen - 1] != '|') { + fd = open(logfiles[i].pathname, O_RDONLY|O_NONBLOCK); + if (fd < 0) { + if (logfiles[i].fd >= 0) /* log once only */ + __pmNotifyErr(LOG_ERR, "open: %s - %s", + logfiles[i].pathname, strerror(errno)); + } else { + if (fstat(fd, &logfiles[i].pathstat) < 0) + if (logfiles[i].fd >= 0) /* log once only */ + __pmNotifyErr(LOG_ERR, "fstat: %s - %s", + logfiles[i].pathname, strerror(errno)); + lseek(fd, 0, SEEK_END); + } + } + else { + strncpy(cmd, logfiles[i].pathname, sizeof(cmd)); + cmd[pathlen - 1] = '\0'; /* get rid of the '|' */ + rstrip(cmd); /* Remove all trailing whitespace. */ + fd = start_cmd(cmd, &logfiles[i].pid); + if (fd < 0) { + if (logfiles[i].fd >= 0) /* log once only */ + __pmNotifyErr(LOG_ERR, "pipe: %s - %s", + logfiles[i].pathname, strerror(errno)); + } else { + if (fd > maxfd) + maxfd = fd; + FD_SET(fd, &fds); + } + } + + logfiles[i].fd = fd; /* keep file descriptor (or error) */ + logfiles[i].pmid = pmid; /* string param metric identifier */ + logfiles[i].queueid = pmdaEventNewQueue(logfiles[i].pmnsname, maxmem); + } +} + +void +event_shutdown(void) +{ + int i; + + __pmNotifyErr(LOG_INFO, "%s: Shutting down...", __FUNCTION__); + + for (i = 0; i < numlogfiles; i++) { + if (logfiles[i].pid != 0) { + stop_cmd(logfiles[i].pid); + logfiles[i].pid = 0; + } + if (logfiles[i].fd > 0) { + close(logfiles[i].fd); + logfiles[i].fd = 0; + } + } +} + +/* + * Ensure given name (identifier) can be used as a namespace entry. + */ +static int +valid_pmns_name(char *name) +{ + if (!isalpha((int)name[0])) + return 0; + for (; *name != '\0'; name++) + if (!isalnum((int)*name) && *name != '_') + return 0; + return 1; +} + +/* + * Parse the configuration file and do initial data structure setup. + */ +int +event_config(const char *fname) +{ + FILE *configFile; + event_logfile_t *logfile; + int sts = 0; + size_t len; + char line[MAXPATHLEN * 2]; + char *ptr, *name, *noaccess; + + configFile = fopen(fname, "r"); + if (configFile == NULL) { + __pmNotifyErr(LOG_ERR, "event_config: %s: %s", fname, strerror(errno)); + return -1; + } + + while (!feof(configFile)) { + if (fgets(line, sizeof(line), configFile) == NULL) { + if (feof(configFile)) + break; + __pmNotifyErr(LOG_ERR, "event_config: fgets: %s", strerror(errno)); + sts = -1; + break; + } + + /* + * fgets() puts the '\n' at the end of the buffer. Remove + * it. If it isn't there, that must mean that the line is + * longer than our buffer. + */ + len = strlen(line); + if (len == 0) /* Ignore empty strings. */ + continue; + if (line[len - 1] != '\n') { /* String must be too long */ + __pmNotifyErr(LOG_ERR, "event_config: config line too long: '%s'", + line); + sts = -1; + break; + } + line[len - 1] = '\0'; /* Remove the '\n'. */ + + /* Strip all trailing whitespace. */ + rstrip(line); + + /* If the string is now empty or a comment, just ignore the line. */ + len = strlen(line); + if (len == 0) + continue; + if (line[0] == '#') + continue; + + /* Skip past all leading whitespace to find the start of + * NAME. */ + ptr = name = lstrip(line); + + /* Now we need to split the line into 3 parts: NAME, ACCESS + * and PATHNAME. NAME can't have whitespace in it, so look + * for the first non-whitespace. */ + while (*ptr != '\0' && ! isspace((int)*ptr)) { + ptr++; + } + /* If we're at the end, we didn't find any whitespace, so + * we've only got a NAME, with no ACCESS/PATHNAME. */ + if (*ptr == '\0') { + __pmNotifyErr(LOG_ERR, "event_config: badly formatted " + " configuration file line: '%s'", line); + sts = -1; + break; + } + /* Terminate NAME at the 1st whitespace. */ + *ptr++ = '\0'; + + /* Make sure NAME isn't too long. */ + if (strlen(name) > MAXPATHLEN) { + __pmNotifyErr(LOG_ERR, "event_config: name too long: '%s'", name); + sts = -1; + break; + } + + /* Make sure NAME is valid. */ + if (valid_pmns_name(name) == 0) { + __pmNotifyErr(LOG_ERR, "event_config: invalid name: '%s'", name); + sts = -1; + break; + } + + /* Skip past any extra whitespace between NAME and ACCESS */ + ptr = noaccess = lstrip(ptr); + + /* Look for the next whitespace, and that terminate ACCESS */ + while (*ptr != '\0' && ! isspace((int)*ptr)) { + ptr++; + } + + /* If we're at the end, we didn't find any whitespace, so + * we've only got NAME and ACCESS with no/PATHNAME. */ + if (*ptr == '\0') { + __pmNotifyErr(LOG_ERR, "event_config: badly formatted " + " configuration file line: '%s'", line); + sts = -1; + break; + } + /* Terminate ACCESS at the 1st whitespace. */ + *ptr++ = '\0'; + + /* Skip past any extra whitespace between ACCESS and PATHNAME */ + ptr = lstrip(ptr); + + /* Make sure PATHNAME (the rest of the line) isn't too long. */ + if (strlen(ptr) > MAXPATHLEN) { + __pmNotifyErr(LOG_ERR, "event_config: path is too long: '%s'", ptr); + sts = -1; + break; + } + + /* Now we've got a reasonable NAME/ACCESS/PATHNAME. Save them. */ + len = (numlogfiles + 1) * sizeof(event_logfile_t); + logfiles = realloc(logfiles, len); + if (logfiles == NULL) { + __pmNoMem("event_config", len, PM_FATAL_ERR); + sts = -1; + break; + } + logfile = &logfiles[numlogfiles]; + memset(logfile, 0, sizeof(*logfile)); + logfile->noaccess = (noaccess[0] == 'y' || noaccess[0] == 'Y'); + strncpy(logfile->pmnsname, name, sizeof(logfile->pmnsname)); + logfile->pmnsname[sizeof(logfile->pmnsname)-1] = '\0'; + strncpy(logfile->pathname, ptr, sizeof(logfile->pathname)); + logfile->pathname[sizeof(logfile->pathname)-1] = '\0'; + /* remaining fields filled in after pmdaInit() is called. */ + numlogfiles++; + + if (pmDebug & DBG_TRACE_APPL0) + __pmNotifyErr(LOG_INFO, "event_config: new logfile %s (%s)", + logfile->pathname, logfile->pmnsname); + } + + fclose(configFile); + if (sts < 0) { + free(logfiles); + return sts; + } + if (numlogfiles == 0) { + __pmNotifyErr(LOG_ERR, "event_config: no valid log files found"); + return -1; + } + return numlogfiles; +} + +static int +event_create(event_logfile_t *logfile) +{ + int j; + char *s, *p; + size_t offset; + ssize_t bytes; + struct timeval timestamp; + + static char *buffer; + static int bufsize; + + /* + * Using a static (global) event buffer to hold initial read. + * The aim is to reduce memory allocation until we know we'll + * need to keep something. + */ + if (!buffer) { + int sts = 0; + bufsize = 16 * getpagesize(); +#ifdef HAVE_POSIX_MEMALIGN + sts = posix_memalign((void **)&buffer, getpagesize(), bufsize); +#else +#ifdef HAVE_MEMALIGN + buffer = (char *)memalign(getpagesize(), bufsize); + if (buffer == NULL) sts = -1; +#else + buffer = (char *)malloc(bufsize); + if (buffer == NULL) sts = -1; +#endif +#endif + if (sts != 0) { + __pmNotifyErr(LOG_ERR, "event buffer allocation failure"); + return -1; + } + } + + offset = 0; +multiread: + if (logfile->fd < 0) + return 0; + bytes = read(logfile->fd, buffer + offset, bufsize - 1 - offset); + /* + * Ignore the error if: + * - we've got EOF (0 bytes read) + * - EBADF (fd isn't valid - most likely a closed pipe) + * - EAGAIN/EWOULDBLOCK (fd is marked nonblocking and read would block) + * - EINVAL/EISDIR (fd is a directory - config file botch) + */ + if (bytes == 0) + return 0; + if (bytes < 0 && (errno == EBADF || errno == EISDIR || errno == EINVAL)) + return 0; + if (bytes < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) + return 0; + if (bytes > maxmem) + return 0; + if (bytes < 0) { + __pmNotifyErr(LOG_ERR, "read failure on %s: %s", + logfile->pathname, strerror(errno)); + return -1; + } + + gettimeofday(×tamp, NULL); + buffer[bufsize-1] = '\0'; + for (s = p = buffer, j = 0; *s != '\0' && j < bufsize-1; s++, j++) { + if (*s != '\n') + continue; + *s = '\0'; + bytes = (s+1) - p; + pmdaEventQueueAppend(logfile->queueid, p, bytes, ×tamp); + p = s + 1; + } + /* did we just do a full buffer read? */ + if (p == buffer) { + char msg[64]; + __pmNotifyErr(LOG_ERR, "Ignoring long (%d bytes) line: \"%s\"", (int) + bytes, __pmdaEventPrint(p, bytes, msg, sizeof(msg))); + } else if (j == bufsize - 1) { + offset = bufsize-1 - (p - buffer); + memmove(buffer, p, offset); + goto multiread; /* read rest of line */ + } + return 1; +} + +void +event_refresh(void) +{ + struct event_logfile *logfile; + struct stat pathstat; + int i, fd, sts; + + for (i = 0; i < numlogfiles; i++) { + logfile = &logfiles[i]; + + if (logfile->pid > 0) /* process pipe */ + goto events; + if (stat(logfile->pathname, &pathstat) < 0) { + if (logfile->fd >= 0) { + close(logfile->fd); + logfile->fd = -1; + } + memset(&logfile->pathstat, 0, sizeof(logfile->pathstat)); + } else { + /* reopen if no descriptor before, or log rotated (new file) */ + if (logfile->fd < 0 || + logfile->pathstat.st_ino != pathstat.st_ino || + logfile->pathstat.st_dev != pathstat.st_dev) { + if (logfile->fd >= 0) + close(logfile->fd); + fd = open(logfile->pathname, O_RDONLY|O_NONBLOCK); + if (fd < 0 && logfile->fd >= 0) /* log once */ + __pmNotifyErr(LOG_ERR, "open: %s - %s", + logfile->pathname, strerror(errno)); + logfile->fd = fd; + } else { + if ((S_ISREG(pathstat.st_mode)) && + (memcmp(&logfile->pathstat.st_mtime, &pathstat.st_mtime, + sizeof(pathstat.st_mtime))) == 0) + continue; + } + logfile->pathstat = pathstat; +events: + do { + sts = event_create(logfile); + } while (sts != 0); + } + } +} + +int +event_logcount(void) +{ + return numlogfiles; +} + +int +event_queueid(int handle) +{ + if (handle < 0 || handle >= numlogfiles) + return 0; + + /* if logfile unrestricted, allow this client access to this queue */ + if (logfiles[handle].noaccess == 0) + pmdaEventSetAccess(pmdaGetContext(), logfiles[handle].queueid, 1); + + return logfiles[handle].queueid; +} + +__uint64_t +event_pathsize(int handle) +{ + if (handle < 0 || handle >= numlogfiles) + return 0; + return logfiles[handle].pathstat.st_size; +} + +const char * +event_pathname(int handle) +{ + if (handle < 0 || handle >= numlogfiles) + return NULL; + return logfiles[handle].pathname; +} + +const char * +event_pmnsname(int handle) +{ + if (handle < 0 || handle >= numlogfiles) + return NULL; + return logfiles[handle].pmnsname; +} + +pmID +event_pmid(int handle) +{ + if (handle < 0 || handle >= numlogfiles) + return 0; + return logfiles[handle].pmid; +} + +int +event_decoder(int eventarray, void *buffer, size_t size, + struct timeval *timestamp, void *data) +{ + int sts, handle = *(int *)data; + pmID pmid = event_pmid(handle); + pmAtomValue atom; + + sts = pmdaEventAddRecord(eventarray, timestamp, PM_EVENT_FLAG_POINT); + if (sts < 0) + return sts; + atom.cp = buffer; + sts = pmdaEventAddParam(eventarray, pmid, PM_TYPE_STRING, &atom); + if (sts < 0) + return sts; + return 1; /* simple decoder, added just one event array */ +} + +int +event_regex_apply(void *rp, void *data, size_t size) +{ + regex_t *regex = (regex_t *)rp; + return regexec(regex, data, 0, NULL, 0) == REG_NOMATCH; +} + +void +event_regex_release(void *rp) +{ + regex_t *regex = (regex_t *)rp; + regfree(regex); +} + +int +event_regex_alloc(const char *string, void **filter) +{ + regex_t *regex = malloc(sizeof(regex_t)); + + if (regex == NULL) + return -ENOMEM; + if (regcomp(regex, string, REG_EXTENDED|REG_NOSUB) != 0) { + free(regex); + return PM_ERR_CONV; + } + *filter = (void *)regex; + return 0; +} diff --git a/src/pmdas/logger/event.h b/src/pmdas/logger/event.h new file mode 100644 index 0000000..b1f176f --- /dev/null +++ b/src/pmdas/logger/event.h @@ -0,0 +1,56 @@ +/* + * Event support for the Logger PMDA + * + * Copyright (c) 2011 Red Hat Inc. + * Copyright (c) 2011 Nathan Scott. All rights reversed. + * + * 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 _EVENT_H +#define _EVENT_H + +#include "pmapi.h" +#include "impl.h" +#include <sys/stat.h> + +typedef struct event_logfile { + pmID pmid; + int fd; + pid_t pid; + int queueid; + int noaccess; + struct stat pathstat; + char pmnsname[MAXPATHLEN]; + char pathname[MAXPATHLEN]; +} event_logfile_t; + +extern int maxfd; +extern fd_set fds; +extern long maxmem; + +extern void event_init(pmID pmid); +extern void event_shutdown(void); +extern void event_refresh(void); +extern int event_config(const char *filename); + +extern int event_logcount(void); +extern pmID event_pmid(int handle); +extern int event_queueid(int handle); +extern __uint64_t event_pathsize(int handle); +extern const char *event_pathname(int handle); +extern const char *event_pmnsname(int handle); +extern int event_decoder(int arrayid, void *buffer, size_t size, + struct timeval *timestamp, void *data); +extern int event_regex_alloc(const char *s, void **filter); +extern int event_regex_apply(void *rp, void *data, size_t size); +extern void event_regex_release(void *rp); + +#endif /* _EVENT_H */ diff --git a/src/pmdas/logger/help b/src/pmdas/logger/help new file mode 100644 index 0000000..b7c1c6c --- /dev/null +++ b/src/pmdas/logger/help @@ -0,0 +1,37 @@ +# +# 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. +# +# logger PMDA help file in the ASCII format +# +# lines beginning with a # are ignored +# lines beginning @ introduce a new entry of the form +# @ metric_name oneline-text +# help test goes +# here over multiple lines +# ... +# +# the metric_name is decoded against the default PMNS -- as a special case, +# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an +# instance domain identification, and the text describes the instance domain +# +# blank lines before the @ line are ignored +# + +@ logger.numclients The number of attached clients +The number of attached clients. +@ logger.numlogfiles The number of monitored logfiles +The number of monitored logfiles. +@ logger.param_string String event data +String event data. +@ logger.maxmem Maximum number of queued event bytes +Maximum number of queued event bytes for each log file. diff --git a/src/pmdas/logger/logger.c b/src/pmdas/logger/logger.c new file mode 100644 index 0000000..98689d7 --- /dev/null +++ b/src/pmdas/logger/logger.c @@ -0,0 +1,587 @@ +/* + * Logger, a configurable log file monitoring PMDA + * + * Copyright (c) 2011-2012 Red Hat. + * Copyright (c) 2011 Nathan Scott. All Rights Reserved. + * Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * Debug options + * APPL0 configfile processing and PMNS setup + * APPL1 loading event data from the log files + * APPL2 interaction with PMCD + */ + +#include "domain.h" +#include "event.h" +#include "util.h" +#include "pmda.h" + +/* + * Logger PMDA + * + * Metrics + * logger.numclients - number of attached clients + * logger.numlogfiles - number of monitored logfiles + * logger.param_string - string event data + * logger.perfile.{LOGFILE}.count - observed event count + * logger.perfile.{LOGFILE}.bytes - observed events size + * logger.perfile.{LOGFILE}.size - logfile size + * logger.perfile.{LOGFILE}.path - logfile path + * logger.perfile.{LOGFILE}.numclients - number of attached + * clients/logfile + * logger.perfile.{LOGFILE}.records - event records/logfile + */ + +#define DEFAULT_MAXMEM (2 * 1024 * 1024) /* 2 megabytes */ +long maxmem; + +int maxfd; +fd_set fds; +static int interval_expired; +static struct timeval interval = { 2, 0 }; +static char *username; + +static int nummetrics; +static __pmnsTree *pmns; + +typedef struct dynamic_metric_info { + int handle; + int pmid_index; + const char *help_text; +} dynamic_metric_info_t; +static dynamic_metric_info_t *dynamic_metric_infotab; + +static pmdaMetric dynamic_metrictab[] = { +/* perfile.{LOGFILE}.count */ + { NULL, /* m_user gets filled in later */ + { 0 /* pmid gets filled in later */, PM_TYPE_U32, PM_INDOM_NULL, + PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, }, +/* perfile.{LOGFILE}.bytes */ + { NULL, /* m_user gets filled in later */ + { 0 /* pmid gets filled in later */, PM_TYPE_U64, PM_INDOM_NULL, + PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, }, +/* perfile.{LOGFILE}.size */ + { NULL, /* m_user gets filled in later */ + { 0 /* pmid gets filled in later */, PM_TYPE_U64, PM_INDOM_NULL, + PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, }, +/* perfile.{LOGFILE}.path */ + { NULL, /* m_user gets filled in later */ + { 0 /* pmid gets filled in later */, PM_TYPE_STRING, PM_INDOM_NULL, + PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) }, }, +/* perfile.{LOGFILE}.numclients */ + { NULL, /* m_user gets filled in later */ + { 0 /* pmid gets filled in later */, PM_TYPE_U32, PM_INDOM_NULL, + PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, }, +/* perfile.{LOGFILE}.records */ + { NULL, /* m_user gets filled in later */ + { 0 /* pmid gets filled in later */, PM_TYPE_EVENT, PM_INDOM_NULL, + PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, }, +/* perfile.{LOGFILE}.queuemem */ + { NULL, /* m_user gets filled in later */ + { 0 /* pmid gets filled in later */, PM_TYPE_U64, PM_INDOM_NULL, + PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, }, +}; + +static char *dynamic_nametab[] = { +/* perfile.{LOGFILE}.count */ + "count", +/* perfile.{LOGFILE}.bytes */ + "bytes", +/* perfile.{LOGFILE}.size */ + "size", +/* perfile.{LOGFILE}.path */ + "path", +/* perfile.{LOGFILE}.numclients */ + "numclients", +/* perfile.{LOGFILE}.records */ + "records", +/* perfile.{LOGFILE}.queuemem */ + "queuemem", +}; + +static const char *dynamic_helptab[] = { +/* perfile.{LOGFILE}.count */ + "The cumulative number of events seen for this logfile.", +/* perfile.{LOGFILE}.bytes */ + "Cumulative number of bytes in events seen for this logfile.", +/* perfile.{LOGFILE}.size */ + "The current size of this logfile.", +/* perfile.{LOGFILE}.path */ + "The path for this logfile.", +/* perfile.{LOGFILE}.numclients */ + "The number of attached clients for this logfile.", +/* perfile.{LOGFILE}.records */ + "Event records for this logfile.", +/* perfile.{LOGFILE}.queuemem */ + "Amount of memory used for event data.", +}; + +static pmdaMetric static_metrictab[] = { +/* numclients */ + { NULL, + { PMDA_PMID(0,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, + PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, }, +/* numlogfiles */ + { NULL, + { PMDA_PMID(0,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE, + PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, }, +/* param_string */ + { NULL, + { PMDA_PMID(0,2), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT, + PMDA_PMUNITS(0,0,0,0,0,0) }, }, +/* perfile.maxmem */ + { NULL, /* m_user gets filled in later */ + { PMDA_PMID(0,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE, + PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, }, +}; + +static pmdaMetric *metrictab; + +static int +logger_profile(__pmProfile *prof, pmdaExt *pmda) +{ + pmdaEventNewClient(pmda->e_context); + return 0; +} + +static int +logger_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda) +{ + pmdaEventNewClient(pmda->e_context); + return pmdaFetch(numpmid, pmidlist, resp, pmda); +} + +static int +valid_pmid(unsigned int cluster, unsigned int item) +{ + if (cluster != 0 || item > nummetrics) + return PM_ERR_PMID; + return 0; +} + +static int +logger_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom) +{ + __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid); + int sts; + + if ((sts = valid_pmid(idp->cluster, idp->item)) < 0) + return sts; + + sts = PMDA_FETCH_STATIC; + if (idp->item < 4) { + switch (idp->item) { + case 0: /* logger.numclients */ + sts = pmdaEventClients(atom); + break; + case 1: /* logger.numlogfiles */ + atom->ul = event_logcount(); + break; + case 2: /* logger.param_string */ + sts = PMDA_FETCH_NOVALUES; + break; + case 3: /* logger.maxmem */ + atom->ull = (unsigned long long)maxmem; + break; + default: + return PM_ERR_PMID; + } + } + else { + dynamic_metric_info_t *pinfo; + int queue; + + if ((pinfo = ((mdesc != NULL) ? mdesc->m_user : NULL)) == NULL) + return PM_ERR_PMID; + queue = event_queueid(pinfo->handle); + + switch (pinfo->pmid_index) { + case 0: /* perfile.{LOGFILE}.count */ + sts = pmdaEventQueueCounter(queue, atom); + break; + case 1: /* perfile.{LOGFILE}.bytes */ + sts = pmdaEventQueueBytes(queue, atom); + break; + case 2: /* perfile.{LOGFILE}.size */ + atom->ull = event_pathsize(pinfo->handle); + break; + case 3: /* perfile.{LOGFILE}.path */ + atom->cp = (char *)event_pathname(pinfo->handle); + break; + case 4: /* perfile.{LOGFILE}.numclients */ + sts = pmdaEventQueueClients(queue, atom); + break; + case 5: /* perfile.{LOGFILE}.records */ + sts = pmdaEventQueueRecords(queue, atom, pmdaGetContext(), + event_decoder, &pinfo->handle); + break; + case 6: /* perfile.{LOGFILE}.queuemem */ + sts = pmdaEventQueueMemory(queue, atom); + break; + default: + return PM_ERR_PMID; + } + } + return sts; +} + +static int +logger_store(pmResult *result, pmdaExt *pmda) +{ + int i, j, sts; + + pmdaEventNewClient(pmda->e_context); + + for (i = 0; i < result->numpmid; i++) { + pmValueSet *vsp = result->vset[i]; + __pmID_int *idp = (__pmID_int *)&vsp->pmid; + dynamic_metric_info_t *pinfo = NULL; + void *filter; + int queueid; + + if ((sts = valid_pmid(idp->cluster, idp->item)) < 0) + return sts; + for (j = 0; j < pmda->e_nmetrics; j++) { + if (vsp->pmid == pmda->e_metrics[j].m_desc.pmid) { + pinfo = pmda->e_metrics[j].m_user; + break; + } + } + if (pinfo == NULL) + return PM_ERR_PMID; + if (pinfo->pmid_index != 5) + return PM_ERR_PERMISSION; + queueid = event_queueid(pinfo->handle); + + if (vsp->numval != 1 || vsp->valfmt != PM_VAL_SPTR) + return PM_ERR_CONV; + + sts = event_regex_alloc(vsp->vlist[0].value.pval->vbuf, &filter); + if (sts < 0) + return sts; + + sts = pmdaEventSetFilter(pmda->e_context, queueid, filter, + event_regex_apply, event_regex_release); + if (sts < 0 ) + return sts; + } + return 0; +} + +static void +logger_end_contextCallBack(int context) +{ + pmdaEventEndClient(context); +} + +static int +logger_pmid(const char *name, pmID *pmid, pmdaExt *pmda) +{ + pmdaEventNewClient(pmda->e_context); + return pmdaTreePMID(pmns, name, pmid); +} + +static int +logger_name(pmID pmid, char ***nameset, pmdaExt *pmda) +{ + pmdaEventNewClient(pmda->e_context); + return pmdaTreeName(pmns, pmid, nameset); +} + +static int +logger_children(const char *name, int traverse, char ***kids, int **sts, + pmdaExt *pmda) +{ + pmdaEventNewClient(pmda->e_context); + return pmdaTreeChildren(pmns, name, traverse, kids, sts); +} + +static int +logger_text(int ident, int type, char **buffer, pmdaExt *pmda) +{ + int numstatics = sizeof(static_metrictab)/sizeof(static_metrictab[0]); + + pmdaEventNewClient(pmda->e_context); + + if ((type & PM_TEXT_PMID) == PM_TEXT_PMID) { + /* Lookup pmid in the metric table. */ + int item = pmid_item(ident); + + /* If the PMID item was for a dynamic metric... */ + if (item >= numstatics && item < nummetrics + /* and the PMID matches... */ + && metrictab[item].m_desc.pmid == (pmID)ident + /* and we've got user data... */ + && metrictab[item].m_user != NULL) { + dynamic_metric_info_t *pinfo = metrictab[item].m_user; + + /* Return the correct help text. */ + *buffer = (char *)pinfo->help_text; + return 0; + } + } + return pmdaText(ident, type, buffer, pmda); +} + +void +logger_init(pmdaInterface *dp, const char *configfile) +{ + size_t size; + int i, j, sts, item, numloggers; + int numstatics = sizeof(static_metrictab)/sizeof(static_metrictab[0]); + int numdynamics = sizeof(dynamic_metrictab)/sizeof(dynamic_metrictab[0]); + pmdaMetric *pmetric; + char name[MAXPATHLEN * 2]; + dynamic_metric_info_t *pinfo; + + __pmSetProcessIdentity(username); + + /* Read and parse config file. */ + if ((numloggers = event_config(configfile)) < 0) + return; + + /* Create the dynamic metric info table based on the logfile table */ + size = sizeof(struct dynamic_metric_info) * numdynamics * numloggers; + if ((dynamic_metric_infotab = malloc(size)) == NULL) { + __pmNoMem("logger_init(dynamic)", size, PM_FATAL_ERR); + return; + } + pinfo = dynamic_metric_infotab; + for (i = 0; i < numloggers; i++) { + for (j = 0; j < numdynamics; j++) { + pinfo->handle = i; + pinfo->pmid_index = j; + pinfo->help_text = dynamic_helptab[j]; + pinfo++; + } + } + + /* Create the metric table based on the static and dynamic metric tables */ + nummetrics = numstatics + (numloggers * numdynamics); + size = sizeof(pmdaMetric) * nummetrics; + if ((metrictab = malloc(size)) == NULL) { + free(dynamic_metric_infotab); + __pmNoMem("logger_init(static)", size, PM_FATAL_ERR); + return; + } + memcpy(metrictab, static_metrictab, sizeof(static_metrictab)); + pmetric = &metrictab[numstatics]; + pinfo = dynamic_metric_infotab; + item = numstatics; + for (i = 0; i < numloggers; i++) { + memcpy(pmetric, dynamic_metrictab, sizeof(dynamic_metrictab)); + for (j = 0; j < numdynamics; j++) { + pmetric[j].m_desc.pmid = PMDA_PMID(0, item++); + pmetric[j].m_user = pinfo++; + } + pmetric += numdynamics; + } + + if (dp->status != 0) + return; + + dp->version.four.fetch = logger_fetch; + dp->version.four.store = logger_store; + dp->version.four.profile = logger_profile; + dp->version.four.pmid = logger_pmid; + dp->version.four.name = logger_name; + dp->version.four.children = logger_children; + dp->version.four.text = logger_text; + + pmdaSetFetchCallBack(dp, logger_fetchCallBack); + pmdaSetEndContextCallBack(dp, logger_end_contextCallBack); + + pmdaInit(dp, NULL, 0, metrictab, nummetrics); + + /* Create the dynamic PMNS tree and populate it. */ + if ((sts = __pmNewPMNS(&pmns)) < 0) { + __pmNotifyErr(LOG_ERR, "%s: failed to create new pmns: %s\n", + pmProgname, pmErrStr(sts)); + pmns = NULL; + return; + } + pmetric = &metrictab[numstatics]; + for (i = 0; i < numloggers; i++) { + const char *id = event_pmnsname(i); + for (j = 0; j < numdynamics; j++) { + snprintf(name, sizeof(name), + "logger.perfile.%s.%s", id, dynamic_nametab[j]); + __pmAddPMNSNode(pmns, pmetric[j].m_desc.pmid, name); + } + pmetric += numdynamics; + } + /* for reverse (pmid->name) lookups */ + pmdaTreeRebuildHash(pmns, (numloggers * numdynamics)); + + /* initialise the event and client tracking code */ + event_init(metrictab[2].m_desc.pmid); +} + +static void +logger_timer(int sig, void *ptr) +{ + interval_expired = 1; +} + +void +loggerMain(pmdaInterface *dispatch) +{ + fd_set readyfds; + int nready, pmcdfd; + + pmcdfd = __pmdaInFd(dispatch); + if (pmcdfd > maxfd) + maxfd = pmcdfd; + + FD_ZERO(&fds); + FD_SET(pmcdfd, &fds); + + /* arm interval timer */ + if (__pmAFregister(&interval, NULL, logger_timer) < 0) { + __pmNotifyErr(LOG_ERR, "registering event interval handler"); + exit(1); + } + + for (;;) { + memcpy(&readyfds, &fds, sizeof(readyfds)); + nready = select(maxfd+1, &readyfds, NULL, NULL, NULL); + if (pmDebug & DBG_TRACE_APPL2) + __pmNotifyErr(LOG_DEBUG, "select: nready=%d interval=%d", + nready, interval_expired); + if (nready < 0) { + if (neterror() != EINTR) { + __pmNotifyErr(LOG_ERR, "select failure: %s", netstrerror()); + exit(1); + } else if (!interval_expired) { + continue; + } + } + + __pmAFblock(); + if (nready > 0 && FD_ISSET(pmcdfd, &readyfds)) { + if (pmDebug & DBG_TRACE_APPL0) + __pmNotifyErr(LOG_DEBUG, "processing pmcd PDU [fd=%d]", pmcdfd); + if (__pmdaMainPDU(dispatch) < 0) { + __pmAFunblock(); + exit(1); /* fatal if we lose pmcd */ + } + if (pmDebug & DBG_TRACE_APPL0) + __pmNotifyErr(LOG_DEBUG, "completed pmcd PDU [fd=%d]", pmcdfd); + } + if (interval_expired) { + interval_expired = 0; + event_refresh(); + } + __pmAFunblock(); + } +} + +static void +convertUnits(char **endnum, long *maxmem) +{ + switch ((int) **endnum) { + case 'b': + case 'B': + break; + case 'k': + case 'K': + *maxmem *= 1024; + break; + case 'm': + case 'M': + *maxmem *= 1024 * 1024; + break; + case 'g': + case 'G': + *maxmem *= 1024 * 1024 * 1024; + break; + } + (*endnum)++; +} + +static void +usage(void) +{ + fprintf(stderr, + "Usage: %s [options] configfile\n\n" + "Options:\n" + " -d domain use domain (numeric) for metrics domain of PMDA\n" + " -l logfile write log into logfile rather than the default\n" + " -m memory maximum memory used per logfile (default %ld bytes)\n" + " -s interval default delay between iterations (default %d sec)\n" + " -U username user account to run under (default \"pcp\")\n", + pmProgname, maxmem, (int)interval.tv_sec); + exit(1); +} + +int +main(int argc, char **argv) +{ + static char helppath[MAXPATHLEN]; + char *endnum; + pmdaInterface desc; + long minmem; + int c, err = 0, sep = __pmPathSeparator(); + + __pmSetProgname(argv[0]); + __pmGetUsername(&username); + + minmem = getpagesize(); + maxmem = (minmem > DEFAULT_MAXMEM) ? minmem : DEFAULT_MAXMEM; + snprintf(helppath, sizeof(helppath), "%s%c" "logger" "%c" "help", + pmGetConfig("PCP_PMDAS_DIR"), sep, sep); + pmdaDaemon(&desc, PMDA_INTERFACE_5, pmProgname, LOGGER, + "logger.log", helppath); + + while ((c = pmdaGetOpt(argc, argv, "D:d:l:m:s:U:?", &desc, &err)) != EOF) { + switch (c) { + case 'm': + maxmem = strtol(optarg, &endnum, 10); + if (*endnum != '\0') + convertUnits(&endnum, &maxmem); + if (*endnum != '\0' || maxmem < minmem) { + fprintf(stderr, "%s: invalid max memory '%s' (min=%ld)\n", + pmProgname, optarg, minmem); + err++; + } + break; + + case 's': + if (pmParseInterval(optarg, &interval, &endnum) < 0) { + fprintf(stderr, "%s: -s requires a time interval: %s\n", + pmProgname, endnum); + free(endnum); + err++; + } + break; + + case 'U': + username = optarg; + break; + + default: + err++; + break; + } + } + + if (err || optind != argc -1) + usage(); + + pmdaOpenLog(&desc); + logger_init(&desc, argv[optind]); + pmdaConnect(&desc); + loggerMain(&desc); + event_shutdown(); + exit(0); +} diff --git a/src/pmdas/logger/pmns b/src/pmdas/logger/pmns new file mode 100644 index 0000000..4b9827a --- /dev/null +++ b/src/pmdas/logger/pmns @@ -0,0 +1,23 @@ +/* + * Metrics for logger PMDA + * + * Copyright (c) 2011 Red Hat Inc. + * + * 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. + */ + +logger { + numclients LOGGER:0:0 + numlogfiles LOGGER:0:1 + param_string LOGGER:0:2 + maxmem LOGGER:0:3 + perfile LOGGER:*:* +} diff --git a/src/pmdas/logger/root b/src/pmdas/logger/root new file mode 100644 index 0000000..51218c4 --- /dev/null +++ b/src/pmdas/logger/root @@ -0,0 +1,10 @@ +/* + * fake "root" for validating the local PMNS subtree + */ + +#include <stdpmid> + +root { logger } + +#include "pmns" + diff --git a/src/pmdas/logger/util.c b/src/pmdas/logger/util.c new file mode 100644 index 0000000..c79787f --- /dev/null +++ b/src/pmdas/logger/util.c @@ -0,0 +1,181 @@ +/* + * Utility functions for the logger PMDA. + * + * Copyright (c) 2011 Red Hat Inc. + * + * 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 <stdio.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#include "pmapi.h" +#include "impl.h" +#include "util.h" + +void +rstrip(char *str) +{ + char *ptr; + + /* Remove all trailing whitespace. Set ptr to last char of + * string. */ + ptr = str + strlen(str) - 1; + /* While trailing whitespace, move back. */ + while (ptr >= str && isspace((int)*ptr)) { + --ptr; + } + *(ptr+1) = '\0'; /* Now set '\0' as terminal byte. */ +} + +char * +lstrip(char *str) +{ + /* While leading whitespace, move forward. */ + char *ptr = str; + while (*ptr != '\0' && isspace((int)*ptr)) { + ptr++; + } + return ptr; +} + +int +start_cmd(const char *cmd, pid_t *ppid) +{ + pid_t child_pid; + int rc; + int pipe_fds[2]; +#define PARENT_END 0 /* parent end of the pipe */ +#define CHILD_END 1 /* child end of the pipe */ +#define STDOUT_FD 1 /* stdout fd */ + + /* FIXME items: + * (1) Should we be looking to handle shell metachars + * differently? Perhaps we should just allow isalnum()||isspace() + * chars only. + * (2) Do we need to clean up the environment in the child before + * the exec()? Remove things like IFS, CDPATH, ENV, and BASH_ENV. + */ + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + __pmNotifyErr(LOG_INFO, "%s: Trying to run command: %s", __FUNCTION__, + cmd); +#endif + + /* Create the pipes. */ +#if defined(HAVE_PIPE2) + rc = pipe2(pipe_fds, O_CLOEXEC|O_NONBLOCK); + if (rc < 0) { + __pmNotifyErr(LOG_ERR, "%s: pipe2() returned %s", __FUNCTION__, + strerror(-rc)); + return rc; + } +#else + rc = pipe(pipe_fds); + if (rc < 0) { + __pmNotifyErr(LOG_ERR, "%s: pipe() returned %s", __FUNCTION__, + strerror(-rc)); + return rc; + } + + /* Set the right flags on the pipes. */ + if (fcntl(pipe_fds[PARENT_END], F_SETFL, O_NDELAY) < 0 + || fcntl(pipe_fds[CHILD_END], F_SETFL, O_NDELAY) < 0) { + __pmNotifyErr(LOG_ERR, "%s: fcntl() returned %s", __FUNCTION__, + strerror(-rc)); + return rc; + } + if (fcntl(pipe_fds[PARENT_END], F_SETFD, O_CLOEXEC) < 0 + || fcntl(pipe_fds[CHILD_END], F_SETFD, O_CLOEXEC) < 0) { + __pmNotifyErr(LOG_ERR, "%s: fcntl() returned %s", __FUNCTION__, + strerror(-rc)); + return rc; + } +#endif + + /* Create the new process. */ + child_pid = fork(); + if (child_pid == 0) { /* child process */ + int i; + + /* Duplicate our pipe fd onto stdout of the child. Note that + * this clears O_CLOEXEC, so the new stdout should stay open + * when we call exec(). */ + if (pipe_fds[CHILD_END] != STDOUT_FD) { + if (dup2(pipe_fds[CHILD_END], STDOUT_FD) < 0) { + __pmNotifyErr(LOG_ERR, "%s: dup2() returned %s", __FUNCTION__, + strerror(errno)); + _exit(127); + } + } + + /* Close all other fds. */ + for (i = 0; i <= pipe_fds[CHILD_END]; i++) { + if (i != STDOUT_FD) { + close(i); + } + } + + /* Actually run the command. */ + execl ("/bin/sh", "sh", "-c", cmd, (char *)NULL); + _exit (127); + + } + else if (child_pid > 0) { /* parent process */ + close (pipe_fds[CHILD_END]); + if (ppid != NULL) { + *ppid = child_pid; + } + } + else if (child_pid < 0) { /* fork error */ + int errno_save = errno; + + __pmNotifyErr(LOG_ERR, "%s: fork() returned %s", __FUNCTION__, + strerror(errno_save)); + close (pipe_fds[PARENT_END]); + close (pipe_fds[CHILD_END]); + + return -errno_save; + } + + return pipe_fds[PARENT_END]; +} + +int +stop_cmd(pid_t pid) +{ + int rc; + pid_t wait_pid; + int wstatus; + + __pmNotifyErr(LOG_INFO, "%s: killing pid %" FMT_PID, __FUNCTION__, pid); + + /* Send the TERM signal. */ + rc = kill(pid, SIGTERM); + __pmNotifyErr(LOG_INFO, "%s: kill returned %d", __FUNCTION__, rc); + + /* Wait for the process to go away. */ + do { + wait_pid = waitpid (pid, &wstatus, 0); + __pmNotifyErr(LOG_INFO, "%s: waitpid returned %d", __FUNCTION__, + (int)wait_pid); + } while (wait_pid == -1 && errno == EINTR); + + /* Return process exit status. */ + return wstatus; +} diff --git a/src/pmdas/logger/util.h b/src/pmdas/logger/util.h new file mode 100644 index 0000000..a26ac2b --- /dev/null +++ b/src/pmdas/logger/util.h @@ -0,0 +1,25 @@ +/* + * Utility routines. + * + * Copyright (c) 2011 Red Hat Inc. + * + * 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 _UTIL_H +#define _UTIL_H + +extern char *lstrip(char *str); +extern void rstrip(char *str); +extern int start_cmd(const char *cmd, pid_t *ppid); +extern int stop_cmd(pid_t pid); + +#endif /* _UTIL_H */ |