- use dig for resolving host names - ascon.c: fix terminator parsing - property callback: change property before callback - logger.c:default for logger period must be the old value instead of 1 - add frappy type history writing - increase max. logreader line length - HIPNONE returns "null" with json protocol - encode strings properly in formatNameValue - fix memory leak in json2tcl - scriptcontext: do not show debug messages when script starts with underscore or when the "send" property is empty - scriptcontext: remove args for action timestamp - scriptcontext: "que" function will replace an already queued action, e.g. for 'halt - introduced updatestatus script
604 lines
15 KiB
C
604 lines
15 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <fortify.h>
|
|
#include <errno.h>
|
|
#include <dirent.h>
|
|
#include <math.h>
|
|
#include "logger.h"
|
|
#include "sics.h"
|
|
|
|
#define LOGGER_NAN -999999.
|
|
#define ONE_YEAR (366*24*3600)
|
|
#define LLEN 4096
|
|
/* max. number of dirs in path */
|
|
#define MAX_DIRS 16
|
|
|
|
typedef enum { NUMERIC, TEXT } CompType;
|
|
|
|
typedef struct {
|
|
time_t t;
|
|
float y;
|
|
} Point;
|
|
|
|
typedef struct {
|
|
SConnection *pCon;
|
|
int exact;
|
|
CompType type;
|
|
time_t step;
|
|
time_t tlim; /* 0: initial state */
|
|
Point best; /* best point, to be written if tlim > 0 */
|
|
Point written; /* last written point */
|
|
Point last; /* last point */
|
|
char buf[LLEN];
|
|
int np;
|
|
char *none;
|
|
char *header;
|
|
time_t period;
|
|
int omitEqual;
|
|
} Compressor;
|
|
|
|
static char *dirs[MAX_DIRS] = { NULL };
|
|
static int ndirs = 0;
|
|
|
|
static float decodeWithPrefix(char *text, int *ok) {
|
|
static char *prefixes = "yzafpnumkMGTPEZY";
|
|
static float multiplier[] = {
|
|
1e-24, 1e-21, 1e-18, 1e-15, 1e-12, 1e-9, 1e-6, 1e-3,
|
|
1e3, 1e6, 1e9, 1e12, 1e15, 1e18, 1e21, 1e24};
|
|
char *pos, *unit;
|
|
float result;
|
|
float eps;
|
|
|
|
result = strtod(text, &unit);
|
|
if (unit == text) {
|
|
if (ok) {
|
|
*ok = 0;
|
|
}
|
|
return 0.0;
|
|
}
|
|
while (*unit == ' ') {
|
|
unit++;
|
|
}
|
|
if (unit[0] != '\0' && unit[1] > ' ') {
|
|
/* do not allow prefixes without unit (like "m" - might be meter instead of milli) */
|
|
pos = strchr(prefixes, *unit);
|
|
if (pos != NULL) {
|
|
result *= multiplier[pos-prefixes];
|
|
}
|
|
}
|
|
if (ok) {
|
|
*ok = 1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void InitCompressor(Compressor * c, SConnection * pCon, time_t step)
|
|
{
|
|
c->pCon = pCon;
|
|
c->step = step;
|
|
c->tlim = 0;
|
|
c->last.y = LOGGER_NAN;
|
|
c->last.t = 0;
|
|
c->buf[0] = '\0';
|
|
c->written.t = 0;
|
|
c->written.y = LOGGER_NAN;
|
|
c->omitEqual = 1;
|
|
}
|
|
|
|
static void OutFloat(Compressor * c, Point p)
|
|
{
|
|
char *value;
|
|
|
|
if (p.y == LOGGER_NAN) {
|
|
if (c->omitEqual && c->written.y == LOGGER_NAN)
|
|
return;
|
|
if (c->none) {
|
|
value = c->none;
|
|
} else {
|
|
value = "";
|
|
}
|
|
} else {
|
|
if (c->omitEqual && c->written.y == p.y)
|
|
return;
|
|
snprintf(c->buf, sizeof(c->buf), "%.7g", p.y);
|
|
value = c->buf;
|
|
}
|
|
SCPrintf(c->pCon, eWarning, "%ld %s", (long) (p.t - c->written.t),
|
|
value);
|
|
c->written = p;
|
|
c->np--;
|
|
}
|
|
|
|
static void WriteHeader(Compressor * c)
|
|
{
|
|
if (c->header) {
|
|
SCPrintf(c->pCon, eWarning, "*%s period %ld\n", c->header, c->period);
|
|
c->header = NULL;
|
|
}
|
|
}
|
|
|
|
static void PutValue(Compressor * c, time_t t, char *value)
|
|
{
|
|
char *p;
|
|
Point new;
|
|
char unitPrefix;
|
|
int ok;
|
|
|
|
WriteHeader(c);
|
|
if (c->type == NUMERIC) {
|
|
new.y = decodeWithPrefix(value, &ok);
|
|
if (ok) {
|
|
if (new.y == LOGGER_NAN) {
|
|
new.y *= 1.0000002;
|
|
}
|
|
} else {
|
|
new.y = LOGGER_NAN;
|
|
}
|
|
new.t = t;
|
|
if (t >= c->tlim) { /* a new interval starts */
|
|
if (c->tlim == 0) {
|
|
c->tlim = t;
|
|
} else if (c->best.y != c->written.y) {
|
|
OutFloat(c, c->best);
|
|
}
|
|
c->best = new;
|
|
c->tlim += c->step;
|
|
if (t > c->tlim) {
|
|
if (c->last.y != c->written.y) {
|
|
OutFloat(c, c->last);
|
|
}
|
|
c->tlim = t + c->step;
|
|
}
|
|
} else { /* not the first point */
|
|
if (fabs(new.y - c->best.y) > fabs(c->written.y - c->best.y)) {
|
|
c->best = new;
|
|
}
|
|
}
|
|
c->last = new;
|
|
} else if (c->type == TEXT) {
|
|
if (0 != strncmp(value, c->buf, sizeof(c->buf) - 1)) {
|
|
snprintf(c->buf, sizeof(c->buf), "%s", value);
|
|
SCPrintf(c->pCon, eWarning, "%ld %s\n", (long) (t - c->written.t),
|
|
value);
|
|
c->written.t = t;
|
|
c->np--;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void PutFinish(Compressor * c, time_t now)
|
|
{
|
|
char value[32];
|
|
|
|
if (c->tlim != 0) { /* there is data already */
|
|
c->omitEqual = 0;
|
|
if (c->type == NUMERIC) {
|
|
if (now > c->last.t + c->period) { /* copy last value to the actual time */
|
|
if (c->last.y != LOGGER_NAN) {
|
|
snprintf(value, sizeof value, "%.7g", c->last.y);
|
|
PutValue(c, now, value);
|
|
}
|
|
if (c->best.t > c->written.t) {
|
|
OutFloat(c, c->best); /* write last buffered value */
|
|
}
|
|
}
|
|
}
|
|
if (now > c->last.t) {
|
|
PutValue(c, now, c->buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
static long getOpt(char *line, int *isdst, int *exact)
|
|
{
|
|
long lxs;
|
|
char *opt;
|
|
|
|
opt = strstr(line, "isdst");
|
|
if (opt) {
|
|
sscanf(opt, "isdst %d", isdst);
|
|
}
|
|
opt = strstr(line, "exact");
|
|
if (opt) {
|
|
sscanf(opt, "exact %d", exact);
|
|
}
|
|
opt = strstr(line, "period");
|
|
if (opt) {
|
|
sscanf(opt, "period %ld", &lxs);
|
|
return lxs;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
static int LogReader(SConnection * pCon, SicsInterp * pSics, void *pData,
|
|
int argc, char *argv[])
|
|
{
|
|
/* Usage:
|
|
graph <start time> <end time> [ none <none value> | text | np <number of points> ] <variable> [<variable> ...]
|
|
|
|
<start time> and <end time> are seconds since epoch (unix time) or, if the value
|
|
is below one day, a time relative to the actual time
|
|
|
|
The <none value> is optional. if not given, unknown values are returned as empty strings
|
|
|
|
<number of points> is the maximal number of points to be returned. If more values
|
|
are present and the values are numeric, the data is reduced. If the data is not
|
|
numeric, the last values may be skipped in order to avoid overflow.
|
|
If np is not given, it is assumed to be very high.
|
|
|
|
<variable> is the name of a variable (several vaiables may be given).
|
|
Note that slashes are converted to dots, and that the first slash is ignored.
|
|
|
|
"text" means that the values are not numeric, in this case np is not needed
|
|
and by default infinitely high
|
|
|
|
|
|
Output format:
|
|
First line: returning the actual time on the server (this time is used for relative times)
|
|
|
|
For each variable which has data in the given interval,
|
|
the variable name is returned preceeded by a '*', followed by some infos* separated with
|
|
blanks.
|
|
After the header line for every data point a line follows with
|
|
a time relative to the last point and the value.
|
|
The first time value relative to zero, e.g. absolute.
|
|
The data value is valid until the next datapoint. Empty values are returned as an
|
|
empty string or as the <none value>.
|
|
|
|
At the very end *<loss> <overflow> is returned.
|
|
<loss> is 1, when the data had to be reduced, 0 else
|
|
<overflow> is 1, when overflow occured, 0 else. Overflow may happen only
|
|
when np is given and a text variable was demanded.
|
|
|
|
*actually only one info exists: period <value>. This is the update rate in seconds.
|
|
As equal values are not transmitted, two points (t1,y1) and (t2,y2) with a distance
|
|
(t2 - t1) > period should not be connected directly. The plot software should generate
|
|
an intermediate point at (t2-period,y1).
|
|
|
|
*/
|
|
time_t from, to, step, xs, lastt;
|
|
char *p, *varp;
|
|
int i, j, iarg, pathLen, iret, loss, np;
|
|
int inRange;
|
|
int yday = 0;
|
|
time_t t, startim;
|
|
struct tm tm;
|
|
char stim[32], path[LLEN], line[LLEN], lastval[LLEN];
|
|
char *lin, *val, *stp;
|
|
FILE *fil;
|
|
Compressor c = { 0 };
|
|
float yy, lasty;
|
|
CompType type0;
|
|
DIR *dr;
|
|
char *opt;
|
|
int isdst;
|
|
int overflow;
|
|
time_t now, nows[MAX_DIRS], nowi;
|
|
char var[256];
|
|
char dirPathBuffer[1024];
|
|
char *dirPath;
|
|
int idir;
|
|
char *colon;
|
|
|
|
static Statistics *stat;
|
|
Statistics *old;
|
|
|
|
/* argtolower(argc, argv); */
|
|
if (argc < 4)
|
|
goto illarg;
|
|
now = time(NULL);
|
|
from = strtol(argv[1], &p, 0); /* unix time, not year 2038 safe */
|
|
if (p == argv[1])
|
|
goto illarg;
|
|
to = strtol(argv[2], &p, 0);
|
|
if (p == argv[2])
|
|
goto illarg;
|
|
if (from < ONE_YEAR) {
|
|
from += now;
|
|
}
|
|
if (to < ONE_YEAR) {
|
|
to += now;
|
|
}
|
|
iarg = 3;
|
|
type0 = NUMERIC;
|
|
while (1) {
|
|
if (iarg >= argc)
|
|
goto illarg;
|
|
if (strcasecmp(argv[iarg], "text") == 0) { /* non-numeric values */
|
|
iarg++;
|
|
step = 1;
|
|
np = to - from + 2;
|
|
type0 = TEXT;
|
|
/* break; */
|
|
} else if (strcasecmp(argv[iarg], "none") == 0) { /* none */
|
|
iarg++;
|
|
if (iarg >= argc)
|
|
goto illarg;
|
|
c.none = argv[iarg];
|
|
iarg++;
|
|
} else if (strcasecmp(argv[iarg], "np") == 0) { /* max. number of points */
|
|
iarg++;
|
|
if (iarg >= argc)
|
|
goto illarg;
|
|
np = strtol(argv[iarg], &p, 0);
|
|
if (p == argv[iarg])
|
|
goto illarg;
|
|
iarg++;
|
|
if (to <= from) {
|
|
step = 1;
|
|
} else if (np <= 2) {
|
|
step = to - from;
|
|
} else {
|
|
step = (to - from) / (np - 2) + 1;
|
|
}
|
|
break;
|
|
} else {
|
|
np = to - from + 2;
|
|
step = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (step <= 0)
|
|
step = 1;
|
|
|
|
snprintf(line, sizeof line, "%ld\n", (long) now);
|
|
SCWrite(pCon, line, eWarning);
|
|
|
|
dirPath = IFindOption(pSICSOptions, "LogReaderPath");
|
|
if (dirPath == NULL) { /* for compatibility, check */
|
|
dirs[0] = IFindOption(pSICSOptions, "LoggerDir");
|
|
if (dirs[0] == NULL) {
|
|
SCWrite(pCon, "ERROR: LoggerPath not found", eError);
|
|
return 0;
|
|
}
|
|
nows[0] = LoggerGetLastLife(NULL);
|
|
if (nows[0] == 0) {
|
|
nows[0] = LoggerGetLastLife(dirs[0]);
|
|
}
|
|
if (dirs[1] == NULL) {
|
|
dirs[1] = IFindOption(pSICSOptions, "LoggerDir2");
|
|
nows[1] = LoggerGetLastLife(dirs[1]);
|
|
}
|
|
} else {
|
|
snprintf(dirPathBuffer, sizeof dirPathBuffer, "%s", dirPath);
|
|
dirPath = dirPathBuffer;
|
|
for (ndirs = 0; ndirs < MAX_DIRS; ndirs++) {
|
|
dirs[ndirs] = dirPath;
|
|
colon = strchr(dirPath, ':');
|
|
if (colon != NULL) {
|
|
*colon = '\0';
|
|
}
|
|
if (ndirs == 0) {
|
|
nows[0] = LoggerGetLastLife(NULL); /* get from internal variable instead of file */
|
|
if (nows[0] == 0) { // internal value not available
|
|
nows[0] = LoggerGetLastLife(dirPath);
|
|
}
|
|
} else {
|
|
nows[ndirs] = LoggerGetLastLife(dirPath);
|
|
}
|
|
if (colon == NULL) {
|
|
ndirs++;
|
|
break;
|
|
}
|
|
dirPath = colon + 1;
|
|
}
|
|
}
|
|
|
|
loss = 0;
|
|
overflow = 0;
|
|
for (i = iarg; i < argc; i++) {
|
|
if (stat ==NULL) {
|
|
stat = StatisticsNew("loggervar");
|
|
}
|
|
old = StatisticsBegin(stat);
|
|
startim = from;
|
|
t = 0;
|
|
lastt = 0;
|
|
inRange = 0;
|
|
xs = step;
|
|
InitCompressor(&c, pCon, step);
|
|
|
|
/* convert slashes to dots */
|
|
varp = argv[i];
|
|
|
|
if (*varp == '/')
|
|
varp++;
|
|
for (j = 0; j < sizeof(var) - 1 && *varp != 0; j++, varp++) {
|
|
if (*varp == '/') {
|
|
var[j] = '.';
|
|
} else {
|
|
var[j] = *varp;
|
|
}
|
|
}
|
|
c.header = argv[i];
|
|
c.period = 0;
|
|
var[j] = '\0';
|
|
c.type = type0;
|
|
c.np = np;
|
|
tm = *localtime(&from);
|
|
pathLen = 0;
|
|
for (idir = 0; idir < ndirs; idir++) {
|
|
if (dirs[idir][0] != '\0') {
|
|
pathLen = LoggerVarPath(dirs[idir], path, sizeof path, var, &tm);
|
|
dr = opendir(path);
|
|
if (dr) {
|
|
nowi = nows[idir];
|
|
closedir(dr);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
isdst = -1;
|
|
fil = NULL;
|
|
while (startim <= to && c.last.t <= to) {
|
|
tm = *localtime(&startim);
|
|
if (tm.tm_yday != yday) {
|
|
if (fil != NULL) { /* close file if day changed */
|
|
fclose(fil);
|
|
fil = NULL;
|
|
}
|
|
}
|
|
if (fil == NULL) {
|
|
yday = tm.tm_yday;
|
|
strftime(path + pathLen, sizeof path - pathLen, "%m-%d.log", &tm);
|
|
fil = fopen(path, "r");
|
|
if (fil != NULL) { /* check if file is from the given year */
|
|
strftime(stim, sizeof stim, "#%Y-%m-%d", &tm);
|
|
fgets(line, sizeof line, fil);
|
|
if (0 != strncmp(line, stim, 11)) {
|
|
fclose(fil);
|
|
fil = NULL;
|
|
} else {
|
|
c.period = getOpt(line, &isdst, &c.exact);
|
|
/* if (c.exact) c.period = 0; */
|
|
if (c.period >= 0) {
|
|
if (c.period == 0) {
|
|
c.type = TEXT;
|
|
} else {
|
|
c.type = type0;
|
|
xs = c.period;
|
|
}
|
|
if (xs < step) {
|
|
loss = 1;
|
|
xs = step;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (fil == NULL) {
|
|
lin = NULL;
|
|
} else {
|
|
do {
|
|
lin = fgets(line, sizeof line, fil);
|
|
/* printf("%s\n", line); */
|
|
if (lin == NULL)
|
|
break;
|
|
if (line[0] == '#') {
|
|
c.period = getOpt(line, &isdst, &c.exact);
|
|
if (c.period >= 0) {
|
|
if (c.period == 0) {
|
|
c.type = TEXT;
|
|
} else {
|
|
c.type = type0;
|
|
xs = c.period;
|
|
}
|
|
if (xs < step) {
|
|
loss = 1;
|
|
xs = step;
|
|
}
|
|
}
|
|
lin[0] = '\0';
|
|
} else {
|
|
p = strchr(line, '\n');
|
|
if (p)
|
|
*p = '\0';
|
|
p = strchr(line, '#');
|
|
if (p)
|
|
*p = '\0';
|
|
}
|
|
} while (lin[0] == '\0');
|
|
}
|
|
if (lin != NULL) {
|
|
/* printf(" %s\n", line); */
|
|
p = strchr(line, '\t');
|
|
if (p) {
|
|
*p = '\0';
|
|
val = p + 1;
|
|
} else {
|
|
val = "";
|
|
}
|
|
p = strchr(val, '\t');
|
|
if (p) {
|
|
stp = p + 1;
|
|
*p = '\0';
|
|
iret = sscanf(stp, "%ld", &c.period);
|
|
if (iret == 1) {
|
|
if (c.period == 0) {
|
|
c.type = TEXT;
|
|
} else {
|
|
c.type = type0;
|
|
xs = c.period;
|
|
}
|
|
if (xs < step) {
|
|
loss = 1;
|
|
xs = step;
|
|
}
|
|
}
|
|
}
|
|
iret =
|
|
sscanf(line, "%d:%d:%d", &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
|
|
if (iret != 3) {
|
|
lin = NULL;
|
|
} else {
|
|
tm.tm_isdst = isdst;
|
|
t = mktime(&tm);
|
|
if (!inRange) {
|
|
if (t < startim) {
|
|
lastval[0] = '\0';
|
|
strncat(lastval, val, sizeof lastval - 1);
|
|
lastt = t;
|
|
} else {
|
|
inRange = 1;
|
|
if (lastt != 0) {
|
|
PutValue(&c, lastt, lastval);
|
|
}
|
|
PutValue(&c, t, val);
|
|
}
|
|
} else {
|
|
PutValue(&c, t, val);
|
|
}
|
|
}
|
|
}
|
|
if (lin == NULL) {
|
|
tm.tm_hour = 24; /* try next day */
|
|
tm.tm_min = 0;
|
|
tm.tm_sec = 0;
|
|
tm.tm_isdst = -1;
|
|
startim = mktime(&tm);
|
|
continue;
|
|
}
|
|
}
|
|
if (!inRange) {
|
|
if (lastt != 0) {
|
|
PutValue(&c, lastt, lastval);
|
|
}
|
|
}
|
|
WriteHeader(&c); /* write header, if not already done */
|
|
PutFinish(&c, nowi);
|
|
if (fil) {
|
|
fclose(fil);
|
|
fil = NULL;
|
|
}
|
|
if (c.np < 0)
|
|
overflow = 1;
|
|
StatisticsEnd(old);
|
|
}
|
|
snprintf(line, sizeof line, "*%d %d\n", loss, overflow);
|
|
SCWrite(pCon, line, eWarning);
|
|
return 1;
|
|
illarg:
|
|
SCWrite(pCon, "illegal argument(s)", eError);
|
|
return 0;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
static void KillLogReader(void *data)
|
|
{
|
|
Logger *p, *next;
|
|
|
|
KillDummy(data);
|
|
LoggerFreeAll();
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
void LogReaderInit(void)
|
|
{
|
|
AddCommand(pServ->pSics, "Graph", LogReader, KillLogReader, NULL);
|
|
}
|