#include #include #include #include #include #include #include #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 [ none | text | np ] [ ...] and are seconds since epoch (unix time) or, if the value is below one day, a time relative to the actual time The is optional. if not given, unknown values are returned as empty strings 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. 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 . At the very end * is returned. is 1, when the data had to be reduced, 0 else 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 . 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); }