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