Merge branch 'collectd-5.7'
[collectd.git] / src / mcelog.c
1 /*-
2  * collectd - src/mcelog.c
3  * MIT License
4  *
5  * Copyright(c) 2016 Intel Corporation. All rights reserved.
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining a
8  * copy of this software and associated documentation files (the "Software"),
9  * to deal in the Software without restriction, including without limitation
10  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
11  * and/or sell copies of the Software, and to permit persons to whom the
12  * Software is furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in
15  * all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23  * DEALINGS IN THE SOFTWARE.
24
25  * Authors:
26  *   Maryam Tahhan <maryam.tahhan@intel.com>
27  *   Volodymyr Mytnyk <volodymyrx.mytnyk@intel.com>
28  *   Taras Chornyi <tarasx.chornyi@intel.com>
29  *   Krzysztof Matczak <krzysztofx.matczak@intel.com>
30  */
31
32 #include "common.h"
33 #include "collectd.h"
34
35 #include <poll.h>
36 #include <sys/socket.h>
37 #include <sys/un.h>
38 #include <unistd.h>
39
40 #define MCELOG_PLUGIN "mcelog"
41 #define MCELOG_BUFF_SIZE 1024
42 #define MCELOG_POLL_TIMEOUT 1000 /* ms */
43 #define MCELOG_SOCKET_STR "SOCKET"
44 #define MCELOG_DIMM_NAME "DMI_NAME"
45 #define MCELOG_CORRECTED_ERR "corrected memory errors"
46 #define MCELOG_UNCORRECTED_ERR "uncorrected memory errors"
47
48 typedef struct mcelog_config_s {
49   char logfile[PATH_MAX]; /* mcelog logfile */
50   pthread_t tid;          /* poll thread id */
51 } mcelog_config_t;
52
53 typedef struct socket_adapter_s socket_adapter_t;
54
55 struct socket_adapter_s {
56   int sock_fd;                  /* mcelog server socket fd */
57   struct sockaddr_un unix_sock; /* mcelog client socket */
58   pthread_rwlock_t lock;
59   /* function pointers for socket operations */
60   int (*write)(socket_adapter_t *self, const char *msg, const size_t len);
61   int (*reinit)(socket_adapter_t *self);
62   int (*receive)(socket_adapter_t *self, FILE **p_file);
63   int (*close)(socket_adapter_t *self);
64 };
65
66 typedef struct mcelog_memory_rec_s {
67   int corrected_err_total; /* x total*/
68   int corrected_err_timed; /* x in 24h*/
69   char corrected_err_timed_period[DATA_MAX_NAME_LEN];
70   int uncorrected_err_total; /* x total*/
71   int uncorrected_err_timed; /* x in 24h*/
72   char uncorrected_err_timed_period[DATA_MAX_NAME_LEN];
73   char location[DATA_MAX_NAME_LEN];  /* SOCKET x CHANNEL x DIMM x*/
74   char dimm_name[DATA_MAX_NAME_LEN]; /* DMI_NAME "DIMM_F1" */
75 } mcelog_memory_rec_t;
76
77 static int socket_close(socket_adapter_t *self);
78 static int socket_write(socket_adapter_t *self, const char *msg,
79                         const size_t len);
80 static int socket_reinit(socket_adapter_t *self);
81 static int socket_receive(socket_adapter_t *self, FILE **p_file);
82
83 static mcelog_config_t g_mcelog_config = {.logfile = "/var/log/mcelog"};
84
85 static socket_adapter_t socket_adapter = {
86     .sock_fd = -1,
87     .unix_sock =
88         {
89             .sun_family = AF_UNIX, .sun_path = "/var/run/mcelog-client",
90         },
91     .lock = PTHREAD_RWLOCK_INITIALIZER,
92     .close = socket_close,
93     .write = socket_write,
94     .reinit = socket_reinit,
95     .receive = socket_receive,
96 };
97
98 static _Bool mcelog_thread_running;
99
100 static int mcelog_config(oconfig_item_t *ci) {
101   for (int i = 0; i < ci->children_num; i++) {
102     oconfig_item_t *child = ci->children + i;
103     if (strcasecmp("McelogClientSocket", child->key) == 0) {
104       if (cf_util_get_string_buffer(child, socket_adapter.unix_sock.sun_path,
105                                     sizeof(socket_adapter.unix_sock.sun_path)) <
106           0) {
107         ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".",
108               child->key);
109         return -1;
110       }
111     } else if (strcasecmp("McelogLogfile", child->key) == 0) {
112       if (cf_util_get_string_buffer(child, g_mcelog_config.logfile,
113                                     sizeof(g_mcelog_config.logfile)) < 0) {
114         ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".",
115               child->key);
116         return -1;
117       }
118     } else {
119       ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".",
120             child->key);
121       return -1;
122     }
123   }
124   return 0;
125 }
126
127 static int socket_close(socket_adapter_t *self) {
128   int ret = 0;
129   pthread_rwlock_rdlock(&self->lock);
130   if (fcntl(self->sock_fd, F_GETFL) != -1) {
131     char errbuf[MCELOG_BUFF_SIZE];
132     if (shutdown(self->sock_fd, SHUT_RDWR) != 0) {
133       ERROR(MCELOG_PLUGIN ": Socket shutdown failed: %s",
134             sstrerror(errno, errbuf, sizeof(errbuf)));
135       ret = -1;
136     }
137     if (close(self->sock_fd) != 0) {
138       ERROR(MCELOG_PLUGIN ": Socket close failed: %s",
139             sstrerror(errno, errbuf, sizeof(errbuf)));
140       ret = -1;
141     }
142   }
143   pthread_rwlock_unlock(&self->lock);
144   return ret;
145 }
146
147 static int socket_write(socket_adapter_t *self, const char *msg,
148                         const size_t len) {
149   int ret = 0;
150   pthread_rwlock_rdlock(&self->lock);
151   if (swrite(self->sock_fd, msg, len) < 0)
152     ret = -1;
153   pthread_rwlock_unlock(&self->lock);
154   return ret;
155 }
156
157 static void mcelog_dispatch_notification(notification_t *n) {
158   if (!n) {
159     ERROR(MCELOG_PLUGIN ": %s: NULL pointer", __FUNCTION__);
160     return;
161   }
162
163   sstrncpy(n->host, hostname_g, sizeof(n->host));
164   sstrncpy(n->type, "gauge", sizeof(n->type));
165   plugin_dispatch_notification(n);
166   if (n->meta)
167     plugin_notification_meta_free(n->meta);
168 }
169
170 static int socket_reinit(socket_adapter_t *self) {
171   char errbuff[MCELOG_BUFF_SIZE];
172   int ret = -1;
173   cdtime_t interval = plugin_get_interval();
174   struct timeval socket_timeout = CDTIME_T_TO_TIMEVAL(interval);
175
176   /* synchronization via write lock since sock_fd may be changed here */
177   pthread_rwlock_wrlock(&self->lock);
178   self->sock_fd =
179       socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
180   if (self->sock_fd < 0) {
181     ERROR(MCELOG_PLUGIN ": Could not create a socket. %s",
182           sstrerror(errno, errbuff, sizeof(errbuff)));
183     pthread_rwlock_unlock(&self->lock);
184     return ret;
185   }
186
187   /* Set socket timeout option */
188   if (setsockopt(self->sock_fd, SOL_SOCKET, SO_SNDTIMEO, &socket_timeout,
189                  sizeof(socket_timeout)) < 0)
190     ERROR(MCELOG_PLUGIN ": Failed to set the socket timeout option.");
191
192   /* downgrading to read lock due to possible recursive read locks
193    * in self->close(self) call */
194   pthread_rwlock_unlock(&self->lock);
195   pthread_rwlock_rdlock(&self->lock);
196   if (connect(self->sock_fd, (struct sockaddr *)&(self->unix_sock),
197               sizeof(self->unix_sock)) < 0) {
198     ERROR(MCELOG_PLUGIN ": Failed to connect to mcelog server. %s",
199           sstrerror(errno, errbuff, sizeof(errbuff)));
200     self->close(self);
201     ret = -1;
202   } else {
203     ret = 0;
204     mcelog_dispatch_notification(
205         &(notification_t){.severity = NOTIF_OKAY,
206                           .time = cdtime(),
207                           .message = "Connected to mcelog server",
208                           .plugin = MCELOG_PLUGIN,
209                           .type_instance = "mcelog_status"});
210   }
211   pthread_rwlock_unlock(&self->lock);
212   return ret;
213 }
214
215 static int mcelog_prepare_notification(notification_t *n,
216                                        const mcelog_memory_rec_t *mr) {
217   if (n == NULL || mr == NULL)
218     return -1;
219
220   if ((mr->location[0] != '\0') &&
221       (plugin_notification_meta_add_string(n, MCELOG_SOCKET_STR, mr->location) <
222        0)) {
223     ERROR(MCELOG_PLUGIN ": add memory location meta data failed");
224     return -1;
225   }
226   if ((mr->dimm_name[0] != '\0') &&
227       (plugin_notification_meta_add_string(n, MCELOG_DIMM_NAME, mr->dimm_name) <
228        0)) {
229     ERROR(MCELOG_PLUGIN ": add DIMM name meta data failed");
230     plugin_notification_meta_free(n->meta);
231     return -1;
232   }
233   if (plugin_notification_meta_add_signed_int(n, MCELOG_CORRECTED_ERR,
234                                               mr->corrected_err_total) < 0) {
235     ERROR(MCELOG_PLUGIN ": add corrected errors meta data failed");
236     plugin_notification_meta_free(n->meta);
237     return -1;
238   }
239   if (plugin_notification_meta_add_signed_int(
240           n, "corrected memory timed errors", mr->corrected_err_timed) < 0) {
241     ERROR(MCELOG_PLUGIN ": add corrected timed errors meta data failed");
242     plugin_notification_meta_free(n->meta);
243     return -1;
244   }
245   if ((mr->corrected_err_timed_period[0] != '\0') &&
246       (plugin_notification_meta_add_string(n, "corrected errors time period",
247                                            mr->corrected_err_timed_period) <
248        0)) {
249     ERROR(MCELOG_PLUGIN ": add corrected errors period meta data failed");
250     plugin_notification_meta_free(n->meta);
251     return -1;
252   }
253   if (plugin_notification_meta_add_signed_int(n, MCELOG_UNCORRECTED_ERR,
254                                               mr->uncorrected_err_total) < 0) {
255     ERROR(MCELOG_PLUGIN ": add corrected errors meta data failed");
256     plugin_notification_meta_free(n->meta);
257     return -1;
258   }
259   if (plugin_notification_meta_add_signed_int(n,
260                                               "uncorrected memory timed errors",
261                                               mr->uncorrected_err_timed) < 0) {
262     ERROR(MCELOG_PLUGIN ": add corrected timed errors meta data failed");
263     plugin_notification_meta_free(n->meta);
264     return -1;
265   }
266   if ((mr->uncorrected_err_timed_period[0] != '\0') &&
267       (plugin_notification_meta_add_string(n, "uncorrected errors time period",
268                                            mr->uncorrected_err_timed_period) <
269        0)) {
270     ERROR(MCELOG_PLUGIN ": add corrected errors period meta data failed");
271     plugin_notification_meta_free(n->meta);
272     return -1;
273   }
274
275   return 0;
276 }
277
278 static int mcelog_submit(const mcelog_memory_rec_t *mr) {
279
280   if (!mr) {
281     ERROR(MCELOG_PLUGIN ": %s: NULL pointer", __FUNCTION__);
282     return -1;
283   }
284
285   value_list_t vl = {
286       .values_len = 1,
287       .values = &(value_t){.derive = (derive_t)mr->corrected_err_total},
288       .time = cdtime(),
289       .plugin = MCELOG_PLUGIN,
290       .type = "errors",
291       .type_instance = "corrected_memory_errors"};
292
293   if (mr->dimm_name[0] != '\0')
294     snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s_%s",
295              mr->location, mr->dimm_name);
296   else
297     sstrncpy(vl.plugin_instance, mr->location, sizeof(vl.plugin_instance));
298
299   plugin_dispatch_values(&vl);
300
301   snprintf(vl.type_instance, sizeof(vl.type_instance),
302            "corrected_memory_errors_in_%s", mr->corrected_err_timed_period);
303   vl.values = &(value_t){.derive = (derive_t)mr->corrected_err_timed};
304   plugin_dispatch_values(&vl);
305
306   sstrncpy(vl.type_instance, "uncorrected_memory_errors",
307            sizeof(vl.type_instance));
308   vl.values = &(value_t){.derive = (derive_t)mr->uncorrected_err_total};
309   plugin_dispatch_values(&vl);
310
311   snprintf(vl.type_instance, sizeof(vl.type_instance),
312            "uncorrected_memory_errors_in_%s", mr->uncorrected_err_timed_period);
313   vl.values = &(value_t){.derive = (derive_t)mr->uncorrected_err_timed};
314   plugin_dispatch_values(&vl);
315
316   return 0;
317 }
318
319 static int parse_memory_info(FILE *p_file, mcelog_memory_rec_t *memory_record) {
320   char buf[DATA_MAX_NAME_LEN] = {0};
321   while (fgets(buf, sizeof(buf), p_file)) {
322     /* Got empty line or "done" */
323     if ((!strncmp("\n", buf, strlen(buf))) ||
324         (!strncmp(buf, "done\n", strlen(buf))))
325       return 1;
326     if (strlen(buf) < 5)
327       continue;
328     if (!strncmp(buf, MCELOG_SOCKET_STR, strlen(MCELOG_SOCKET_STR))) {
329       sstrncpy(memory_record->location, buf, strlen(buf));
330       /* replace spaces with '_' */
331       for (size_t i = 0; i < strlen(memory_record->location); i++)
332         if (memory_record->location[i] == ' ')
333           memory_record->location[i] = '_';
334       DEBUG(MCELOG_PLUGIN ": Got SOCKET INFO %s", memory_record->location);
335     }
336     if (!strncmp(buf, MCELOG_DIMM_NAME, strlen(MCELOG_DIMM_NAME))) {
337       char *name = NULL;
338       char *saveptr = NULL;
339       name = strtok_r(buf, "\"", &saveptr);
340       if (name != NULL && saveptr != NULL) {
341         name = strtok_r(NULL, "\"", &saveptr);
342         if (name != NULL) {
343           sstrncpy(memory_record->dimm_name, name,
344                    sizeof(memory_record->dimm_name));
345           DEBUG(MCELOG_PLUGIN ": Got DIMM NAME %s", memory_record->dimm_name);
346         }
347       }
348     }
349     if (!strncmp(buf, MCELOG_CORRECTED_ERR, strlen(MCELOG_CORRECTED_ERR))) {
350       /* Get next line*/
351       if (fgets(buf, sizeof(buf), p_file) != NULL) {
352         sscanf(buf, "\t%d total", &(memory_record->corrected_err_total));
353         DEBUG(MCELOG_PLUGIN ": Got corrected error total %d",
354               memory_record->corrected_err_total);
355       }
356       if (fgets(buf, sizeof(buf), p_file) != NULL) {
357         sscanf(buf, "\t%d in %s", &(memory_record->corrected_err_timed),
358                memory_record->corrected_err_timed_period);
359         DEBUG(MCELOG_PLUGIN ": Got timed corrected errors %d in %s",
360               memory_record->corrected_err_total,
361               memory_record->corrected_err_timed_period);
362       }
363     }
364     if (!strncmp(buf, MCELOG_UNCORRECTED_ERR, strlen(MCELOG_UNCORRECTED_ERR))) {
365       if (fgets(buf, sizeof(buf), p_file) != NULL) {
366         sscanf(buf, "\t%d total", &(memory_record->uncorrected_err_total));
367         DEBUG(MCELOG_PLUGIN ": Got uncorrected error total %d",
368               memory_record->uncorrected_err_total);
369       }
370       if (fgets(buf, sizeof(buf), p_file) != NULL) {
371         sscanf(buf, "\t%d in %s", &(memory_record->uncorrected_err_timed),
372                memory_record->uncorrected_err_timed_period);
373         DEBUG(MCELOG_PLUGIN ": Got timed uncorrected errors %d in %s",
374               memory_record->uncorrected_err_total,
375               memory_record->uncorrected_err_timed_period);
376       }
377     }
378     memset(buf, 0, sizeof(buf));
379   }
380   /* parsing definitely finished */
381   return 0;
382 }
383
384 static void poll_worker_cleanup(void *arg) {
385   mcelog_thread_running = 0;
386   FILE *p_file = *((FILE **)arg);
387   if (p_file != NULL)
388     fclose(p_file);
389   free(arg);
390 }
391
392 static int socket_receive(socket_adapter_t *self, FILE **pp_file) {
393   int res = -1;
394   pthread_rwlock_rdlock(&self->lock);
395   struct pollfd poll_fd = {
396       .fd = self->sock_fd, .events = POLLIN | POLLPRI,
397   };
398
399   if ((res = poll(&poll_fd, 1, MCELOG_POLL_TIMEOUT)) <= 0) {
400     if (res != 0 && errno != EINTR) {
401       char errbuf[MCELOG_BUFF_SIZE];
402       ERROR("mcelog: poll failed: %s",
403             sstrerror(errno, errbuf, sizeof(errbuf)));
404     }
405     pthread_rwlock_unlock(&self->lock);
406     return res;
407   }
408
409   if (poll_fd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
410     /* connection is broken */
411     ERROR(MCELOG_PLUGIN ": Connection to socket is broken");
412     if (poll_fd.revents & (POLLERR | POLLHUP)) {
413       mcelog_dispatch_notification(
414           &(notification_t){.severity = NOTIF_FAILURE,
415                             .time = cdtime(),
416                             .message = "Connection to mcelog socket is broken.",
417                             .plugin = MCELOG_PLUGIN,
418                             .type_instance = "mcelog_status"});
419     }
420     pthread_rwlock_unlock(&self->lock);
421     return -1;
422   }
423
424   if (!(poll_fd.revents & (POLLIN | POLLPRI))) {
425     INFO(MCELOG_PLUGIN ": No data to read");
426     pthread_rwlock_unlock(&self->lock);
427     return 0;
428   }
429
430   if ((*pp_file = fdopen(dup(self->sock_fd), "r")) == NULL)
431     res = -1;
432
433   pthread_rwlock_unlock(&self->lock);
434   return res;
435 }
436
437 static void *poll_worker(__attribute__((unused)) void *arg) {
438   char errbuf[MCELOG_BUFF_SIZE];
439   mcelog_thread_running = 1;
440   FILE **pp_file = calloc(1, sizeof(*pp_file));
441   if (pp_file == NULL) {
442     ERROR("mcelog: memory allocation failed: %s",
443           sstrerror(errno, errbuf, sizeof(errbuf)));
444     pthread_exit((void *)1);
445   }
446
447   pthread_cleanup_push(poll_worker_cleanup, pp_file);
448
449   while (1) {
450     /* blocking call */
451     int res = socket_adapter.receive(&socket_adapter, pp_file);
452     if (res < 0) {
453       socket_adapter.close(&socket_adapter);
454       while (socket_adapter.reinit(&socket_adapter) != 0) {
455         nanosleep(&CDTIME_T_TO_TIMESPEC(MS_TO_CDTIME_T(MCELOG_POLL_TIMEOUT)),
456                   NULL);
457       }
458       continue;
459     }
460     /* timeout or no data to read */
461     else if (res == 0)
462       continue;
463
464     if (*pp_file == NULL)
465       continue;
466
467     mcelog_memory_rec_t memory_record = {0};
468     while (parse_memory_info(*pp_file, &memory_record)) {
469       /* Check if location was successfully parsed */
470       if (memory_record.location[0] == '\0') {
471         memset(&memory_record, 0, sizeof(memory_record));
472         continue;
473       }
474
475       notification_t n = {.severity = NOTIF_OKAY,
476                           .time = cdtime(),
477                           .message = "Got memory errors info.",
478                           .plugin = MCELOG_PLUGIN,
479                           .type_instance = "memory_erros"};
480
481       if (mcelog_prepare_notification(&n, &memory_record) == 0)
482         mcelog_dispatch_notification(&n);
483       if (mcelog_submit(&memory_record) != 0)
484         ERROR(MCELOG_PLUGIN ": Failed to submit memory errors");
485       memset(&memory_record, 0, sizeof(memory_record));
486     }
487
488     fclose(*pp_file);
489     *pp_file = NULL;
490   }
491
492   mcelog_thread_running = 0;
493   pthread_cleanup_pop(1);
494   return NULL;
495 }
496
497 static int mcelog_init(void) {
498   if (socket_adapter.reinit(&socket_adapter) != 0) {
499     ERROR(MCELOG_PLUGIN ": Cannot connect to client socket");
500     return -1;
501   }
502
503   if (plugin_thread_create(&g_mcelog_config.tid, NULL, poll_worker, NULL,
504                            NULL) != 0) {
505     ERROR(MCELOG_PLUGIN ": Error creating poll thread.");
506     return -1;
507   }
508   return 0;
509 }
510
511 static int get_memory_machine_checks(void) {
512   static const char dump[] = "dump all bios\n";
513   int ret = socket_adapter.write(&socket_adapter, dump, sizeof(dump));
514   if (ret != 0)
515     ERROR(MCELOG_PLUGIN ": SENT DUMP REQUEST FAILED");
516   else
517     DEBUG(MCELOG_PLUGIN ": SENT DUMP REQUEST OK");
518   return ret;
519 }
520
521 static int mcelog_read(__attribute__((unused)) user_data_t *ud) {
522   DEBUG(MCELOG_PLUGIN ": %s", __FUNCTION__);
523
524   if (get_memory_machine_checks() != 0)
525     ERROR(MCELOG_PLUGIN ": MACHINE CHECK INFO NOT AVAILABLE");
526
527   return 0;
528 }
529
530 static int mcelog_shutdown(void) {
531   int ret = 0;
532   if (mcelog_thread_running) {
533     pthread_cancel(g_mcelog_config.tid);
534     if (pthread_join(g_mcelog_config.tid, NULL) != 0) {
535       ERROR(MCELOG_PLUGIN ": Stopping thread failed.");
536       ret = -1;
537     }
538   }
539
540   ret = socket_adapter.close(&socket_adapter) || ret;
541   pthread_rwlock_destroy(&(socket_adapter.lock));
542   return -ret;
543 }
544
545 void module_register(void) {
546   plugin_register_complex_config(MCELOG_PLUGIN, mcelog_config);
547   plugin_register_init(MCELOG_PLUGIN, mcelog_init);
548   plugin_register_complex_read(NULL, MCELOG_PLUGIN, mcelog_read, 0, NULL);
549   plugin_register_shutdown(MCELOG_PLUGIN, mcelog_shutdown);
550 }