Skip to content

Commit

Permalink
upsmon code, docs, conf: introduce "OTHER"/"NOTOTHER" notifications f…
Browse files Browse the repository at this point in the history
…or unknown tokens in `ups.status` [networkupstools#415]

Signed-off-by: Jim Klimov <[email protected]>
  • Loading branch information
jimklimov committed Dec 3, 2024
1 parent c7c0a83 commit 6bf775c
Show file tree
Hide file tree
Showing 13 changed files with 143 additions and 6 deletions.
3 changes: 3 additions & 0 deletions NEWS.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,9 @@ https://github.com/networkupstools/nut/milestone/11
* revised internal `do_notify()` method to support formatting strings
with two `%s` placeholders, to use if certain use-cases pass any extra
information (e.g. not just "we have alarms" but their values too). [#415]
* introduced handling for "unknown" `ups.status` tokens, reporting them
as "OTHER" notification type (whenever the set of such tokens appears
or changes) or "NOTOTHER" when they disappear. [#415]
- More systemd integration:
* Introduced a `nut-sleep.service` unit which stops `nut.target` when a
Expand Down
94 changes: 90 additions & 4 deletions clients/upsmon.c
Original file line number Diff line number Diff line change
Expand Up @@ -1728,6 +1728,7 @@ static void addups(int reloading, const char *sys, const char *pvs,

tmp->pw = xstrdup(pw);
tmp->status = 0;
tmp->status_tokens = NULL;
tmp->retain = 1;

/* ignore initial COMMOK and ONLINE by default */
Expand Down Expand Up @@ -2465,8 +2466,9 @@ static int try_connect(utype_t *ups)
/* deal with the contents of STATUS or ups.status for this ups */
static void parse_status(utype_t *ups, char *status)
{
char *statword, *ptr;
int handled_stat_words = 0;
char *statword, *ptr, other_stat_words[SMALLBUF];
int handled_stat_words = 0, changed_other_stat_words = 0;
st_tree_timespec_t st_start;

clear_alarm();

Expand Down Expand Up @@ -2499,6 +2501,9 @@ static void parse_status(utype_t *ups, char *status)
ups_is_notalarm(ups);

statword = status;
/* Track what status tokens we no longer see */
state_get_timestamp(&st_start);
other_stat_words[0] = '\0';

/* split up the status words and parse each one separately */
while (statword != NULL) {
Expand Down Expand Up @@ -2562,21 +2567,102 @@ static void parse_status(utype_t *ups, char *status)
|| !strcasecmp(statword, "TRIM")
|| !strcasecmp(statword, "BOOST")
) {
/* FIXME: Do we want these logged similar to OTHERs? */
upsdebugx(4, "Known and ignored status token: [%s]", statword);
handled++;
}

if (!handled) {
/* NOTE: Could just state_getinfo() but then
* to refresh the timestamp we'd need to walk
* it again. So replicating a bit of that code. */
st_tree_t *sttmp = (ups->status_tokens ? state_tree_find(ups->status_tokens, statword) : NULL);

/* Driver reported something non-standard? */
upsdebugx(1, "WARNING: Unexpected status token: [%s]", statword);
/* FIXME: Define a notification type like "OTHER" to report the un-handled status */
upsdebugx(4, "Unexpected status token: [%s]", statword);

snprintfcat(other_stat_words,
sizeof(other_stat_words), "%s%s",
*other_stat_words ? " " : "",
statword);

/* Part of our job is to update the timestamp,
* so we can report which tokens disappeared
* and eject them from the list.
*
* The recorded value currently does not matter.
*
* FIXME: Use the value as e.g. "0"/"1" and
* maybe the state->aux as counter, to track
* and report that the non-standard token
* was seen more than once?
*/
if (sttmp) {
/* Start from the discovered node to shortcut to
* (static) st_tree_node_refresh_timestamp(sttmp)
* and complete the info-setting quickly.
*/
state_setinfo(&sttmp, statword, "1");
} else {
/* This token was not seen last time => new state
* Handle with a notification type "OTHER"
* to report the new un-handled status. */
changed_other_stat_words++;
upsdebugx(5, "Unexpected status token: [%s]: appeared", statword);
state_setinfo(&(ups->status_tokens), statword, "1");
}
}

update_crittimer(ups);

statword = ptr;
}

if (ups->status_tokens) {
st_tree_t *node = ups->status_tokens, *sttmp = node;

/* Scroll to the leftmost entry for alphanumeric sorted processing */
while (sttmp) {
node = sttmp;
sttmp = sttmp->left;
}

/* Go from left to right, on a freeing spree if need be */
while (node) {
sttmp = node->right;
if (st_tree_node_compare_timestamp(node, &st_start) < 0) {
upsdebugx(5, "Unexpected status token: [%s]: disappeared",
NUT_STRARG(node->var));
changed_other_stat_words++;

/* whatever is on the left, hang it off current right */
if (node->right) {
node->right->left = node->left; /* May be NULL*/
}
/* whatever is on the right, hang it off current left */
if (node->left) {
node->left->right = node->right; /* May be NULL*/
}
/* forget the neighbors before dropping the "tree" */
node->right = NULL;
node->left = NULL;

state_infofree(node);
}
node = sttmp;
}
}

if (changed_other_stat_words) {
if (*other_stat_words) {
do_notify(ups, NOTIFY_OTHER, other_stat_words);
} else {
do_notify(ups, NOTIFY_NOTOTHER, NULL);
state_infofree(ups->status_tokens);
ups->status_tokens = NULL;
}
}

upsdebugx(3, "Handled %d status tokens", handled_stat_words);
}

Expand Down
14 changes: 14 additions & 0 deletions clients/upsmon.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#ifndef NUT_UPSMON_H_SEEN
#define NUT_UPSMON_H_SEEN 1

#include "state.h"

/* flags for ups->status */

#define ST_ONLINE (1 << 0) /* UPS is on line (OL) */
Expand All @@ -39,6 +41,7 @@
#define ST_BYPASS (1 << 9) /* UPS is on bypass so not protecting */
#define ST_ECO (1 << 10) /* UPS is in ECO (High Efficiency) mode or similar tweak, e.g. Energy Saver System mode */
#define ST_ALARM (1 << 11) /* UPS has at least one active alarm */
#define ST_OTHER (1 << 12) /* UPS has at least one unclassified status token */

/* required contents of flag file */
#define SDMAGIC "upsmon-shutdown-file"
Expand All @@ -63,6 +66,7 @@ typedef struct {
char *un; /* username (optional for now) */
char *pw; /* password from conf */
int status; /* status (see flags above) */
st_tree_t *status_tokens; /* parsed ups.status, mapping each token to whatever value if it is currently set (evicted when not) */
int retain; /* tracks deletions at reload */

/* handle suppression of COMMOK and ONLINE at startup */
Expand Down Expand Up @@ -117,6 +121,10 @@ typedef struct {
#define NOTIFY_ALARM 18 /* UPS has at least one active alarm */
#define NOTIFY_NOTALARM 19 /* UPS has no active alarms */

/* Special handling below */
#define NOTIFY_OTHER 28 /* UPS has at least one unclassified status token */
#define NOTIFY_NOTOTHER 29 /* UPS has no unclassified status tokens anymore */

#define NOTIFY_SUSPEND_STARTING 30 /* OS is entering sleep/suspend/hibernate slumber mode, and we know it */
#define NOTIFY_SUSPEND_FINISHED 31 /* OS just finished sleep/suspend/hibernate slumber mode, and we know it */

Expand Down Expand Up @@ -175,6 +183,12 @@ static struct {
{ NOTIFY_ALARM, "ALARM", NULL, "UPS %s: one or more active alarms (check ups.alarm)", NOTIFY_DEFAULT },
{ NOTIFY_NOTALARM, "NOTALARM", NULL, "UPS %s is no longer in an alarm state (no active alarms)", NOTIFY_DEFAULT },

/* Special handling, two string placeholders!
* Reported when status_tokens tree changes (and is not empty in the end) */
{ NOTIFY_OTHER, "OTHER", NULL, "UPS %s: has at least one unclassified status token: [%s]", NOTIFY_DEFAULT },
/* Reported when status_tokens tree becomes empty */
{ NOTIFY_NOTOTHER, "NOTOTHER", NULL, "UPS %s has no unclassified status tokens anymore", NOTIFY_DEFAULT },

{ NOTIFY_SUSPEND_STARTING, "SUSPEND_STARTING", NULL, "OS is entering sleep/suspend/hibernate mode", NOTIFY_DEFAULT },
{ NOTIFY_SUSPEND_FINISHED, "SUSPEND_FINISHED", NULL, "OS just finished sleep/suspend/hibernate mode, de-activating obsolete UPS readings to avoid an unfortunate shutdown", NOTIFY_DEFAULT },

Expand Down
4 changes: 4 additions & 0 deletions common/nutconf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1324,6 +1324,10 @@ UpsmonConfiguration::NotifyType UpsmonConfiguration::NotifyTypeFromString(const
return NOTIFY_ALARM;
else if(str=="NOTALARM")
return NOTIFY_NOTALARM;
else if(str=="OTHER")
return NOTIFY_OTHER;
else if(str=="NOTOTHER")
return NOTIFY_NOTOTHER;
else if(str=="SUSPEND_STARTING")
return NOTIFY_SUSPEND_STARTING;
else if(str=="SUSPEND_FINISHED")
Expand Down
2 changes: 2 additions & 0 deletions common/nutwriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,8 @@ const NotifyFlagsStrings::TypeStrings NotifyFlagsStrings::type_str = {
"NOTECO", // NOTIFY_NOTECO
"ALARM", // NOTIFY_ALARM
"NOTALARM", // NOTIFY_NOTALARM
"OTHER", // NOTIFY_OTHER
"NOTOTHER", // NOTIFY_NOTOTHER
"SUSPEND_STARTING", // NOTIFY_SUSPEND_STARTING
"SUSPEND_FINISHED", // NOTIFY_SUSPEND_FINISHED
};
Expand Down
8 changes: 8 additions & 0 deletions conf/upsmon.conf.sample.in
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,14 @@ POWERDOWNFLAG "@POWERDOWNFLAG@"
# NOTIFYMSG ALARM "UPS %s: one or more active alarms (check ups.alarm)"
# NOTIFYMSG NOTALARM "UPS %s is no longer in an alarm state (no active alarms)"
#
# Special handling is provided for surprise tokens seen in ups.status, which
# are not in the standard NUT dictionary (but some drivers are known to use);
# note that unlike other formatting strings, the "OTHER" one has two string
# placeholders "%s" (it is safe to use one, leaving just the UPS name, or none):
#
# NOTIFYMSG OTHER "UPS %s: has at least one unclassified status token: [%s]"
# NOTIFYMSG NOTOTHER "UPS %s has no unclassified status tokens anymore"
#
# A few messages not directly related to UPS events are also available:
#
# NOTIFYMSG SUSPEND_STARTING "OS is entering sleep/suspend/hibernate mode"
Expand Down
2 changes: 2 additions & 0 deletions docs/man/nutconf.txt
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ Notification types are:
- 'NOTBYPASS' (no longer on bypass)
- 'ALARM' (UPS is in an alarm state (has active alarms))
- 'NOTALARM' (UPS is no longer in an alarm state (no active alarms))
- 'OTHER' (UPS has at least one unclassified status token)
- 'NOTOTHER' (UPS has no unclassified status tokens anymore)
- 'SUSPEND_STARTING' (OS is entering sleep/suspend/hibernate mode)
- 'SUSPEND_FINISHED' (OS just finished sleep/suspend/hibernate mode)

Expand Down
6 changes: 6 additions & 0 deletions docs/man/upsmon.conf.txt
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,12 @@ ALARM;; UPS has one or more active alarms (check ups.alarm)

NOTALARM;; UPS is no longer in an alarm state (no active alarms)

OTHER;; UPS has at least one unclassified `ups.status` token;
for this notification, the `message` can contain a second `%s` placeholder
to substitute the current collection of such tokens.

NOTOTHER;; UPS has no unclassified status tokens anymore

SUSPEND_STARTING;; OS is entering sleep/suspend/hibernate mode

SUSPEND_FINISHED;; OS just finished sleep/suspend/hibernate mode,
Expand Down
6 changes: 6 additions & 0 deletions docs/man/upsmon.txt
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,12 @@ UPS has one or more active alarms (look at `ups.alarm` for details).
*NOTALARM*::
UPS is no longer in an alarm state (no active alarms).

*OTHER*::
UPS has at least one unclassified status token.

*NOTOTHER*::
UPS has no unclassified status tokens anymore.

*SUSPEND_STARTING*::
OS is entering sleep/suspend/hibernate mode.

Expand Down
3 changes: 2 additions & 1 deletion docs/nut.dict
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
personal_ws-1.1 en 3246 utf-8
personal_ws-1.1 en 3247 utf-8
AAC
AAS
ABI
Expand Down Expand Up @@ -752,6 +752,7 @@ NOTIFYFLAG
NOTIFYFLAGS
NOTIFYMSG
NOTOFF
NOTOTHER
NQA
NTP
NUT's
Expand Down
3 changes: 3 additions & 0 deletions include/nutconf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1475,6 +1475,9 @@ class UpsmonConfiguration : public Serialisable
NOTIFY_ALARM,
NOTIFY_NOTALARM,

NOTIFY_OTHER = 28,
NOTIFY_NOTOTHER,

NOTIFY_SUSPEND_STARTING = 30,
NOTIFY_SUSPEND_FINISHED,

Expand Down
2 changes: 2 additions & 0 deletions scripts/augeas/nutupsmonconf.aug.in
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ let upsmon_notify_type = "ONLINE"
| "NOTECO"
| "ALARM"
| "NOTALARM"
| "OTHER"
| "NOTOTHER"
| "SUSPEND_STARTING"
| "SUSPEND_FINISHED"

Expand Down
2 changes: 1 addition & 1 deletion tools/nutconf/nutconf-cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ const char * Usage::s_text[] = {
"Notification types:",
" ONLINE, ONBATT, LOWBATT, FSD, COMMOK, COMMBAD, SHUTDOWN, REPLBATT, NOCOMM, NOPARENT,",
" CAL, NOTCAL, OFF, NOTOFF, BYPASS, NOTBYPASS, ECO, NOTECO, ALARM, NOTALARM,",
" SUSPEND_STARTING, SUSPEND_FINISHED",
" OTHER, NOTOTHER, SUSPEND_STARTING, SUSPEND_FINISHED",
"Notification flags:",
" SYSLOG, WALL, EXEC, IGNORE",
"User specification:",
Expand Down

0 comments on commit 6bf775c

Please sign in to comment.