]> git.itanic.dy.fi Git - rrdd/blob - onewire_parser.c
onewire_parser: Fix "85" degree handling case
[rrdd] / onewire_parser.c
1 #include <stdio.h>
2 #include <ownetapi.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>
6 #include <unistd.h>
7 #include <math.h>
8
9 #include "parser.h"
10 #include "debug.h"
11 #include "string.h"
12 #include "utils.h"
13 #include "plugin.h"
14 #include "version.h"
15 #include "utils.h"
16
17 struct owparser_state {
18         double prev_delta[20];
19         double prev_data;
20 };
21
22 static struct owparser_state *allocate_parser_state(const char **datastr)
23 {
24         int i;
25
26         /*
27          * Count how many sensor entries we need. First entry belongs
28          * to server address or mount point and last one is NULL. So
29          * the index final is the count of actual valid sensor
30          * entries.
31          */
32         for (i = 0; datastr[i]; i++)
33                 ;
34
35         return calloc(sizeof(struct owparser_state), i);
36 }
37
38 static double max_glitch_delta(const struct owparser_state *s)
39 {
40         double max_delta = 0;
41         int i;
42
43         for (i = 0; i < ARRAY_SIZE(s->prev_delta); i++)
44                 max_delta = max(s->prev_delta[i], max_delta);
45
46         return max_delta;
47 }
48
49 static int might_be_glitch(double data, const struct owparser_state *s)
50 {
51         double max_delta, delta;
52
53         /* The known bad data value from the sensor */
54         if (data == 85)
55                 return 1;
56
57         max_delta = max_glitch_delta(s);
58
59         /* Probably no enough data yet, so no glitch detection */
60         if (max_delta == 0)
61                 return 0;
62
63         /*
64          * Simple glitch detection. If delta to previous value is more
65          * than twice as larger as any of the older delta, we might
66          * have a glitch
67          */
68         delta = fabs(data - s->prev_data);
69
70         return delta > max_delta * 2;
71 }
72
73 static void update_glitch_data(double data, struct owparser_state *s)
74 {
75         double max_delta = 0;
76         int i;
77
78         for (i = 1; i < ARRAY_SIZE(s->prev_delta); i++) {
79                 s->prev_delta[i - 1] = s->prev_delta[i];
80                 max_delta = max(s->prev_delta[i], max_delta);
81         }
82
83         /* Avoid storing the first incorrect delta value */
84         if (s->prev_data || max_delta)
85                 s->prev_delta[--i] = fabs(data - s->prev_data);
86
87         s->prev_data = data;
88 }
89
90 static int parse_opts(const char *str, char *ow_path, size_t pathlen,
91                 double *offset)
92 {
93         char *endptr;
94         const char *start_str = str;
95         const char offset_str[] = "offset=";
96
97         if (!offset)
98                 return 0;
99
100         /*
101          * Skip the onewire path entry. Options begin after the first
102          * white space
103          */
104         for (; *str; str++)
105                 if (isspace(*str))
106                         break;
107
108         /* Copy the onewire path without options */
109         strncpy(ow_path, start_str, pathlen);
110         ow_path[str - start_str] = '\0';
111
112         /* Get the next non-space, which is where the argument begins */
113         for (; *str; str++)
114                 if (!isspace(*str))
115                         break;
116
117         if (strncmp(str, offset_str, sizeof(offset_str) - 1))
118                 return 0;
119         str += sizeof(offset_str) - 1;
120
121         *offset = strtod(str, &endptr);
122
123         if (str != endptr)
124                 return 1;
125
126         return 0;
127 }
128
129 static int make_uncached(char *path, size_t len)
130 {
131         int ret;
132         char p1[1024], p2[1024], *p = path;
133
134         if (strstr(path, "/uncached/"))
135                 return 0;
136
137         p1[sizeof(p1) - 1] = '\0';
138         p2[sizeof(p2) - 1] = '\0';
139
140         /*
141          * Naively assume the "uncached" string can be put after the
142          * first slash
143          */
144         while (*p && *p != '/')
145                 p++;
146
147         if (!*p)
148                 return -1;
149
150         *p = 0;
151         p++;
152
153         strncpy(p1, path, sizeof(p1) - 1);
154         strncpy(p2, p, sizeof(p2) - 1);
155         ret = snprintf(path, len, "%s/uncached/%s", p1, p2);
156
157         /* No actual data overflow, snprintf just couldn't fit all data in the buffer */
158         if (ret >= RRD_DATA_MAX_LEN)
159                 pr_err("Buffer overlfow\n");
160
161         return 0;
162 }
163
164 static int owfs_read(const char *mount_point, const char *path, char **res)
165 {
166         char result[64];
167         char file[2048];
168         int fd, ret;
169
170         snprintf(file, sizeof(file), "%s/%s", mount_point, path);
171
172         fd = open(file, O_RDONLY | O_CLOEXEC);
173         if (fd < 0) {
174                 pr_err("Failed to open file %s: %m\n", file);
175                 return -1;
176         }
177
178         ret = read(fd, result, sizeof(result));
179         if (ret < 0) {
180                 pr_err("Failed to read from file %s: %m\n", file);
181                 goto out_close;
182         }
183
184         *res = strndup(result, sizeof(result));
185
186 out_close:
187         close(fd);
188
189         return ret;
190 }
191
192 static int is_mount_point(const char *str)
193 {
194         /*
195          * Filesystem paths begin with a slash, everything else must
196          * be a network addresses
197          */
198         if (str[0] == '/')
199                 return 1;
200
201         return 0;
202 }
203
204 static int onewire_parser(char *rrd_data, const char **parser_data, void **s)
205 {
206         OWNET_HANDLE h;
207         const char *server_addr, *mount_point;
208         struct owparser_state *state = *s;
209         char buf[24], *tmp;
210         int i = 1, ret;
211         int max_str = RRD_DATA_MAX_LEN;
212         int is_mountpoint = is_mount_point(parser_data[0]);
213
214         if (!parser_data) {
215                 pr_err("No parser data available\n");
216                 return -1;
217         }
218
219         if (!state)
220                 *s = state = allocate_parser_state(parser_data);
221
222         if (is_mountpoint) {
223                 mount_point = parser_data[0];
224
225                 if (!mount_point) {
226                         pr_err("Server address not specified\n");
227                         return -1;
228                 }
229         } else {
230                 server_addr = parser_data[0];
231
232                 if (!server_addr) {
233                         pr_err("Server address not specified\n");
234                         return -1;
235                 }
236
237                 h = OWNET_init(server_addr);
238                 if (h < 0) {
239                         pr_err("Failed to connect to server %s\n", server_addr);
240                         return -1;
241                 }
242         }
243
244         while (parser_data[i]) {
245                 double offset = 0, data, prev_data = 85;
246                 char *endptr;
247                 char ow_path[1024];
248                 int retries = 0;
249                 int glitches = 0;
250
251                 if (!strcmp("U", parser_data[i])) {
252 undefined:
253                         ret = snprintf(rrd_data, max_str, "U");
254                         max_str -= ret;
255                         rrd_data += ret;
256                         goto next;
257                 }
258
259                 parse_opts(parser_data[i], ow_path, sizeof(ow_path), &offset);
260
261                 while (1) {
262                         int j;
263                         char *tmp2;
264
265                         tmp = NULL;
266                         pr_info("Reading data for entry %s with offset of %.2f\n",
267                                 ow_path, offset);
268
269                         if (is_mountpoint)
270                                 ret = owfs_read(mount_point, ow_path, &tmp);
271                         else
272                                 ret = OWNET_read(h, ow_path, &tmp);
273
274                         /* Skip leading white space */
275                         tmp2 = tmp;
276                         for (j = 0; j < ret && *tmp2 == ' '; j++)
277                                 tmp2++;
278
279                         if (ret <= 0)
280                                 goto retry;
281
282                         /*
283                          * Older versions of OWNET_read did not NULL
284                          * terminate data.
285                          */
286                         memcpy(buf, tmp, min(ret, sizeof(buf) -1));
287                         buf[ret] = 0;
288
289                         data = strtod(buf, &endptr);
290
291                         free(tmp);
292                         tmp = NULL;
293
294                         /*
295                          * If we read the almost same value as
296                          * previously, it's not a glitch
297                          */
298                         if (glitches && prev_data != 85) {
299                                 double d = max_glitch_delta(&state[i]);
300
301                                 if (fabs(data - prev_data) <= d * 2)
302                                         break;
303                         }
304
305                         if (might_be_glitch(data, &state[i]) &&
306                                 glitches < 4 && retries < 7) {
307                                 glitches++;
308                                 prev_data = data;
309                                 pr_info("Retrying due to a glitch: %f\n", data);
310                                 goto retry;
311                         }
312
313                         break;
314 retry:
315                         /*
316                          * In case of failure, retry with uncached
317                          * data. This is likely to help as it forces a
318                          * retry even if the sensor is missing from
319                          * the cache. We treat "85" also as a failure,
320                          * as temp sensors some times report 85 under
321                          * faulty conditions.
322                          */
323                         ret = make_uncached(ow_path, sizeof(ow_path));
324                         if (retries >= 10 || ret < 0) {
325                                 pr_err("Failed to read entry %s: %m\n",
326                                         parser_data[i]);
327                                 goto undefined;
328                         }
329                         retries++;
330                         if (tmp)
331                                 free(tmp);
332                 }
333
334                 update_glitch_data(data, &state[i]);
335
336                 if (endptr == buf) {
337                         pr_err("Failed to parse data %s\n", buf);
338                         goto undefined;
339                 }
340
341                 data += offset;
342
343                 ret = snprintf(rrd_data, max_str, "%f", data);
344                 max_str -= ret;
345                 rrd_data += ret;
346
347 next:
348                 i++;
349                 if (!parser_data[i])
350                         break;
351
352                 ret = snprintf(rrd_data, max_str, ":");
353                 max_str -= ret;
354                 rrd_data += ret;
355         }
356         rrd_data = 0;
357
358         if (!is_mountpoint)
359                 OWNET_finish();
360
361         return 0;
362 }
363
364 static struct parser_info onewire_parser_info = {
365         .name = "onewire",
366         .parse = onewire_parser,
367 };
368
369 static int init_onewire_parser(void)
370 {
371         return register_parser(&onewire_parser_info);
372 }
373
374 struct plugin_info plugin_info = {
375         .name = "onewire_parser",
376         .init = init_onewire_parser,
377         .version = RRDD_VERSION,
378 };