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