ipmi plugin: Send notifications upon `not present' sensors.
[collectd.git] / src / ipmi.c
1 /**
2  * collectd - src/ipmi.c
3  * Copyright (C) 2008  Florian octo Forster
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Florian octo Forster <octo at verplant.org>
20  **/
21
22 #include "collectd.h"
23 #include "common.h"
24 #include "plugin.h"
25 #include "utils_ignorelist.h"
26
27 #include <pthread.h>
28
29 #include <OpenIPMI/ipmiif.h>
30 #include <OpenIPMI/ipmi_err.h>
31 #include <OpenIPMI/ipmi_posix.h>
32 #include <OpenIPMI/ipmi_conn.h>
33 #include <OpenIPMI/ipmi_smi.h>
34
35 /*
36  * Private data types
37  */
38 struct c_ipmi_sensor_list_s;
39 typedef struct c_ipmi_sensor_list_s c_ipmi_sensor_list_t;
40
41 struct c_ipmi_sensor_list_s
42 {
43   ipmi_sensor_id_t sensor_id;
44   char sensor_name[DATA_MAX_NAME_LEN];
45   char sensor_type[DATA_MAX_NAME_LEN];
46   int sensor_not_present;
47   c_ipmi_sensor_list_t *next;
48 };
49
50 /*
51  * Module global variables
52  */
53 static pthread_mutex_t sensor_list_lock = PTHREAD_MUTEX_INITIALIZER;
54 static c_ipmi_sensor_list_t *sensor_list = NULL;
55
56 static int c_ipmi_init_in_progress = 0;
57 static int c_ipmi_active = 0;
58 static pthread_t thread_id = (pthread_t) 0;
59
60 static const char *config_keys[] =
61 {
62         "Sensor",
63         "IgnoreSelected",
64         "NotifySensorAdd",
65         "NotifySensorRemove",
66         "NotifySensorNotPresent"
67 };
68 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
69
70 static ignorelist_t *ignorelist = NULL;
71
72 static int c_ipmi_nofiy_add = 0;
73 static int c_ipmi_nofiy_remove = 0;
74 static int c_ipmi_nofiy_notpresent = 0;
75
76 /*
77  * Misc private functions
78  */
79 static void c_ipmi_error (const char *func, int status)
80 {
81   char errbuf[4096];
82
83   memset (errbuf, 0, sizeof (errbuf));
84
85   if (IPMI_IS_OS_ERR (status))
86   {
87     sstrerror (IPMI_GET_OS_ERR (status), errbuf, sizeof (errbuf));
88   }
89   else if (IPMI_IS_IPMI_ERR (status))
90   {
91     ipmi_get_error_string (IPMI_GET_IPMI_ERR (status), errbuf, sizeof (errbuf));
92   }
93
94   if (errbuf[0] == 0)
95   {
96     ssnprintf (errbuf, sizeof (errbuf), "Unknown error %#x", status);
97   }
98   errbuf[sizeof (errbuf) - 1] = 0;
99
100   ERROR ("ipmi plugin: %s failed: %s", func, errbuf);
101 } /* void c_ipmi_error */
102
103 /*
104  * Sensor handlers
105  */
106 /* Prototype for sensor_list_remove, so sensor_read_handler can call it. */
107 static int sensor_list_remove (ipmi_sensor_t *sensor);
108
109 static void sensor_read_handler (ipmi_sensor_t *sensor,
110     int err,
111     enum ipmi_value_present_e value_present,
112     unsigned int raw_value,
113     double value,
114     ipmi_states_t *states,
115     void *user_data)
116 {
117   value_t values[1];
118   value_list_t vl = VALUE_LIST_INIT;
119
120   c_ipmi_sensor_list_t *list_item = (c_ipmi_sensor_list_t *)user_data;
121
122   if (err != 0)
123   {
124     if ((err & 0xff) == IPMI_NOT_PRESENT_CC)
125     {
126       if (list_item->sensor_not_present == 0)
127       {
128         list_item->sensor_not_present = 1;
129
130         INFO ("ipmi plugin: sensor_read_handler: sensor %s "
131             "not present.", list_item->sensor_name);
132
133         if (c_ipmi_nofiy_notpresent)
134         {
135           notification_t n = { NOTIF_WARNING, time(NULL), "", "", "ipmi",
136             "", "", "", NULL };
137
138           sstrncpy (n.host, hostname_g, sizeof (n.host));
139           sstrncpy (n.type_instance, list_item->sensor_name,
140               sizeof (n.type_instance));
141           sstrncpy (n.type, list_item->sensor_type, sizeof (n.type));
142           ssnprintf (n.message, sizeof (n.message),
143               "sensor %s not present", list_item->sensor_name);
144
145           plugin_dispatch_notification (&n);
146         }
147       }
148     }
149     else
150     {
151       INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
152           "because it failed with status %#x.",
153           list_item->sensor_name, err);
154       sensor_list_remove (sensor);
155     }
156     return;
157   }
158   else if (list_item->sensor_not_present == 1)
159   {
160     list_item->sensor_not_present = 0;
161
162     INFO ("ipmi plugin: sensor_read_handler: sensor %s present.",
163         list_item->sensor_name);
164
165     if (c_ipmi_nofiy_notpresent)
166     {
167       notification_t n = { NOTIF_OKAY, time(NULL), "", "", "ipmi",
168         "", "", "", NULL };
169
170       sstrncpy (n.host, hostname_g, sizeof (n.host));
171       sstrncpy (n.type_instance, list_item->sensor_name,
172           sizeof (n.type_instance));
173       sstrncpy (n.type, list_item->sensor_type, sizeof (n.type));
174       ssnprintf (n.message, sizeof (n.message),
175           "sensor %s present", list_item->sensor_name);
176
177       plugin_dispatch_notification (&n);
178     }
179   }
180
181   if (value_present != IPMI_BOTH_VALUES_PRESENT)
182   {
183     INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
184         "because it provides %s. If you need this sensor, "
185         "please file a bug report.",
186         list_item->sensor_name,
187         (value_present == IPMI_RAW_VALUE_PRESENT)
188         ? "only the raw value"
189         : "no value");
190     sensor_list_remove (sensor);
191     return;
192   }
193
194   values[0].gauge = value;
195
196   vl.values = values;
197   vl.values_len = 1;
198   vl.time = time (NULL);
199
200   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
201   sstrncpy (vl.plugin, "ipmi", sizeof (vl.plugin));
202   sstrncpy (vl.type, list_item->sensor_type, sizeof (vl.type));
203   sstrncpy (vl.type_instance, list_item->sensor_name, sizeof (vl.type_instance));
204
205   plugin_dispatch_values (&vl);
206 } /* void sensor_read_handler */
207
208 static int sensor_list_add (ipmi_sensor_t *sensor)
209 {
210   ipmi_sensor_id_t sensor_id;
211   c_ipmi_sensor_list_t *list_item;
212   c_ipmi_sensor_list_t *list_prev;
213
214   char sensor_name[DATA_MAX_NAME_LEN];
215   char *sensor_name_ptr;
216   int sensor_type, len;
217   const char *type;
218   ipmi_entity_t *ent = ipmi_sensor_get_entity(sensor);
219
220   sensor_id = ipmi_sensor_convert_to_id (sensor);
221
222   memset (sensor_name, 0, sizeof (sensor_name));
223   ipmi_sensor_get_name (sensor, sensor_name, sizeof (sensor_name));
224   sensor_name[sizeof (sensor_name) - 1] = 0;
225
226   len = DATA_MAX_NAME_LEN - strlen(sensor_name);
227   strncat(sensor_name, " ", len--);
228   strncat(sensor_name, ipmi_entity_get_entity_id_string(ent), len);
229
230   sensor_name_ptr = strstr (sensor_name, ").");
231   if (sensor_name_ptr == NULL)
232     sensor_name_ptr = sensor_name;
233   else
234   {
235     char *sensor_name_ptr_id = strstr (sensor_name, "(");
236
237     sensor_name_ptr += 2;
238     len = DATA_MAX_NAME_LEN - strlen(sensor_name);
239     strncat(sensor_name, " ", len--);
240     strncat(sensor_name, sensor_name_ptr_id, 
241       MIN(sensor_name_ptr - sensor_name_ptr_id - 1, len));
242   }
243
244   /* Both `ignorelist' and `plugin_instance' may be NULL. */
245   if (ignorelist_match (ignorelist, sensor_name_ptr) != 0)
246     return (0);
247
248   /* FIXME: Use rate unit or base unit to scale the value */
249
250   sensor_type = ipmi_sensor_get_sensor_type (sensor);
251   switch (sensor_type)
252   {
253     case IPMI_SENSOR_TYPE_TEMPERATURE:
254       type = "temperature";
255       break;
256
257     case IPMI_SENSOR_TYPE_VOLTAGE:
258       type = "voltage";
259       break;
260
261     case IPMI_SENSOR_TYPE_CURRENT:
262       type = "current";
263       break;
264
265     case IPMI_SENSOR_TYPE_FAN:
266       type = "fanspeed";
267       break;
268
269     default:
270       {
271         const char *sensor_type_str;
272
273         sensor_type_str = ipmi_sensor_get_sensor_type_string (sensor);
274         INFO ("ipmi plugin: sensor_list_add: Ignore sensor %s, "
275             "because I don't know how to handle its type (%#x, %s). "
276             "If you need this sensor, please file a bug report.",
277             sensor_name_ptr, sensor_type, sensor_type_str);
278         return (-1);
279       }
280   } /* switch (sensor_type) */
281
282   pthread_mutex_lock (&sensor_list_lock);
283
284   list_prev = NULL;
285   for (list_item = sensor_list;
286       list_item != NULL;
287       list_item = list_item->next)
288   {
289     if (ipmi_cmp_sensor_id (sensor_id, list_item->sensor_id) == 0)
290       break;
291     list_prev = list_item;
292   } /* for (list_item) */
293
294   if (list_item != NULL)
295   {
296     pthread_mutex_unlock (&sensor_list_lock);
297     return (0);
298   }
299
300   list_item = (c_ipmi_sensor_list_t *) calloc (1, sizeof (c_ipmi_sensor_list_t));
301   if (list_item == NULL)
302   {
303     pthread_mutex_unlock (&sensor_list_lock);
304     return (-1);
305   }
306
307   list_item->sensor_id = ipmi_sensor_convert_to_id (sensor);
308
309   if (list_prev != NULL)
310     list_prev->next = list_item;
311   else
312     sensor_list = list_item;
313
314   sstrncpy (list_item->sensor_name, sensor_name_ptr,
315             sizeof (list_item->sensor_name));
316   sstrncpy (list_item->sensor_type, type, sizeof (list_item->sensor_type));
317
318   pthread_mutex_unlock (&sensor_list_lock);
319
320   if (c_ipmi_nofiy_add && (c_ipmi_init_in_progress == 0))
321   {
322     notification_t n = { NOTIF_OKAY, time(NULL), "", "", "ipmi",
323                          "", "", "", NULL };
324
325     sstrncpy (n.host, hostname_g, sizeof (n.host));
326     sstrncpy (n.type_instance, list_item->sensor_name,
327               sizeof (n.type_instance));
328     sstrncpy (n.type, list_item->sensor_type, sizeof (n.type));
329     ssnprintf (n.message, sizeof (n.message),
330               "sensor %s added", list_item->sensor_name);
331
332     plugin_dispatch_notification (&n);
333   }
334
335   return (0);
336 } /* int sensor_list_add */
337
338 static int sensor_list_remove (ipmi_sensor_t *sensor)
339 {
340   ipmi_sensor_id_t sensor_id;
341   c_ipmi_sensor_list_t *list_item;
342   c_ipmi_sensor_list_t *list_prev;
343
344   sensor_id = ipmi_sensor_convert_to_id (sensor);
345
346   pthread_mutex_lock (&sensor_list_lock);
347
348   list_prev = NULL;
349   for (list_item = sensor_list;
350       list_item != NULL;
351       list_item = list_item->next)
352   {
353     if (ipmi_cmp_sensor_id (sensor_id, list_item->sensor_id) == 0)
354       break;
355     list_prev = list_item;
356   } /* for (list_item) */
357
358   if (list_item == NULL)
359   {
360     pthread_mutex_unlock (&sensor_list_lock);
361     return (-1);
362   }
363
364   if (list_prev == NULL)
365     sensor_list = list_item->next;
366   else
367     list_prev->next = list_item->next;
368
369   list_prev = NULL;
370   list_item->next = NULL;
371
372   pthread_mutex_unlock (&sensor_list_lock);
373
374   if (c_ipmi_nofiy_remove && c_ipmi_active)
375   {
376     notification_t n = { NOTIF_WARNING, time(NULL), "", "",
377                          "ipmi", "", "", "", NULL };
378
379     sstrncpy (n.host, hostname_g, sizeof (n.host));
380     sstrncpy (n.type_instance, list_item->sensor_name,
381               sizeof (n.type_instance));
382     sstrncpy (n.type, list_item->sensor_type, sizeof (n.type));
383     ssnprintf (n.message, sizeof (n.message),
384               "sensor %s removed", list_item->sensor_name);
385
386     plugin_dispatch_notification (&n);
387   }
388
389   free (list_item);
390   return (0);
391 } /* int sensor_list_remove */
392
393 static int sensor_list_read_all (void)
394 {
395   c_ipmi_sensor_list_t *list_item;
396
397   pthread_mutex_lock (&sensor_list_lock);
398
399   for (list_item = sensor_list;
400       list_item != NULL;
401       list_item = list_item->next)
402   {
403     ipmi_sensor_id_get_reading (list_item->sensor_id,
404         sensor_read_handler, /* user data = */ list_item);
405   } /* for (list_item) */
406
407   pthread_mutex_unlock (&sensor_list_lock);
408
409   return (0);
410 } /* int sensor_list_read_all */
411
412 static int sensor_list_remove_all (void)
413 {
414   c_ipmi_sensor_list_t *list_item;
415
416   pthread_mutex_lock (&sensor_list_lock);
417
418   list_item = sensor_list;
419   sensor_list = NULL;
420
421   pthread_mutex_unlock (&sensor_list_lock);
422
423   while (list_item != NULL)
424   {
425     c_ipmi_sensor_list_t *list_next = list_item->next;
426
427     free (list_item);
428
429     list_item = list_next;
430   } /* while (list_item) */
431
432   return (0);
433 } /* int sensor_list_remove_all */
434
435 /*
436  * Entity handlers
437  */
438 static void entity_sensor_update_handler (enum ipmi_update_e op,
439     ipmi_entity_t *entity,
440     ipmi_sensor_t *sensor,
441     void *user_data)
442 {
443   /* TODO: Ignore sensors we cannot read */
444
445   if ((op == IPMI_ADDED) || (op == IPMI_CHANGED))
446   {
447     /* Will check for duplicate entries.. */
448     sensor_list_add (sensor);
449   }
450   else if (op == IPMI_DELETED)
451   {
452     sensor_list_remove (sensor);
453   }
454 } /* void entity_sensor_update_handler */
455
456 /*
457  * Domain handlers
458  */
459 static void domain_entity_update_handler (enum ipmi_update_e op,
460     ipmi_domain_t *domain,
461     ipmi_entity_t *entity,
462     void *user_data)
463 {
464   int status;
465
466   if (op == IPMI_ADDED)
467   {
468     status = ipmi_entity_add_sensor_update_handler (entity,
469         entity_sensor_update_handler, /* user data = */ NULL);
470     if (status != 0)
471     {
472       c_ipmi_error ("ipmi_entity_add_sensor_update_handler", status);
473     }
474   }
475   else if (op == IPMI_DELETED)
476   {
477     status = ipmi_entity_remove_sensor_update_handler (entity,
478         entity_sensor_update_handler, /* user data = */ NULL);
479     if (status != 0)
480     {
481       c_ipmi_error ("ipmi_entity_remove_sensor_update_handler", status);
482     }
483   }
484 } /* void domain_entity_update_handler */
485
486 static void domain_connection_change_handler (ipmi_domain_t *domain,
487     int err,
488     unsigned int conn_num,
489     unsigned int port_num,
490     int still_connected,
491     void *user_data)
492 {
493   int status;
494
495   printf ("domain_connection_change_handler (domain = %p, err = %i, "
496       "conn_num = %u, port_num = %u, still_connected = %i, "
497       "user_data = %p);\n",
498       (void *) domain, err, conn_num, port_num, still_connected, user_data);
499
500   status = ipmi_domain_add_entity_update_handler (domain,
501       domain_entity_update_handler, /* user data = */ NULL);
502   if (status != 0)
503   {
504     c_ipmi_error ("ipmi_domain_add_entity_update_handler", status);
505   }
506 } /* void domain_connection_change_handler */
507
508 static int thread_init (os_handler_t **ret_os_handler)
509 {
510   os_handler_t *os_handler;
511   ipmi_open_option_t open_option[1];
512   ipmi_con_t *smi_connection = NULL;
513   ipmi_domain_id_t domain_id;
514   int status;
515
516   os_handler = ipmi_posix_thread_setup_os_handler (SIGUSR2);
517   if (os_handler == NULL)
518   {
519     ERROR ("ipmi plugin: ipmi_posix_thread_setup_os_handler failed.");
520     return (-1);
521   }
522
523   ipmi_init (os_handler);
524
525   status = ipmi_smi_setup_con (/* if_num = */ 0,
526       os_handler,
527       /* user data = */ NULL,
528       &smi_connection);
529   if (status != 0)
530   {
531     c_ipmi_error ("ipmi_smi_setup_con", status);
532     return (-1);
533   }
534
535   memset (open_option, 0, sizeof (open_option));
536   open_option[0].option = IPMI_OPEN_OPTION_ALL;
537   open_option[0].ival = 1;
538
539   status = ipmi_open_domain ("mydomain", &smi_connection, /* num_con = */ 1,
540       domain_connection_change_handler, /* user data = */ NULL,
541       /* domain_fully_up_handler = */ NULL, /* user data = */ NULL,
542       open_option, sizeof (open_option) / sizeof (open_option[0]),
543       &domain_id);
544   if (status != 0)
545   {
546     c_ipmi_error ("ipmi_open_domain", status);
547     return (-1);
548   }
549
550   *ret_os_handler = os_handler;
551   return (0);
552 } /* int thread_init */
553
554 static void *thread_main (void *user_data)
555 {
556   int status;
557   os_handler_t *os_handler = NULL;
558
559   status = thread_init (&os_handler);
560   if (status != 0)
561   {
562     fprintf (stderr, "ipmi plugin: thread_init failed.\n");
563     return ((void *) -1);
564   }
565
566   while (c_ipmi_active != 0)
567   {
568     struct timeval tv = { 1, 0 };
569     os_handler->perform_one_op (os_handler, &tv);
570   }
571
572   ipmi_posix_thread_free_os_handler (os_handler);
573
574   return ((void *) 0);
575 } /* void *thread_main */
576
577 static int c_ipmi_config (const char *key, const char *value)
578 {
579   if (ignorelist == NULL)
580     ignorelist = ignorelist_create (/* invert = */ 1);
581   if (ignorelist == NULL)
582     return (1);
583
584   if (strcasecmp ("Sensor", key) == 0)
585   {
586     ignorelist_add (ignorelist, value);
587   }
588   else if (strcasecmp ("IgnoreSelected", key) == 0)
589   {
590     int invert = 1;
591     if ((strcasecmp ("True", value) == 0)
592         || (strcasecmp ("Yes", value) == 0)
593         || (strcasecmp ("On", value) == 0))
594       invert = 0;
595     ignorelist_set_invert (ignorelist, invert);
596   }
597   else if (strcasecmp ("NotifySensorAdd", key) == 0)
598   {
599     if ((strcasecmp ("True", value) == 0)
600         || (strcasecmp ("Yes", value) == 0)
601         || (strcasecmp ("On", value) == 0))
602       c_ipmi_nofiy_add = 1;
603   }
604   else if (strcasecmp ("NotifySensorRemove", key) == 0)
605   {
606     if ((strcasecmp ("True", value) == 0)
607         || (strcasecmp ("Yes", value) == 0)
608         || (strcasecmp ("On", value) == 0))
609       c_ipmi_nofiy_remove = 1;
610   }
611   else if (strcasecmp ("NotifySensorNotPresent", key) == 0)
612   {
613     if ((strcasecmp ("True", value) == 0)
614         || (strcasecmp ("Yes", value) == 0)
615         || (strcasecmp ("On", value) == 0))
616       c_ipmi_nofiy_notpresent = 1;
617   }
618   else
619   {
620     return (-1);
621   }
622
623   return (0);
624 } /* int c_ipmi_config */
625
626 static int c_ipmi_init (void)
627 {
628   int status;
629
630   /* Don't send `ADD' notifications during startup (~ 1 minute) */
631   c_ipmi_init_in_progress = 1 + (60 / interval_g);
632
633   c_ipmi_active = 1;
634
635   status = pthread_create (&thread_id, /* attr = */ NULL, thread_main,
636       /* user data = */ NULL);
637   if (status != 0)
638   {
639     c_ipmi_active = 0;
640     thread_id = (pthread_t) 0;
641     ERROR ("ipmi plugin: pthread_create failed.");
642     return (-1);
643   }
644
645   return (0);
646 } /* int c_ipmi_init */
647
648 static int c_ipmi_read (void)
649 {
650   if ((c_ipmi_active == 0) || (thread_id == (pthread_t) 0))
651   {
652     INFO ("ipmi plugin: c_ipmi_read: I'm not active, returning false.");
653     return (-1);
654   }
655
656   sensor_list_read_all ();
657
658   if (c_ipmi_init_in_progress > 0)
659     c_ipmi_init_in_progress--;
660   else
661     c_ipmi_init_in_progress = 0;
662
663   return (0);
664 } /* int c_ipmi_read */
665
666 static int c_ipmi_shutdown (void)
667 {
668   c_ipmi_active = 0;
669
670   if (thread_id != (pthread_t) 0)
671   {
672     pthread_join (thread_id, NULL);
673     thread_id = (pthread_t) 0;
674   }
675
676   sensor_list_remove_all ();
677
678   return (0);
679 } /* int c_ipmi_shutdown */
680
681 void module_register (void)
682 {
683   plugin_register_config ("ipmi", c_ipmi_config,
684       config_keys, config_keys_num);
685   plugin_register_init ("ipmi", c_ipmi_init);
686   plugin_register_read ("ipmi", c_ipmi_read);
687   plugin_register_shutdown ("ipmi", c_ipmi_shutdown);
688 } /* void module_register */
689
690 /* vim: set sw=2 sts=2 ts=8 fdm=marker et : */