summaryrefslogtreecommitdiff
path: root/src/pmdas/logger
diff options
context:
space:
mode:
Diffstat (limited to 'src/pmdas/logger')
-rw-r--r--src/pmdas/logger/GNUmakefile56
-rw-r--r--src/pmdas/logger/Install164
-rw-r--r--src/pmdas/logger/README51
-rw-r--r--src/pmdas/logger/Remove24
-rw-r--r--src/pmdas/logger/event.c493
-rw-r--r--src/pmdas/logger/event.h56
-rw-r--r--src/pmdas/logger/help37
-rw-r--r--src/pmdas/logger/logger.c587
-rw-r--r--src/pmdas/logger/pmns23
-rw-r--r--src/pmdas/logger/root10
-rw-r--r--src/pmdas/logger/util.c181
-rw-r--r--src/pmdas/logger/util.h25
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(&timestamp, 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, &timestamp);
+ 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 */