]> git.itanic.dy.fi Git - rrdd/blob - onewire_parser.c
worker_thread: Drop process priority when executing low priority work
[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[1024], p2[1024], *p = path;
116
117         if (strstr(path, "/uncached/"))
118                 return 0;
119
120         p1[sizeof(p1) - 1] = '\0';
121         p2[sizeof(p2) - 1] = '\0';
122
123         /*
124          * Naively assume the "uncached" string can be put after the
125          * first slash
126          */
127         while (*p && *p != '/')
128                 p++;
129
130         if (!*p)
131                 return -1;
132
133         *p = 0;
134         p++;
135
136         strncpy(p1, path, sizeof(p1) - 1);
137         strncpy(p2, p, sizeof(p2) - 1);
138         snprintf(path, len, "%s/uncached/%s", p1, p2);
139
140         return 0;
141 }
142
143 static int owfs_read(const char *mount_point, const char *path, char **res)
144 {
145         char result[64];
146         char file[2048];
147         int fd, ret;
148
149         snprintf(file, sizeof(file), "%s/%s", mount_point, path);
150
151         fd = open(file, O_RDONLY | O_CLOEXEC);
152         if (fd < 0) {
153                 pr_err("Failed to open file %s: %m\n", file);
154                 return -1;
155         }
156
157         ret = read(fd, result, sizeof(result));
158         if (ret < 0) {
159                 pr_err("Failed to read from file %s: %m\n", file);
160                 goto out_close;
161         }
162
163         *res = strndup(result, sizeof(result));
164
165 out_close:
166         close(fd);
167
168         return ret;
169 }
170
171 static int is_mount_point(const char *str)
172 {
173         /*
174          * Filesystem paths begin with a slash, everything else must
175          * be a network addresses
176          */
177         if (str[0] == '/')
178                 return 1;
179
180         return 0;
181 }
182
183 static int onewire_parser(char *rrd_data, const char **parser_data, void **s)
184 {
185         OWNET_HANDLE h;
186         const char *server_addr, *mount_point;
187         struct owparser_state *state = *s;
188         char buf[24], *tmp;
189         int i = 1, ret;
190         int max_str = RRD_DATA_MAX_LEN;
191         int is_mountpoint = is_mount_point(parser_data[0]);
192
193         if (!parser_data) {
194                 pr_err("No parser data available\n");
195                 return -1;
196         }
197
198         if (!state)
199                 *s = state = allocate_parser_state(parser_data);
200
201         if (is_mountpoint) {
202                 mount_point = parser_data[0];
203
204                 if (!mount_point) {
205                         pr_err("Server address not specified\n");
206                         return -1;
207                 }
208         } else {
209                 server_addr = parser_data[0];
210
211                 if (!server_addr) {
212                         pr_err("Server address not specified\n");
213                         return -1;
214                 }
215
216                 h = OWNET_init(server_addr);
217                 if (h < 0) {
218                         pr_err("Failed to connect to server %s\n", server_addr);
219                         return -1;
220                 }
221         }
222
223         while (parser_data[i]) {
224                 double offset = 0, data, prev_data = 85;
225                 char *endptr;
226                 char ow_path[1024];
227                 int retries = 0;
228                 int glitches = 0;
229
230                 if (!strcmp("U", parser_data[i])) {
231 undefined:
232                         ret = snprintf(rrd_data, max_str, "U");
233                         max_str -= ret;
234                         rrd_data += ret;
235                         goto next;
236                 }
237
238                 parse_opts(parser_data[i], ow_path, sizeof(ow_path), &offset);
239
240                 while (1) {
241                         int fail, j;
242                         char *tmp2;
243
244                         tmp = NULL;
245                         pr_info("Reading data for entry %s with offset of %.2f\n",
246                                 ow_path, offset);
247
248                         if (is_mountpoint)
249                                 ret = owfs_read(mount_point, ow_path, &tmp);
250                         else
251                                 ret = OWNET_read(h, ow_path, &tmp);
252
253                         /* Skip leading white space */
254                         tmp2 = tmp;
255                         for (j = 0; j < ret && *tmp2 == ' '; j++)
256                                 tmp2++;
257
258                         if (ret > 0)
259                                 fail = !strncmp(tmp2, "85", 2);
260                         else
261                                 fail = 1;
262
263                         if (ret <= 0 || fail)
264                                 goto retry;
265
266
267                         /*
268                          * Older versions of OWNET_read did not NULL
269                          * terminate data.
270                          */
271                         memcpy(buf, tmp, min(ret, sizeof(buf) -1));
272                         buf[ret] = 0;
273
274                         data = strtod(buf, &endptr);
275
276                         free(tmp);
277                         tmp = NULL;
278
279                         /*
280                          * If we read the same value as previously,
281                          * it's not a glitch
282                          */
283                         if (glitches && prev_data == data)
284                                 break;
285
286                         if (might_be_glitch(data, &state[i]) &&
287                                 glitches < 2 && retries < 7) {
288                                 glitches++;
289                                 prev_data = data;
290                                 pr_info("Retrying due to a glitch: %f\n", data);
291                                 goto retry;
292                         }
293
294                         break;
295 retry:
296                         /*
297                          * In case of failure, retry with uncached
298                          * data. This is likely to help as it forces a
299                          * retry even if the sensor is missing from
300                          * the cache. We treat "85" also as a failure,
301                          * as temp sensors some times report 85 under
302                          * faulty conditions.
303                          */
304                         ret = make_uncached(ow_path, sizeof(ow_path));
305                         if (retries >= 10 || ret < 0) {
306                                 pr_err("Failed to read entry %s: %m\n",
307                                         parser_data[i]);
308                                 goto undefined;
309                         }
310                         retries++;
311                         if (tmp)
312                                 free(tmp);
313                 }
314
315                 update_glitch_data(data, &state[i]);
316
317                 if (endptr == buf) {
318                         pr_err("Failed to parse data %s\n", buf);
319                         goto undefined;
320                 }
321
322                 data += offset;
323
324                 ret = snprintf(rrd_data, max_str, "%f", data);
325                 max_str -= ret;
326                 rrd_data += ret;
327
328 next:
329                 i++;
330                 if (!parser_data[i])
331                         break;
332
333                 ret = snprintf(rrd_data, max_str, ":");
334                 max_str -= ret;
335                 rrd_data += ret;
336         }
337         rrd_data = 0;
338
339         if (!is_mountpoint)
340                 OWNET_finish();
341
342         return 0;
343 }
344
345 static struct parser_info onewire_parser_info = {
346         .name = "onewire",
347         .parse = onewire_parser,
348 };
349
350 static int init_onewire_parser(void)
351 {
352         return register_parser(&onewire_parser_info);
353 }
354
355 struct plugin_info plugin_info = {
356         .name = "onewire_parser",
357         .init = init_onewire_parser,
358         .version = RRDD_VERSION,
359 };