ipmi plugin: Add a plugin to read sensor values via IPMI.
authorFlorian Forster <octo@noris.net>
Wed, 2 Apr 2008 15:49:44 +0000 (17:49 +0200)
committerFlorian Forster <octo@noris.net>
Wed, 2 Apr 2008 15:49:44 +0000 (17:49 +0200)
The plugin is very basic and still basically alpha quality. You cannot
configure anything yet, neither where to connect to (the local, SMI connection
is used right now) nor what sensors to collect (collects all it can make sense
of). I hope to be able to do that on friday or some other workday in the near
future.

configure.in
src/Makefile.am
src/ipmi.c [new file with mode: 0644]

index 113cb16..ae4c550 100644 (file)
@@ -29,6 +29,7 @@ AC_LIBTOOL_DLOPEN
 AC_PROG_LIBTOOL
 AC_PROG_LEX
 AC_PROG_YACC
+PKG_PROG_PKG_CONFIG
 AC_CONFIG_SUBDIRS(libltdl)
 
 AC_MSG_CHECKING([for kernel type ($host_os)])
@@ -1812,6 +1813,85 @@ then
 fi
 AM_CONDITIONAL(BUILD_WITH_LIBNETLINK, test "x$with_libnetlink" = "xyes")
 
+with_libopenipmipthread="yes"
+with_libopenipmipthread_cflags=""
+with_libopenipmipthread_libs=""
+
+AC_MSG_CHECKING([for pkg-config])
+temp_result="no"
+if test "x$PKG_CONFIG" = "x"
+then
+       with_libopenipmipthread="no"
+       temp_result="no"
+else
+       temp_result="$PKG_CONFIG"
+fi
+AC_MSG_RESULT([$temp_result])
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       AC_MSG_CHECKING([for libOpenIPMIpthread])
+       $PKG_CONFIG --exists OpenIPMIpthread 2>/dev/null
+       if test "$?" != "0"
+       then
+               with_libopenipmipthread="no ($PKG_CONFIG doesn't know OpenIPMIpthread)"
+       fi
+       AC_MSG_RESULT([$with_libopenipmipthread])
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       AC_MSG_CHECKING([for libOpenIPMIpthread CFLAGS])
+       temp_result="`$PKG_CONFIG --cflags OpenIPMIpthread`"
+       if test "$?" = "0"
+       then
+               with_libopenipmipthread_cflags="$temp_result"
+       else
+               with_libopenipmipthread="no ($PKG_CONFIG --cflags OpenIPMIpthread failed)"
+               temp_result="$PKG_CONFIG --cflags OpenIPMIpthread failed"
+       fi
+       AC_MSG_RESULT([$temp_result])
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       AC_MSG_CHECKING([for libOpenIPMIpthread LDFLAGS])
+       temp_result="`$PKG_CONFIG --libs OpenIPMIpthread`"
+       if test "$?" = "0"
+       then
+               with_libopenipmipthread_ldflags="$temp_result"
+       else
+               with_libopenipmipthread="no ($PKG_CONFIG --libs OpenIPMIpthread failed)"
+               temp_result="$PKG_CONFIG --libs OpenIPMIpthread failed"
+       fi
+       AC_MSG_RESULT([$temp_result])
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libopenipmipthread_cflags"
+
+       AC_CHECK_HEADERS(OpenIPMI/ipmi_smi.h,
+                        [with_libopenipmipthread="yes"],
+                        [with_libopenipmipthread="no (OpenIPMI/ipmi_smi.h not found)"],
+[#include <OpenIPMI/ipmiif.h>
+#include <OpenIPMI/ipmi_err.h>
+#include <OpenIPMI/ipmi_posix.h>
+#include <OpenIPMI/ipmi_conn.h>
+])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       BUILD_WITH_OPENIPMI_CFLAGS="$with_libopenipmipthread_cflags"
+       BUILD_WITH_OPENIPMI_LIBS="$with_libopenipmipthread_ldflags"
+       AC_SUBST(BUILD_WITH_OPENIPMI_CFLAGS)
+       AC_SUBST(BUILD_WITH_OPENIPMI_LIBS)
+fi
+
 dnl Check for libvirt and libxml2 libraries.
 with_libxml2="no (pkg-config isn't available)"
 with_libxml2_cflags=""
@@ -1819,7 +1899,6 @@ with_libxml2_ldflags=""
 with_libvirt="no (pkg-config isn't available)"
 with_libvirt_cflags=""
 with_libvirt_ldflags=""
