#include <stdio.h>
#include <ownetapi.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <math.h>
+#include <time.h>
#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);
-static int parse_opts(const char *str, char *ow_path, size_t pathlen, double *offset)
+ 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;
break;
/* Copy the onewire path without options */
- strncpy(ow_path, start_str, pathlen);
+ strncpy(ow_path, start_str, pathlen - 1);
ow_path[str - start_str] = '\0';
/* Get the next non-space, which is where the argument begins */
return 0;
}
-static int onewire_parser(char *rrd_data, const char **parser_data)
+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;
+ 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;
}
- server_addr = parser_data[0];
+ if (!state)
+ *s = state = allocate_parser_state(parser_data);
- if (!server_addr) {
- pr_err("Server address not specified\n");
- return -1;
- }
+ if (is_mountpoint) {
+ mount_point = parser_data[0];
- h = OWNET_init(server_addr);
- if (h < 0) {
- pr_err("Failed to connect to server %s\n", server_addr);
- return -1;
+ 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;
+ 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:
parse_opts(parser_data[i], ow_path, sizeof(ow_path), &offset);
- pr_info("Reading data for entry %s with offset of %.2f\n",
- ow_path, offset);
- ret = OWNET_read(h, ow_path, &tmp);
- if (ret < 0) {
- pr_err("Failed to read entry %s\n", parser_data[i]);
- goto undefined;
- }
+ if (is_mountpoint)
+ enable_simultaneous_reading(mount_point);
+
+ while (1) {
+ int j;
+ char *tmp2;
- /* The data from OWNET_read appears to not be NULL terminated */
- memcpy(buf, tmp, MIN(ret, sizeof(buf) -1));
- free(tmp);
- buf[ret] = 0;
+ tmp = NULL;
+ pr_info("Reading data for entry %s with offset of %.2f\n",
+ ow_path, offset);
- data = strtod(buf, &endptr);
+ 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);
}
rrd_data = 0;
- OWNET_finish();
+ if (!is_mountpoint)
+ OWNET_finish();
+
return 0;
}
struct plugin_info plugin_info = {
.name = "onewire_parser",
.init = init_onewire_parser,
+ .version = RRDD_VERSION,
};