Files
sics/logreader.c
zolliker 61341b52f4 various improvements
- 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
2021-09-16 12:26:18 +02:00

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);
}