RFC: enhanced watchdog.

Alfred Perlstein bright at mu.org
Sat Jan 19 06:29:17 UTC 2013


We at iX are trying to enhance the watchdog and we think some of the 
changes may benefit the community as a whole.

Basically we want to make it easy for developers to prototype watchdog 
scripts in a "test-only" mode that basically logs if the watchdog had 
failed.

I have most of the code done, but could really use help on two things:

1) review
2) suggestion for inserting the warning messages from the userland 
watchdogd into the kernel message buffer.
3) suggestion for logging/warning of pending death.

In detail:
1) The reason for review should be obvious, we want to make sure that 
this works for everyone.
2) The reason for inserting messages into the kernel log is because that 
is the easiest place for us to recover the diagnostics when we do have a 
crash due to watchdog.  Maybe there is a smarter thing to do?
3) What is a good way to warn of impeding death?  I was thinking of just 
another thread in the process that would be signalled before the 
watchdog script was run and would log when the timer is about to expire 
or based on a configurable threshold.


Finally, there is some thought about adding a kernel daemon to the 
watchdog facility that would allow us to strobe watchdogs with low max 
values while our userland watchdog was polling the system.

Why??? Well because the ICH driver has a max timeout of ~2 minutes.  We 
really want to be able to leverage this watchdog, but also go higher 
than this.  The way to do this is to drive the system almost like a step 
up electrical relay.

The way this works is that you have a thread who's logic is as follows:

