#include #include #include #include #include #include #include #include #include "parser.h" #include "debug.h" #include "string.h" #include "utils.h" #include "plugin.h" #include "version.h" #include "utils.h" struct owparser_state { double prev_delta[20]; double prev_data; }; static struct owparser_state *allocate_parser_state(const char **datastr) { int i; /* * Count how many sensor entries we need. First entry belongs * to server address or mount point and last one is NULL. So * the index final is the count of actual valid sensor * entries. */ for (i = 0; datastr[i]; i++) ; return calloc(sizeof(struct owparser_state), i); } static double max_glitch_delta(const struct owparser_state *s) { double max_delta = 0; int i; for (i = 0; i < ARRAY_SIZE(s->prev_delta); i++) max_delta = max(s->prev_delta[i], max_delta); return max_delta; } static int might_be_glitch(double data, const struct owparser_state *s) { double max_delta, delta; /* The known bad data value from the sensor */ if (data == 85) return 1; max_delta = max_glitch_delta(s); /* Probably no enough data yet, so no glitch detection */ if (max_delta == 0) return 0; /* * Simple glitch detection. If delta to previous value is more * than twice as larger as any of the older delta, we might * have a glitch */ delta = fabs(data - s->prev_data); return delta > max_delta * 2; } static void update_glitch_data(double data, struct owparser_state *s) { double max_delta = 0; int i; for (i = 1; i < ARRAY_SIZE(s->prev_delta); i++) { s->prev_delta[i - 1] = s->prev_delta[i]; max_delta = max(s->prev_delta[i], max_delta); } /* Avoid storing the first incorrect delta value */ if (s->prev_data || max_delta) s->prev_delta[--i] = fabs(data - s->prev_data); s->prev_data = data; } static int parse_opts(const char *str, char *ow_path, size_t pathlen, double *offset) { char *endptr; const char *start_str = str; const char offset_str[] = "offset="; if (!offset) return 0; /* * Skip the onewire path entry. Options begin after the first * white space */ for (; *str; str++) if (isspace(*str)) break; /* Copy the onewire path without options */ strncpy(ow_path, start_str, pathlen - 1); ow_path[str - start_str] = '\0'; /* Get the next non-space, which is where the argument begins */ for (; *str; str++) if (!isspace(*str)) break; if (strncmp(str, offset_str, sizeof(offset_str) - 1)) return 0; str += sizeof(offset_str) - 1; *offset = strtod(str, &endptr); if (str != endptr) return 1; return 0; } static int make_uncached(char *path, size_t len) { int ret; char p1[1028], p2[1028], *p = path; if (strstr(path, "/uncached/")) return 0; p1[sizeof(p1) - 1] = '\0'; p2[sizeof(p2) - 1] = '\0'; /* * Naively assume the "uncached" string can be put after the * first slash */ while (*p && *p != '/') p++; if (!*p) return -1; *p = 0; p++; strncpy(p1, path, sizeof(p1) - 1); strncpy(p2, p, sizeof(p2) - 1); ret = snprintf(path, len, "%s/uncached/%s", p1, p2); /* No actual data overflow, snprintf just couldn't fit all data in the buffer */ if (ret >= RRD_DATA_MAX_LEN) pr_err("Buffer overlfow\n"); return 0; } static int owfs_read(const char *mount_point, const char *path, char **res) { char result[64]; char file[2048]; int fd, ret; snprintf(file, sizeof(file), "%s/%s", mount_point, path); fd = open(file, O_RDONLY | O_CLOEXEC); if (fd < 0) { pr_err("Failed to open file %s: %m\n", file); return -1; } ret = read(fd, result, sizeof(result)); if (ret < 0) { pr_err("Failed to read from file %s: %m\n", file); goto out_close; } *res = strndup(result, sizeof(result)); out_close: close(fd); return ret; } static void enable_simultaneous_reading(const char *mountpoint) { static time_t last_simultaneous; static struct mutex lock = { .lock = PTHREAD_MUTEX_INITIALIZER, }; time_t now = time(0); int fd; int ret; char path[4096]; char one = '1'; mutex_lock(&lock); /* Arbitrary 10 second limit between simultaneous reads */ if (now < last_simultaneous + 10) { mutex_unlock(&lock); return; } last_simultaneous = now; mutex_unlock(&lock); /* * We only protect setting the variable. From now on we have * 10 seconds time until we could race writing multiple times * to this file. If that happens, well, can't help it.. */ strncpy(path, mountpoint, sizeof(path) - 1); strncat(path, "/simultaneous/temperature", sizeof(path) - 1); path[sizeof(path) - 1 ] = '\0'; fd = open(path, O_WRONLY); if (path < 0) { pr_err("Failed to open %s for writing: %m\n", path); return; } ret = write(fd, &one, 1); if (ret < 0) pr_warn("Failed to write to %s: %m\n", path); close(fd); } static int is_mount_point(const char *str) { /* * Filesystem paths begin with a slash, everything else must * be a network addresses */ if (str[0] == '/') return 1; return 0; } static int onewire_parser(char *rrd_data, const char **parser_data, void **s) { OWNET_HANDLE h; const char *server_addr, *mount_point; struct owparser_state *state = *s; char buf[24], *tmp; int i = 1, ret; int max_str = RRD_DATA_MAX_LEN; int is_mountpoint = is_mount_point(parser_data[0]); if (!parser_data) { pr_err("No parser data available\n"); return -1; } if (!state) *s = state = allocate_parser_state(parser_data); if (is_mountpoint) { mount_point = parser_data[0]; if (!mount_point) { pr_err("Server address not specified\n"); return -1; } } else { server_addr = parser_data[0]; if (!server_addr) { pr_err("Server address not specified\n"); return -1; } h = OWNET_init(server_addr); if (h < 0) { pr_err("Failed to connect to server %s\n", server_addr); return -1; } } while (parser_data[i]) { double offset = 0, data, prev_data = 85; char *endptr; char ow_path[1024]; int retries = 0; int glitches = 0; if (!strcmp("U", parser_data[i])) { undefined: ret = snprintf(rrd_data, max_str, "U"); max_str -= ret; rrd_data += ret; goto next; } parse_opts(parser_data[i], ow_path, sizeof(ow_path), &offset); if (is_mountpoint) enable_simultaneous_reading(mount_point); while (1) { int j; char *tmp2; tmp = NULL; pr_info("Reading data for entry %s with offset of %.2f\n", ow_path, offset); if (is_mountpoint) ret = owfs_read(mount_point, ow_path, &tmp); else ret = OWNET_read(h, ow_path, &tmp); /* Skip leading white space */ tmp2 = tmp; for (j = 0; j < ret && *tmp2 == ' '; j++) tmp2++; if (ret <= 0) goto retry; /* * Older versions of OWNET_read did not NULL * terminate data. */ memcpy(buf, tmp, min(ret, sizeof(buf) -1)); buf[ret] = 0; data = strtod(buf, &endptr); free(tmp); tmp = NULL; /* * If we read the almost same value as * previously, it's not a glitch */ if (glitches && prev_data != 85) { double d = max_glitch_delta(&state[i]); if (fabs(data - prev_data) <= d * 2) break; } if (might_be_glitch(data, &state[i]) && glitches < 4 && retries < 7) { glitches++; prev_data = data; pr_info("Retrying due to a glitch: %f\n", data); goto retry; } break; retry: /* * In case of failure, retry with uncached * data. This is likely to help as it forces a * retry even if the sensor is missing from * the cache. We treat "85" also as a failure, * as temp sensors some times report 85 under * faulty conditions. */ ret = make_uncached(ow_path, sizeof(ow_path)); if (retries >= 10 || ret < 0) { pr_err("Failed to read entry %s: %m\n", parser_data[i]); goto undefined; } retries++; if (tmp) free(tmp); } update_glitch_data(data, &state[i]); if (endptr == buf) { pr_err("Failed to parse data %s\n", buf); goto undefined; } data += offset; ret = snprintf(rrd_data, max_str, "%f", data); max_str -= ret; rrd_data += ret; next: i++; if (!parser_data[i]) break; ret = snprintf(rrd_data, max_str, ":"); max_str -= ret; rrd_data += ret; } rrd_data = 0; if (!is_mountpoint) OWNET_finish(); return 0; } static struct parser_info onewire_parser_info = { .name = "onewire", .parse = onewire_parser, }; static int init_onewire_parser(void) { return register_parser(&onewire_parser_info); } struct plugin_info plugin_info = { .name = "onewire_parser", .init = init_onewire_parser, .version = RRDD_VERSION, };