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

Reply via email to