short_wd_strober(){
   for ( ;; ) {
    /* wait to be signalled or a timeout. */
    error = msleep(&short_wd, &short_mtx,  0, "shrtwd", hz * 2);
    /* we got signalled, reset the last long_strobe so we continue to 
"relay" the strobe */
    if (error == 0) {
       long_strobe = currsec;
    }
    /* if the time since our last signal is less than
long_strobe_interval then strobe the watchdog
       otherwise just loop.. hopefully we get a signal soon
     */
    if (currsec - long_strobe < long_strobe_interval){
       strobe_watchdogs();
    } else {
       if (currsec - long_strobe > long_strobe_warning)
         log("...something about not seeing a long strobe in a while");
   }
}

Another ioctl can be used to poke this thread and effectively signal the 
&short_wd wait channel.  (note the primitives will be fixed here, I 
_know_ we need flags or perhaps a cv would be better).

-Alfred

-------- Original Message --------
Subject: 	svn commit: r245658 - user/alfred/ewatchdog/usr.sbin/watchdogd
Date: 	Sat, 19 Jan 2013 06:04:27 +0000 (UTC)
From: 	Alfred Perlstein <alfred at FreeBSD.org>
To: 	src-committers at freebsd.org, svn-src-user at freebsd.org



Author: alfred
Date: Sat Jan 19 06:04:26 2013
New Revision: 245658
URL: http://svnweb.freebsd.org/changeset/base/245658

Log:
   Add code to watchdog to time the watchdog command program, carp when the program takes too long.
   
   The purpose of this is to allow system integrators to tune their watchdogs and
   get advanced notice if they are behaving poorly.
   
   The following facilities are added:
      - Warn if the watchdog program takes too long.
      - Disable activation of the system watchdog so that one can test the watchdogd script
        without potentially rebooting the system.
      - Ability to log to syslog when scripts begin to timeout.
   
   The following changes are included:
      - When told to measure time, do not unconditionally nap for 'sleep' seconds, instead adjust
        the naptime by the elapsed time so as not to trigger the watchdog.
   
   Example:
   
   /usr/trees/head/usr.sbin/watchdogd # ./watchdogd -d -n -w -e "sleep 1"
   watchdogd: mlockall failed: Cannot allocate memory
   watchdogd: Watchdog program: 'sleep 1' took too long: 1.010894 seconds >= 1 seconds threshhold
   watchdogd: Watchdog program: 'sleep 1' took too long: 1.010636 seconds >= 1 seconds threshhold
   watchdogd: Watchdog program: 'sleep 1' took too long: 1.010700 seconds >= 1 seconds threshhold
   ^C
   
   /usr/trees/head/usr.sbin/watchdogd # ./watchdogd -d -n -w -e "sleep 0.9"
   watchdogd: mlockall failed: Cannot allocate memory
   
   ... doesn't complain ...

Modified:
   user/alfred/ewatchdog/usr.sbin/watchdogd/watchdogd.8
   user/alfred/ewatchdog/usr.sbin/watchdogd/watchdogd.c

Modified: user/alfred/ewatchdog/usr.sbin/watchdogd/watchdogd.8
==============================================================================
--- user/alfred/ewatchdog/usr.sbin/watchdogd/watchdogd.8	Sat Jan 19 05:55:18 2013	(r245657)
+++ user/alfred/ewatchdog/usr.sbin/watchdogd/watchdogd.8	Sat Jan 19 06:04:26 2013	(r245658)
@@ -33,11 +33,12 @@
  .Nd watchdog daemon
  .Sh SYNOPSIS
  .Nm
-.Op Fl d
+.Op Fl dnw
  .Op Fl e Ar cmd
  .Op Fl I Ar file
  .Op Fl s Ar sleep
  .Op Fl t Ar timeout
+.Op Fl T Ar script_timeout
  .Sh DESCRIPTION
  The
  .Nm
@@ -62,6 +63,13 @@ is not specified, the daemon will perfor
  check instead.
  .Pp
  The
+.Fl n
+argument 'dry-run' will cause watchdog not to arm the system watchdog and
+instead only run the watchdog function and report on failures.
+This is useful for developing new watchdogd scripts as the system will not
+reboot if there are problems with the script.
+.Pp
+The
  .Fl s Ar sleep
  argument can be used to control the sleep period between each execution
  of the check and defaults to one second.
@@ -78,6 +86,16 @@ If this occurs,
  will no longer execute and thus the kernel's watchdog routines will take
  action after a configurable timeout.
  .Pp
+The
+.Fl T Ar script_timeout
+specifies the threshold (in seconds) at which the watchdogd will complain
+that its script has run for too long.
+If unset
+.Ar script_timeout
+defaults to the value specified by the
+.Fl s Ar sleep
+option.
+.Pp
  Upon receiving the
  .Dv SIGTERM
  or
@@ -100,6 +118,11 @@ Do not fork.
  When this option is specified,
  .Nm
  will not fork into the background at startup.
+.Pp
+.It Fl w
+Complain when the watchdog script takes too long.
+This flag will cause watchdogd to complain when the amount of time to
+execute the watchdog script exceeds the threshold of 'sleep' option.
  .El
  .Sh FILES
  .Bl -tag -width ".Pa /var/run/watchdogd.pid" -compact

Modified: user/alfred/ewatchdog/usr.sbin/watchdogd/watchdogd.c
==============================================================================
--- user/alfred/ewatchdog/usr.sbin/watchdogd/watchdogd.c	Sat Jan 19 05:55:18 2013	(r245657)
+++ user/alfred/ewatchdog/usr.sbin/watchdogd/watchdogd.c	Sat Jan 19 06:04:26 2013	(r245658)
@@ -50,6 +50,7 @@ __FBSDID("$FreeBSD$");
  #include <string.h>
  #include <strings.h>
  #include <sysexits.h>
+#include <syslog.h>
  #include <unistd.h>
  
  static void	parseargs(int, char *[]);
@@ -66,8 +67,14 @@ static const char *pidfile = _PATH_VARRU
  static u_int timeout = WD_TO_16SEC;
  static u_int passive = 0;
  static int is_daemon = 0;
+static int is_dry_run = 0;  /* do not arm the watchdog, only
+			       report on timing of the watch
+			       program */
+static int do_timedog = 0;
+static int do_syslog = 0;
  static int fd = -1;
  static int nap = 1;
+static int carp_thresh_seconds = -1;
  static char *test_cmd = NULL;
  
  /*
@@ -85,12 +92,18 @@ main(int argc, char *argv[])
  		
  	parseargs(argc, argv);
  
+	if (do_syslog) {
+		openlog("watchdogd", LOG_CONS|LOG_NDELAY|LOG_PERROR,
+		    LOG_DAEMON);
+
+	}
+
  	rtp.type = RTP_PRIO_REALTIME;
  	rtp.prio = 0;
  	if (rtprio(RTP_SET, 0, &rtp) == -1)
  		err(EX_OSERR, "rtprio");
  
-	if (watchdog_init() == -1)
+	if (!is_dry_run && watchdog_init() == -1)
  		errx(EX_SOFTWARE, "unable to initialize watchdog");
  
  	if (is_daemon) {
@@ -156,6 +169,9 @@ static int
  watchdog_init(void)
  {
  
+	if (is_dry_run)
+		return 0;
+
  	fd = open("/dev/" _PATH_WATCHDOG, O_RDWR);
  	if (fd >= 0)
  		return (0);
@@ -164,26 +180,98 @@ watchdog_init(void)
  }
  
  /*
+ * If we are doing timing, then get the time.
+ */
+static int
+watchdog_getuptime(struct timespec *tp)
+{
+	int error;
+
+	if (!do_timedog)
+		return 0;
+
+	error = clock_gettime(CLOCK_UPTIME_FAST, tp);
+	if (error)
+		warn("clock_gettime");
+	return (error);
+}
+
+static long
+watchdog_check_dogfunction_time(struct timespec *tp_start,
+    struct timespec *tp_end)
+{
+	struct timeval tv_start, tv_end, tv;
+	const char *cmd_prefix, *cmd;
+	int sec;
+
+	if (!do_timedog)
+		return (0);
+
+	TIMESPEC_TO_TIMEVAL(&tv_start, tp_start);
+	TIMESPEC_TO_TIMEVAL(&tv_end, tp_end);
+	timersub(&tv_end, &tv_start, &tv);
+	sec = tv.tv_sec;
+	if (sec < carp_thresh_seconds)
+		return (sec);
+
+	if (test_cmd) {
+		cmd_prefix = "Watchdog program";
+		cmd = test_cmd;
+	} else {
+		cmd_prefix = "Watchdog operation";
+		cmd = "stat(\"/etc\", &sb)";
+	}
+	if (do_syslog)
+		syslog(LOG_CRIT, "%s: '%s' took too long: "
+		    "%d.%06ld seconds >= %d seconds threshhold",
+		    cmd_prefix, cmd, sec, (long)tv.tv_usec,
+		    carp_thresh_seconds);
+	warnx("%s: '%s' took too long: "
+	    "%d.%06ld seconds >= %d seconds threshhold",
+	    cmd_prefix, cmd, sec, (long)tv.tv_usec, carp_thresh_seconds);
+	return (sec);
+}
+
+
+/*
   * Main program loop which is iterated every second.
   */
  static void
  watchdog_loop(void)
  {
+	struct timespec ts_start, ts_end;
  	struct stat sb;
-	int failed;
+	long waited;
+	int error, failed;
  
  	while (end_program != 2) {
  		failed = 0;
  
+		error = watchdog_getuptime(&ts_start);
+		if (error) {
+			end_program = 1;
+			goto try_end;
+		}
+
  		if (test_cmd != NULL)
  			failed = system(test_cmd);
  		else
  			failed = stat("/etc", &sb);
  
+		error = watchdog_getuptime(&ts_end);
+		if (error) {
+			end_program = 1;
+			goto try_end;
+		}
+
+		waited = watchdog_check_dogfunction_time(&ts_start, &ts_end);
+
  		if (failed == 0)
  			watchdog_patpat(timeout|WD_ACTIVE);
-		sleep(nap);
+		if (nap - waited > 0)
+			sleep(nap - waited);
  
+try_end:
  		if (end_program != 0) {
  			if (watchdog_onoff(0) == 0) {
  				end_program = 2;
@@ -203,6 +291,9 @@ static int
  watchdog_patpat(u_int t)
  {
  
+	if (is_dry_run)
+		return 0;
+
  	return ioctl(fd, WDIOCPATPAT, &t);
  }
  
@@ -214,6 +305,10 @@ static int
  watchdog_onoff(int onoff)
  {
  
+	/* fake successful watchdog op if a dry run */
+	if (is_dry_run)
+		return 0;
+
  	if (onoff)
  		return watchdog_patpat((timeout|WD_ACTIVE));
  	else
@@ -227,12 +322,26 @@ static void
  usage(void)
  {
  	if (is_daemon)
-		fprintf(stderr, "usage: watchdogd [-d] [-e cmd] [-I file] [-s sleep] [-t timeout]\n");
+		fprintf(stderr, "usage: watchdogd [-dnw] [-e cmd] [-I file] [-s sleep] [-t timeout] [-T script_timeout]\n");
  	else
  		fprintf(stderr, "usage: watchdog [-d] [-t timeout]\n");
  	exit(EX_USAGE);
  }
  
+static long
+fetchtimeout(int opt, const char *myoptarg)
+{
+	char *p;
+	long rv;
+
+	p = NULL;
+	errno = 0;
+	rv = strtol(myoptarg, &p, 0);
+	if ((p != NULL && *p != '\0') || errno != 0)
+		errx(EX_USAGE, "-%c argument is not a number", opt);
+	return (rv);
+}
+
  /*
   * Handle the few command line arguments supported.
   */
@@ -247,7 +356,7 @@ parseargs(int argc, char *argv[])
  	if (argv[0][c - 1] == 'd')
  		is_daemon = 1;
  	while ((c = getopt(argc, argv,
-	    is_daemon ? "I:de:s:t:?" : "dt:?")) != -1) {
+	    is_daemon ? "I:de:ns:t:ST:w?" : "dt:?")) != -1) {
  		switch (c) {
  		case 'I':
  			pidfile = optarg;
@@ -258,17 +367,19 @@ parseargs(int argc, char *argv[])
  		case 'e':
  			test_cmd = strdup(optarg);
  			break;
+		case 'n':
+			is_dry_run = 1;
+			break;
  #ifdef notyet
  		case 'p':
  			passive = 1;
  			break;
  #endif
  		case 's':
-			p = NULL;
-			errno = 0;
-			nap = strtol(optarg, &p, 0);
-			if ((p != NULL && *p != '\0') || errno != 0)
-				errx(EX_USAGE, "-s argument is not a number");
+			nap = fetchtimeout(c, optarg);
+			break;
+		case 'S':
+			do_syslog = 1;
  			break;
  		case 't':
  			p = NULL;
@@ -286,12 +397,22 @@ parseargs(int argc, char *argv[])
  				printf("Timeout is 2^%d nanoseconds\n",
  				    timeout);
  			break;
+		case 'T':
+			carp_thresh_seconds = fetchtimeout(c, optarg);
+			break;
+		case 'w':
+			do_timedog = 1;
+			break;
  		case '?':
  		default:
  			usage();
  			/* NOTREACHED */
  		}
  	}
+
+	if (carp_thresh_seconds == -1)
+		carp_thresh_seconds = nap;
+
  	if (argc != optind)
  		errx(EX_USAGE, "extra arguments.");
  	if (is_daemon && timeout < WD_TO_1SEC)





More information about the freebsd-arch mailing list