On Fri, Dec 16, 2005 at 09:22:49AM +0100, Arnaud Quette wrote: > 2005/12/15, Alexander V. Inyukhin <[EMAIL PROTECTED]>: > > > I think it is a good idea to log ups variables to rrd > > database instead of log file. So, I create standalone > > rrd logger dervied from upslog nut client. > > Of course, this could be easily implemented using > > upsc or upslog clients and a cron script > > but stanalone application is better for some reasons. > > > > Does anybody else need this functionality? > > Should such a client be included in the nut > > (with additional librrd dependency) or nut-rrd > > package or distributed separately? > > > interesting point: I'm thinking of some logging improvements, such as: > - making upslog a real daemon, able to log data from several UPSs, > - making log asynchronous and smart (ie log only things that have changed), > - switching by default to a NUT specific logfile (ie upsd.log), instead of > the syslog default, > - adding the ability to log to other system, such as RRD... > > So, I'm definitly interested in your work.
Source files of upsrrd daemon and sample init.d script are attached. Differences from upslog are minimal: data is recorded by rrd_update using fixed log format, log format can't be changed from command line, so option -f and most parts of parser are removed, invalid values are printed as 'U', and there is no need for log rotation. Errors while updating database will be written to syslog. I intentionally left the rest of parser for possible modifications of list variables in the future. To complile daemon sources should be placed into the clients subdirectory of nut package and appropriate Makefile rules should be created (based on upslog rules). Obviously, upsrrd should be linked with -lrrd and requres librrd2-dev for building and librrd2 for execution. Sample init.d script has hardcoded daemon command line, which should be placed into config file. Database could be created with following command (I also use rrdcollect and place ups logs there): rrdtool create /var/lib/rrdcollect/ups.rrd -s 30 \ DS:bat_charge:GAUGE:75:0:100 DS:line_voltage:GAUGE:75:0:U \ DS:ups_load:GAUGE:75:0:100 DS:ups_temp:GAUGE:75:U:U \ DS:line_freq:GAUGE:75:0:U DS:bat_voltage:GAUGE:75:0:U \ DS:bat_runtime:GAUGE:75:0:U RRA:AVERAGE:0.5:10:210000 I think this daemon with some reasonable defaults might be appropriate for most users.
/* upsrrd - log ups values to a file for later collection and analysis Copyright (C) 1998 Russell Kroll <[EMAIL PROTECTED]> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* Basic theory of operation: * * First we go through and parse as much of the status format string as * possible. We used to do this parsing run every time, but that's a * waste of CPU since it can't change during the program's run. * * This version does the parsing pass once, and creates a linked list of * pointers to the functions that do the work and the arg they get. * * That means the main loop just has to run the linked list and call * anything it finds in there. Everything happens from there, and we * don't have to pointlessly reparse the string every time around. */ #include <rrd.h> #include "common.h" #include "upsclient.h" #include "config.h" #include "timehead.h" #include "upsrrd.h" static int port, exit_flag = 0; static char *upsname, *hostname; static UPSCONN ups; static const char *logfn, *monhost; static sigset_t nut_upsrrd_sigmask; static char logbuffer[LARGEBUF]; static struct flist_t *fhead = NULL; #define DEFAULT_LOGFORMAT "N:%VAR battery.charge%:" \ "%VAR input.voltage%:%VAR ups.load%:" \ "%VAR ups.temperature%:%VAR input.frequency%:" \ "%VAR battery.voltage%:%VAR battery.runtime%" const char *logformat = DEFAULT_LOGFORMAT; static void set_exit_flag(int sig) { exit_flag = sig; } /* handlers: reload on HUP, exit on INT/QUIT/TERM */ static void setup_signals(void) { struct sigaction sa; sigemptyset(&nut_upsrrd_sigmask); sigaddset(&nut_upsrrd_sigmask, SIGHUP); sa.sa_mask = nut_upsrrd_sigmask; sa.sa_flags = 0; sa.sa_handler = set_exit_flag; if (sigaction(SIGINT, &sa, NULL) < 0) fatal("Can't install SIGINT handler"); if (sigaction(SIGQUIT, &sa, NULL) < 0) fatal("Can't install SIGQUIT handler"); if (sigaction(SIGTERM, &sa, NULL) < 0) fatal("Can't install SIGTERM handler"); } static void help(const char *prog) { printf("UPS status logger.\n"); printf("\nusage: %s [OPTIONS]\n", prog); printf("\n"); printf(" -i <interval> - Time between updates, in seconds\n"); printf(" -l <rrdfile> - RRD file name\n"); printf(" -s <ups> - Monitor UPS <ups> - <upsname>@<host>[:<port>]\n"); printf(" - Example: -s [EMAIL PROTECTED]"); printf(" -u <user> - Switch to <user> if started as root\n"); printf("\n"); printf("See the upsrrd(8) man page for more information.\n"); exit(EXIT_SUCCESS); } static void getvar(const char *var) { int ret; unsigned int numq, numa; const char *query[4]; char **answer; query[0] = "VAR"; query[1] = upsname; query[2] = var; numq = 3; ret = upscli_get(&ups, numq, query, &numa, &answer); if ((ret < 0) || (numa < numq)) { snprintfcat(logbuffer, sizeof(logbuffer), "U"); return; } snprintfcat(logbuffer, sizeof(logbuffer), "%s", answer[3]); } static void do_var(const char *arg) { if ((!arg) || (strlen(arg) < 1)) { snprintfcat(logbuffer, sizeof(logbuffer), "U"); return; } /* old variable names are no longer supported */ if (!strchr(arg, '.')) { snprintfcat(logbuffer, sizeof(logbuffer), "U"); return; } /* a UPS name is now required */ if (!upsname) { snprintfcat(logbuffer, sizeof(logbuffer), "U"); return; } getvar(arg); } static void print_literal(const char *arg) { snprintfcat(logbuffer, sizeof(logbuffer), "%s", arg); } /* register another parsing function to be called later */ static void add_call(void (*fptr)(const char *arg), const char *arg) { struct flist_t *tmp, *last; tmp = last = fhead; while (tmp) { last = tmp; tmp = tmp->next; } tmp = xmalloc(sizeof(struct flist_t)); tmp->fptr = fptr; if (arg) tmp->arg = xstrdup(arg); else tmp->arg = NULL; tmp->next = NULL; if (last) last->next = tmp; else fhead = tmp; } /* turn the format string into a list of function calls with args */ static void compile_format(void) { unsigned int i; int j, found, ofs; char *cmd, *arg, *ptr; for (i = 0; i < strlen(logformat); i++) { /* if not a % sequence, append character and start over */ if (logformat[i] != '%') { char buf[4]; /* we have to stuff it into a string first */ snprintf(buf, sizeof(buf), "%c", logformat[i]); add_call(print_literal, buf); continue; } /* if a %%, append % and start over */ if (logformat[i+1] == '%') { add_call(print_literal, "%"); /* make sure we don't parse the second % next time */ i++; continue; } /* it must start with a % now - %<cmd>[ <arg>]%*/ cmd = xstrdup(&logformat[i+1]); ptr = strchr(cmd, '%'); /* no trailing % = broken */ if (!ptr) { add_call(print_literal, "U"); free(cmd); continue; } *ptr = '\0'; /* remember length (plus first %) so we can skip over it */ ofs = strlen(cmd) + 1; /* jump out to argument (if any) */ arg = strchr(cmd, ' '); if (arg) *arg++ = '\0'; found = 0; /* see if we know how to handle this command */ for (j = 0; logcmds[j].name != NULL; j++) { if (strncasecmp(cmd, logcmds[j].name, strlen(logcmds[j].name)) == 0) { add_call(logcmds[j].func, arg); found = 1; break; } } free(cmd); if (!found) add_call(print_literal, "U"); /* now do the skip ahead saved from before */ i += ofs; } /* for (i = 0; i < strlen(logformat); i++) */ } /* go through the list of functions and call them in order */ static void run_flist(void) { struct flist_t *tmp; tmp = fhead; memset(logbuffer, 0, sizeof(logbuffer)); while (tmp) { tmp->fptr(tmp->arg); tmp = tmp->next; } { char *argv[] ={ "update", logfn, logbuffer, NULL }; rrd_update(3, argv); if (rrd_test_error()) { upslogx(LOG_INFO, "rrd_update: %s", rrd_get_error()); rrd_clear_error(); } } } /* -s <monhost> * -l <log file> * -i <interval> * -f <format> * -u <username> */ int main(int argc, char **argv) { int interval = 30, i; char *prog = NULL; const char *user = NULL; struct passwd *new_uid = NULL; user = RUN_AS_USER; printf("Network UPS Tools upsrrd %s\n", UPS_VERSION); prog = argv[0]; while ((i = getopt(argc, argv, "+hs:l:i:u:V")) != EOF) { switch(i) { case 'h': help(prog); break; case 's': monhost = optarg; break; case 'l': logfn = optarg; break; case 'i': interval = atoi(optarg); break; case 'u': user = optarg; break; case 'V': exit(EXIT_SUCCESS); } } argc -= optind; argv += optind; /* not enough args for the old way? */ if ((argc == 1) || (argc == 2)) help(prog); /* see if it's being called in the old style - 3 or 4 args */ /* <system> <logfn> <interval> [<format>] */ if (argc >= 3) { monhost = argv[0]; logfn = argv[1]; interval = atoi(argv[2]); } if (!monhost) fatalx("No UPS defined for monitoring - use -s <system>"); if (!logfn) fatalx("No filename defined for logging - use -l <file>"); printf("logging status of %s to %s (%is intervals)\n", monhost, logfn, interval); if (upscli_splitname(monhost, &upsname, &hostname, &port) != 0) fatalx("Fatal error: unusable UPS definition"); if (upscli_connect(&ups, hostname, port, UPSCLI_CONN_TRYSSL) < 0) fprintf(stderr, "Warning: initial connect failed: %s\n", upscli_strerror(&ups)); /* now drop root if we have it */ if ((new_uid = get_user_pwent(user)) == NULL) fatal("getpwnam(%s)", user); openlog("upsrrd", LOG_PID, LOG_FACILITY); background(); setup_signals(); writepid("upsrrd"); become_user(new_uid); compile_format(); while (exit_flag == 0) { /* reconnect if necessary */ if (upscli_fd(&ups) == -1) { upscli_disconnect(&ups); upscli_connect(&ups, hostname, port, 0); } run_flist(); sleep(interval); } upslogx(LOG_INFO, "Signal %d: exiting", exit_flag); upscli_disconnect(&ups); exit(EXIT_SUCCESS); }
/* upsrrd.h - table of functions for handling various logging functions */ /* function list */ struct flist_t { void (*fptr)(const char *arg); const char *arg; struct flist_t *next; }; static void do_var(const char *arg); struct { const char *name; void (*func)(const char *arg); } logcmds[] = { { "VAR", do_var }, { NULL, (void(*)())(NULL) } };
#! /bin/sh # # skeleton example file to build /etc/init.d/ scripts. # This file should be used to construct scripts for /etc/init.d. # # Written by Miquel van Smoorenburg <[EMAIL PROTECTED]>. # Modified for Debian GNU/Linux # by Ian Murdock <[EMAIL PROTECTED]>. # # Version: @(#)skeleton 1.8 03-Mar-1998 [EMAIL PROTECTED] # # This file was automatically customized by dh-make on Tue, 3 Sep 2002 14:53:55 +0200 PATH=/sbin:/bin:/usr/sbin:/usr/bin DAEMON=/usr/local/sbin/upsrrd NAME=upsrrd DESC=upsrrd if [ -f /etc/default/upsrrd ]; then . /etc/default/upsrrd fi test -f $DAEMON || exit 0 set -e case "$1" in start) echo -n "Starting $DESC: $NAME" start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \ --exec $DAEMON -- -s [EMAIL PROTECTED] -l /var/lib/rrdcollect/ups.rrd -u nobody -i 60 echo "." ;; stop) echo -n "Stopping $DESC: $NAME" start-stop-daemon --oknodo --stop --quiet --pidfile /var/run/$NAME.pid \ --exec $DAEMON echo "." ;; restart|force-reload) # # If the "reload" option is implemented, move the "force-reload" # option to the "reload" entry above. If not, "force-reload" is # just the same as "restart". # echo -n "Restarting $DESC: $NAME" start-stop-daemon --stop --quiet --pidfile --make-pidfile \ /var/run/$NAME.pid --exec $DAEMON sleep 1 start-stop-daemon --start --quiet --pidfile \ /var/run/$NAME.pid --exec $DAEMON echo "." ;; *) N=/etc/init.d/$NAME # echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2 echo "Usage: $N {start|stop|restart|force-reload}" >&2 exit 1 ;; esac exit 0