-PKG_PROG_PKG_CONFIG
 if test "x$PKG_CONFIG" != "x"
 then
        pkg-config --exists 'libxml-2.0' 2>/dev/null
@@ -2041,6 +2120,7 @@ plugin_df="no"
 plugin_disk="no"
 plugin_entropy="no"
 plugin_interface="no"
+plugin_ipmi="no"
 plugin_ipvs="no"
 plugin_irq="no"
 plugin_libvirt="no"
@@ -2113,6 +2193,11 @@ then
        plugin_swap="yes"
 fi
 
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       plugin_ipmi="yes"
+fi
+
 if test "x$have_processor_info" = "xyes"
 then
        plugin_cpu="yes"
@@ -2204,6 +2289,7 @@ AC_PLUGIN([exec],        [yes],                [Execution of external programs])
 AC_PLUGIN([hddtemp],     [yes],                [Query hddtempd])
 AC_PLUGIN([interface],   [$plugin_interface],  [Interface traffic statistics])
 AC_PLUGIN([iptables],    [$with_libiptc],      [IPTables rule counters])
+AC_PLUGIN([ipmi],        [$plugin_ipmi],       [IPMI sensor statistics])
 AC_PLUGIN([ipvs],        [$plugin_ipvs],       [IPVS connection statistics])
 AC_PLUGIN([irq],         [$plugin_irq],        [IRQ statistics])
 AC_PLUGIN([libvirt],     [$plugin_libvirt],    [Virtual machine statistics])
@@ -2345,6 +2431,7 @@ Configuration:
     hddtemp . . . . . . $enable_hddtemp
     interface . . . . . $enable_interface
     iptables  . . . . . $enable_iptables
+    ipmi  . . . . . . . $enable_ipmi
     ipvs  . . . . . . . $enable_ipvs
     irq . . . . . . . . $enable_irq
     libvirt . . . . . . $enable_libvirt
index afa9976..5114d45 100644 (file)
@@ -272,6 +272,16 @@ collectd_LDADD += "-dlopen" iptables.la
 collectd_DEPENDENCIES += iptables.la
 endif
 
+if BUILD_PLUGIN_IPMI
+pkglib_LTLIBRARIES += ipmi.la
+ipmi_la_SOURCES = ipmi.c
+ipmi_la_CFLAGS = $(BUILD_WITH_OPENIPMI_CFLAGS)
+ipmi_la_LDFLAGS = -module -avoid-version
+ipmi_la_LIBADD = $(BUILD_WITH_OPENIPMI_LIBS)
+collectd_LDADD += "-dlopen" ipmi.la
+collectd_DEPENDENCIES += ipmi.la
+endif
+
 if BUILD_PLUGIN_IPVS
 pkglib_LTLIBRARIES += ipvs.la
 ipvs_la_SOURCES = ipvs.c
diff --git a/src/ipmi.c b/src/ipmi.c
new file mode 100644 (file)
index 0000000..5e4a6ad
--- /dev/null
@@ -0,0 +1,506 @@
+/**
+ * collectd - src/ipmi.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include <pthread.h>
+
+#include <OpenIPMI/ipmiif.h>
+#include <OpenIPMI/ipmi_err.h>
+#include <OpenIPMI/ipmi_posix.h>
+#include <OpenIPMI/ipmi_conn.h>
+#include <OpenIPMI/ipmi_smi.h>
+
+/*
+ * Private data types
+ */
+struct c_ipmi_sensor_list_s;
+typedef struct c_ipmi_sensor_list_s c_ipmi_sensor_list_t;
+
+struct c_ipmi_sensor_list_s
+{
+  ipmi_sensor_id_t sensor_id;
+  c_ipmi_sensor_list_t *next;
+};
+
+/*
+ * Module global variables
+ */
+static pthread_mutex_t sensor_list_lock = PTHREAD_MUTEX_INITIALIZER;
+static c_ipmi_sensor_list_t *sensor_list = NULL;
+
+static int c_ipmi_active = 0;
+static pthread_t thread_id = (pthread_t) 0;
+
+/*
+ * Misc private functions
+ */
+static void c_ipmi_error (const char *func, int status)
+{
+  char errbuf[4096];
+
+  memset (errbuf, 0, sizeof (errbuf));
+
+  if (IPMI_IS_OS_ERR (status))
+  {
+    sstrerror_r (IPMI_GET_OS_ERR (status), errbuf, sizeof (errbuf));
+  }
+  else if (IPMI_IS_IPMI_ERR (status))
+  {
+    ipmi_get_error_string (IPMI_GET_IPMI_ERR (status), errbuf, sizeof (errbuf));
+  }
+
+  if (errbuf[0] == 0)
+  {
+    ssnprintf (errbuf, sizeof (errbuf), "Unknown error %#x", status);
+  }
+  errbuf[sizeof (errbuf) - 1] = 0;
+
+  ERROR ("ipmi plugin: %s failed: %s", func, errbuf);
+} /* void c_ipmi_error */
+
+/*
+ * Sensor handlers
+ */
+/* Prototype for sensor_list_remove, so sensor_read_handler can call it. */
+static int sensor_list_remove (ipmi_sensor_t *sensor);
+
+static void sensor_read_handler (ipmi_sensor_t *sensor,
+    int err,
+    enum ipmi_value_present_e value_present,
+    unsigned int raw_value,
+    double value,
+    ipmi_states_t *states,
+    void *user_data)
+{
+  value_t values[1];
+  value_list_t vl = VALUE_LIST_INIT;
+
+  char sensor_name[IPMI_SENSOR_NAME_LEN];
+  char *sensor_name_ptr;
+  int sensor_type;
+  const char *type;
+
+  memset (sensor_name, 0, sizeof (sensor_name));
+  ipmi_sensor_get_name (sensor, sensor_name, sizeof (sensor_name));
+  sensor_name[sizeof (sensor_name) - 1] = 0;
+
+  sensor_name_ptr = strstr (sensor_name, ").");
+  if (sensor_name_ptr == NULL)
+    sensor_name_ptr = sensor_name;
+  else
+    sensor_name_ptr += 2;
+
+  if (err != 0)
+  {
+    INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+        "because it failed with status %#x. If you need this sensor, "
+        "please file a bug report.",
+        sensor_name_ptr, err);
+    sensor_list_remove (sensor);
+    return;
+  }
+
+  if (value_present != IPMI_RAW_VALUE_PRESENT)
+  {
+    INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+        "because it provides %s.",
+        sensor_name_ptr,
+        (value_present == IPMI_RAW_VALUE_PRESENT)
+        ? "only the raw value"
+        : "no value");
+    sensor_list_remove (sensor);
+    return;
+  }
+
+  /* FIXME: Use rate unit or base unit to scale the value */
+
+  sensor_type = ipmi_sensor_get_sensor_type (sensor);
+  switch (sensor_type)
+  {
+    case IPMI_SENSOR_TYPE_TEMPERATURE:
+      type = "temperature";
+      break;
+
+    case IPMI_SENSOR_TYPE_VOLTAGE:
+      type = "voltage";
+      break;
+
+    case IPMI_SENSOR_TYPE_CURRENT:
+      type = "current";
+      break;
+
+    case IPMI_SENSOR_TYPE_FAN:
+      type = "fanspeed";
+      break;
+
+    default:
+      {
+        const char *sensor_type_str;
+        
+        sensor_type_str = ipmi_sensor_get_sensor_type_string (sensor);
+        INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+            "because I don't know how to handle its type (%#x, %s).",
+            sensor_name_ptr, sensor_type, sensor_type_str);
+        sensor_list_remove (sensor);
+        return;
+      }
+  } /* switch (sensor_type) */
+
+  values[0].gauge = value;
+
+  vl.values = values;
+  vl.values_len = 1;
+  vl.time = time (NULL);
+
+  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+  sstrncpy (vl.plugin, "ipmi", sizeof (vl.plugin));
+  sstrncpy (vl.type_instance, sensor_name_ptr, sizeof (vl.type_instance));
+
+  plugin_dispatch_values (type, &vl);
+} /* void sensor_read_handler */
+
+static int sensor_list_add (ipmi_sensor_t *sensor)
+{
+  ipmi_sensor_id_t sensor_id;
+  c_ipmi_sensor_list_t *list_item;
+  c_ipmi_sensor_list_t *list_prev;
+
+  sensor_id = ipmi_sensor_convert_to_id (sensor);
+
+  pthread_mutex_lock (&sensor_list_lock);
+
+  list_prev = NULL;
+  for (list_item = sensor_list;
+      list_item != NULL;
+      list_item = list_item->next)
+  {
+    if (ipmi_cmp_sensor_id (sensor_id, list_item->sensor_id) == 0)
+      break;
+    list_prev = list_item;
+  } /* for (list_item) */
+
+  if (list_item != NULL)
+  {
+    pthread_mutex_unlock (&sensor_list_lock);
+    return (0);
+  }
+
+  list_item = (c_ipmi_sensor_list_t *) calloc (1, sizeof (c_ipmi_sensor_list_t));
+  if (list_item == NULL)
+  {
+    pthread_mutex_unlock (&sensor_list_lock);
+    return (-1);
+  }
+
+  list_item->sensor_id = ipmi_sensor_convert_to_id (sensor);
+
+  if (list_prev != NULL)
+    list_prev->next = list_item;
+  else
+    sensor_list = list_item;
+
+  pthread_mutex_unlock (&sensor_list_lock);
+
+  return (0);
+} /* int sensor_list_add */
+
+static int sensor_list_remove (ipmi_sensor_t *sensor)
+{
+  ipmi_sensor_id_t sensor_id;
+  c_ipmi_sensor_list_t *list_item;
+  c_ipmi_sensor_list_t *list_prev;
+
+  sensor_id = ipmi_sensor_convert_to_id (sensor);
+
+  pthread_mutex_lock (&sensor_list_lock);
+
+  list_prev = NULL;
+  for (list_item = sensor_list;
+      list_item != NULL;
+      list_item = list_item->next)
+  {
+    if (ipmi_cmp_sensor_id (sensor_id, list_item->sensor_id) == 0)
+      break;
+    list_prev = list_item;
+  } /* for (list_item) */
+
+  if (list_item == NULL)
+  {
+    pthread_mutex_unlock (&sensor_list_lock);
+    return (-1);
+  }
+
+  if (list_prev == NULL)
+    sensor_list = list_item->next;
+  else
+    list_prev->next = list_item->next;
+
+  list_prev = NULL;
+  list_item->next = NULL;
+
+  pthread_mutex_unlock (&sensor_list_lock);
+
+  free (list_item);
+  return (0);
+} /* int sensor_list_remove */
+
+static int sensor_list_read_all (void)
+{
+  c_ipmi_sensor_list_t *list_item;
+
+  pthread_mutex_lock (&sensor_list_lock);
+
+  for (list_item = sensor_list;
+      list_item != NULL;
+      list_item = list_item->next)
+  {
+    ipmi_sensor_id_get_reading (list_item->sensor_id,
+        sensor_read_handler, /* user data = */ NULL);
+  } /* for (list_item) */
+
+  pthread_mutex_unlock (&sensor_list_lock);
+
+  return (0);
+} /* int sensor_list_read_all */
+
+static int sensor_list_remove_all (void)
+{
+  c_ipmi_sensor_list_t *list_item;
+
+  pthread_mutex_lock (&sensor_list_lock);
+
+  list_item = sensor_list;
+  sensor_list = NULL;
+
+  pthread_mutex_unlock (&sensor_list_lock);
+
+  while (list_item != NULL)
+  {
+    c_ipmi_sensor_list_t *list_next = list_item->next;
+
+    free (list_item);
+
+    list_item = list_next;
+  } /* while (list_item) */
+
+  return (0);
+} /* int sensor_list_remove_all */
+
+/*
+ * Entity handlers
+ */
+static void entity_sensor_update_handler (enum ipmi_update_e op,
+    ipmi_entity_t *entity,
+    ipmi_sensor_t *sensor,
+    void *user_data)
+{
+  /* TODO: Ignore sensors we cannot read */
+
+  if ((op == IPMI_ADDED) || (op == IPMI_CHANGED))
+  {
+    /* Will check for duplicate entries.. */
+    sensor_list_add (sensor);
+  }
+  else if (op == IPMI_DELETED)
+  {
+    sensor_list_remove (sensor);
+  }
+} /* void entity_sensor_update_handler */
+
+/*
+ * Domain handlers
+ */
+static void domain_entity_update_handler (enum ipmi_update_e op,
+    ipmi_domain_t *domain,
+    ipmi_entity_t *entity,
+    void *user_data)
+{
+  int status;
+
+  if (op == IPMI_ADDED)
+  {
+    status = ipmi_entity_add_sensor_update_handler (entity,
+        entity_sensor_update_handler, /* user data = */ NULL);
+    if (status != 0)
+    {
+      c_ipmi_error ("ipmi_entity_add_sensor_update_handler", status);
+    }
+  }
+  else if (op == IPMI_DELETED)
+  {
+    status = ipmi_entity_remove_sensor_update_handler (entity,
+        entity_sensor_update_handler, /* user data = */ NULL);
+    if (status != 0)
+    {
+      c_ipmi_error ("ipmi_entity_remove_sensor_update_handler", status);
+    }
+  }
+} /* void domain_entity_update_handler */
+
+static void domain_connection_change_handler (ipmi_domain_t *domain,
+    int err,
+    unsigned int conn_num,
+    unsigned int port_num,
+    int still_connected,
+    void *user_data)
+{
+  int status;
+
+  printf ("domain_connection_change_handler (domain = %p, err = %i, "
+      "conn_num = %u, port_num = %u, still_connected = %i, "
+      "user_data = %p);\n",
+      (void *) domain, err, conn_num, port_num, still_connected, user_data);
+
+  status = ipmi_domain_add_entity_update_handler (domain,
+      domain_entity_update_handler, /* user data = */ NULL);
+  if (status != 0)
+  {
+    c_ipmi_error ("ipmi_domain_add_entity_update_handler", status);
+  }
+} /* void domain_connection_change_handler */
+
+static int thread_init (os_handler_t **ret_os_handler)
+{
+  os_handler_t *os_handler;
+  ipmi_open_option_t open_option[1];
+  ipmi_con_t *smi_connection = NULL;
+  ipmi_domain_id_t domain_id;
+  int status;
+
+  os_handler = ipmi_posix_thread_setup_os_handler (SIGUSR2);
+  if (os_handler == NULL)
+  {
+    ERROR ("ipmi plugin: ipmi_posix_thread_setup_os_handler failed.");
+    return (-1);
+  }
+
+  ipmi_init (os_handler);
+
+  status = ipmi_smi_setup_con (/* if_num = */ 0,
+      os_handler,
+      /* user data = */ NULL,
+      &smi_connection);
+  if (status != 0)
+  {
+    c_ipmi_error ("ipmi_smi_setup_con", status);
+    return (-1);
+  }
+
+  memset (open_option, 0, sizeof (open_option));
+  open_option[0].option = IPMI_OPEN_OPTION_ALL;
+  open_option[0].ival = 1;
+
+  status = ipmi_open_domain ("mydomain", &smi_connection, /* num_con = */ 1,
+      domain_connection_change_handler, /* user data = */ NULL,
+      /* domain_fully_up_handler = */ NULL, /* user data = */ NULL,
+      open_option, sizeof (open_option) / sizeof (open_option[0]),
+      &domain_id);
+  if (status != 0)
+  {
+    c_ipmi_error ("ipmi_open_domain", status);
+    return (-1);
+  }
+
+  *ret_os_handler = os_handler;
+  return (0);
+} /* int thread_init */
+
+static void *thread_main (void *user_data)
+{
+  int status;
+  os_handler_t *os_handler = NULL;
+
+  status = thread_init (&os_handler);
+  if (status != 0)
+  {
+    fprintf (stderr, "ipmi plugin: thread_init failed.\n");
+    return ((void *) -1);
+  }
+
+  while (c_ipmi_active != 0)
+  {
+    struct timeval tv = { 1, 0 };
+    os_handler->perform_one_op (os_handler, &tv);
+  }
+
+  ipmi_posix_thread_free_os_handler (os_handler);
+
+  return ((void *) 0);
+} /* void *thread_main */
+
+static int c_ipmi_init (void)
+{
+  int status;
+
+  c_ipmi_active = 1;
+
+  status = pthread_create (&thread_id, /* attr = */ NULL, thread_main,
+      /* user data = */ NULL);
+  if (status != 0)
+  {
+    c_ipmi_active = 0;
+    thread_id = (pthread_t) 0;
+    ERROR ("ipmi plugin: pthread_create failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* int c_ipmi_init */
+
+static int c_ipmi_read (void)
+{
+  if ((c_ipmi_active == 0) || (thread_id == (pthread_t) 0))
+  {
+    INFO ("ipmi plugin: c_ipmi_read: I'm not active, returning false.");
+    return (-1);
+  }
+
+  sensor_list_read_all ();
+  
+  return (0);
+} /* int c_ipmi_read */
+
+static int c_ipmi_shutdown (void)
+{
+  c_ipmi_active = 0;
+
+  if (thread_id != (pthread_t) 0)
+  {
+    pthread_join (thread_id, NULL);
+    thread_id = (pthread_t) 0;
+  }
+
+  sensor_list_remove_all ();
+
+  return (0);
+} /* int c_ipmi_shutdown */
+
+void module_register (void)
+{
+  plugin_register_init ("ipmi", c_ipmi_init);
+  plugin_register_read ("ipmi", c_ipmi_read);
+  plugin_register_shutdown ("ipmi", c_ipmi_shutdown);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 fdm=marker et : */