snmp plugin: Fix a segfault when a host times out but more data should be queried.
[collectd.git] / src / snmp.c
index 918e777..5cc1b2d 100644 (file)
@@ -23,6 +23,8 @@
 #include "common.h"
 #include "plugin.h"
 
+#include <pthread.h>
+
 #include <net-snmp/net-snmp-config.h>
 #include <net-snmp/net-snmp-includes.h>
 
@@ -61,11 +63,17 @@ struct host_definition_s
   char *address;
   char *community;
   int version;
-  struct snmp_session sess;
+  void *sess_handle;
   int16_t skip_num;
   int16_t skip_left;
   data_definition_t **data_list;
   int data_list_len;
+  enum          /****************************************************/
+  {             /* This host..                                      */
+    STATE_IDLE, /* - just sits there until `skip_left < interval_g' */
+    STATE_WAIT, /* - waits to be queried.                           */
+    STATE_BUSY  /* - is currently being queried.                    */
+  } state;      /****************************************************/
   struct host_definition_s *next;
 };
 typedef struct host_definition_s host_definition_t;
@@ -91,9 +99,17 @@ typedef struct csnmp_table_values_s csnmp_table_values_t;
 /*
  * Private variables
  */
+static int do_shutdown = 0;
+
+pthread_t *threads = NULL;
+int threads_num = 0;
+
 static data_definition_t *data_head = NULL;
 static host_definition_t *host_head = NULL;
 
+static pthread_mutex_t host_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t  host_cond = PTHREAD_COND_INITIALIZER;
+
 /*
  * Private functions
  */
@@ -339,8 +355,6 @@ static int csnmp_config_add_host_address (host_definition_t *hd, oconfig_item_t
   DEBUG ("snmp plugin: host = %s; host->address = %s;",
       hd->name, hd->address);
 
-  hd->sess.peername = hd->address;
-
   return (0);
 } /* int csnmp_config_add_host_address */
 
@@ -363,9 +377,6 @@ static int csnmp_config_add_host_community (host_definition_t *hd, oconfig_item_
   DEBUG ("snmp plugin: host = %s; host->community = %s;",
       hd->name, hd->community);
 
-  hd->sess.community = (u_char *) hd->community;
-  hd->sess.community_len = strlen (hd->community);
-
   return (0);
 } /* int csnmp_config_add_host_community */
 
@@ -389,11 +400,6 @@ static int csnmp_config_add_host_version (host_definition_t *hd, oconfig_item_t
 
   hd->version = version;
 
-  if (hd->version == 1)
-    hd->sess.version = SNMP_VERSION_1;
-  else
-    hd->sess.version = SNMP_VERSION_2c;
-
   return (0);
 } /* int csnmp_config_add_host_address */
 
@@ -492,11 +498,10 @@ static int csnmp_config_add_host (oconfig_item_t *ci)
     return (-1);
   }
 
-  snmp_sess_init (&hd->sess);
-  hd->sess.version = SNMP_VERSION_2c;
-
+  hd->sess_handle = NULL;
   hd->skip_num = 0;
   hd->skip_left = 0;
+  hd->state = STATE_IDLE;
 
   for (i = 0; i < ci->children_num; i++)
   {
@@ -587,49 +592,58 @@ static int csnmp_config (oconfig_item_t *ci)
   return (0);
 } /* int csnmp_config */
 
-static int csnmp_init (void)
+/* End of the config stuff. Now the interesting part begins */
+
+static void csnmp_host_close_session (host_definition_t *host)
 {
-  host_definition_t *host;
+  int status;
 
-  call_snmp_init_once ();
+  if (host->sess_handle == NULL)
+    return;
 
-  for (host = host_head; host != NULL; host = host->next)
+  status = snmp_sess_close (host->sess_handle);
+
+  if (status != 0)
   {
-    host->skip_left = interval_g;
-    if (host->skip_num == 0)
-    {
-      host->skip_num = interval_g;
-    }
-    else if (host->skip_num < interval_g)
-    {
-      host->skip_num = interval_g;
-      WARNING ("snmp plugin: Data for host `%s' will be collected every %i seconds.",
-         host->name, host->skip_num);
-    }
-  } /* for (host) */
+    char *errstr = NULL;
 
-  return (0);
-}
+    snmp_sess_error (host->sess_handle, NULL, NULL, &errstr);
+
+    ERROR ("snmp plugin: host %s: snmp_sess_close failed: %s",
+       host->name, (errstr == NULL) ? "Unknown problem" : errstr);
+    sfree (errstr);
+  }
 
-#if 0
-static void csnmp_submit (gauge_t snum, gauge_t mnum, gauge_t lnum)
+  host->sess_handle = NULL;
+} /* void csnmp_host_close_session */
+
+static void csnmp_host_open_session (host_definition_t *host)
 {
-  value_t values[3];
-  value_list_t vl = VALUE_LIST_INIT;
+  struct snmp_session sess;
 
-  values[0].gauge = snum;
-  values[1].gauge = mnum;
-  values[2].gauge = lnum;
+  if (host->sess_handle != NULL)
+    csnmp_host_close_session (host);
 
-  vl.values = values;
-  vl.values_len = STATIC_ARRAY_SIZE (values);
-  vl.time = time (NULL);
-  strcpy (vl.host, hostname_g);
-  strcpy (vl.plugin, "load");
+  snmp_sess_init (&sess);
+  sess.peername = host->address;
+  sess.community = (u_char *) host->community;
+  sess.community_len = strlen (host->community);
+  sess.version = (host->version == 1) ? SNMP_VERSION_1 : SNMP_VERSION_2c;
 
-  plugin_dispatch_values ("load", &vl);
-}
-#endif
+  /* snmp_sess_open will copy the `struct snmp_session *'. */
+  host->sess_handle = snmp_sess_open (&sess);
+
+  if (host->sess_handle == NULL)
+  {
+    char *errstr = NULL;
+
+    snmp_error (&sess, NULL, NULL, &errstr);
+
+    ERROR ("snmp plugin: host %s: snmp_sess_open failed: %s",
+       host->name, (errstr == NULL) ? "Unknown problem" : errstr);
+    sfree (errstr);
+  }
+} /* void csnmp_host_open_session */
 
 static value_t csnmp_value_list_to_value (struct variable_list *vl, int type)
 {
@@ -640,6 +654,9 @@ static value_t csnmp_value_list_to_value (struct variable_list *vl, int type)
   if ((vl->type == ASN_INTEGER)
       || (vl->type == ASN_UINTEGER)
       || (vl->type == ASN_COUNTER)
+#ifdef ASN_TIMETICKS
+      || (vl->type == ASN_TIMETICKS)
+#endif
       || (vl->type == ASN_GAUGE))
   {
     temp = (uint32_t) *vl->val.integer;
@@ -751,8 +768,7 @@ static int csnmp_dispatch_table (host_definition_t *host, data_definition_t *dat
   return (0);
 } /* int csnmp_dispatch_table */
 
-static int csnmp_read_table (struct snmp_session *sess_ptr,
-    host_definition_t *host, data_definition_t *data)
+static int csnmp_read_table (host_definition_t *host, data_definition_t *data)
 {
   struct snmp_pdu *req;
   struct snmp_pdu *res;
@@ -773,9 +789,15 @@ static int csnmp_read_table (struct snmp_session *sess_ptr,
   csnmp_table_values_t **value_table;
   csnmp_table_values_t **value_table_ptr;
 
-  DEBUG ("snmp plugin: csnmp_read_value (host = %s, data = %s)",
+  DEBUG ("snmp plugin: csnmp_read_table (host = %s, data = %s)",
       host->name, data->name);
 
+  if (host->sess_handle == NULL)
+  {
+    DEBUG ("snmp plugin: csnmp_read_table: host->sess_handle == NULL");
+    return (-1);
+  }
+
   ds = plugin_get_ds (data->type);
   if (!ds)
   {
@@ -829,11 +851,17 @@ static int csnmp_read_table (struct snmp_session *sess_ptr,
     for (i = 0; i < oid_list_len; i++)
       snmp_add_null_var (req, oid_list[i].oid, oid_list[i].oid_len);
 
-    status = snmp_synch_response (sess_ptr, req, &res);
+    status = snmp_sess_synch_response (host->sess_handle, req, &res);
 
     if (status != STAT_SUCCESS)
     {
-      ERROR ("snmp plugin: snmp_synch_response failed.");
+      char *errstr = NULL;
+
+      snmp_sess_error (host->sess_handle, NULL, NULL, &errstr);
+      ERROR ("snmp plugin: host %s: snmp_sess_synch_response failed: %s",
+         host->name, (errstr == NULL) ? "Unknown problem" : errstr);
+      csnmp_host_close_session (host);
+
       status = -1;
       break;
     }
@@ -991,8 +1019,7 @@ static int csnmp_read_table (struct snmp_session *sess_ptr,
   return (0);
 } /* int csnmp_read_table */
 
-static int csnmp_read_value (struct snmp_session *sess_ptr,
-    host_definition_t *host, data_definition_t *data)
+static int csnmp_read_value (host_definition_t *host, data_definition_t *data)
 {
   struct snmp_pdu *req;
   struct snmp_pdu *res;
@@ -1007,6 +1034,12 @@ static int csnmp_read_value (struct snmp_session *sess_ptr,
   DEBUG ("snmp plugin: csnmp_read_value (host = %s, data = %s)",
       host->name, data->name);
 
+  if (host->sess_handle == NULL)
+  {
+    DEBUG ("snmp plugin: csnmp_read_table: host->sess_handle == NULL");
+    return (-1);
+  }
+
   ds = plugin_get_ds (data->type);
   if (!ds)
   {
@@ -1051,12 +1084,18 @@ static int csnmp_read_value (struct snmp_session *sess_ptr,
 
   for (i = 0; i < data->values_len; i++)
     snmp_add_null_var (req, data->values[i].oid, data->values[i].oid_len);
-  status = snmp_synch_response (sess_ptr, req, &res);
+  status = snmp_sess_synch_response (host->sess_handle, req, &res);
 
   if (status != STAT_SUCCESS)
   {
-    ERROR ("snmp plugin: snmp_synch_response failed.");
-    sfree (vl.values);
+    char *errstr = NULL;
+
+    snmp_sess_error (host->sess_handle, NULL, NULL, &errstr);
+    ERROR ("snmp plugin: host %s: snmp_sess_synch_response failed: %s",
+       host->name, (errstr == NULL) ? "Unknown problem" : errstr);
+    csnmp_host_close_session (host);
+    sfree (errstr);
+
     return (-1);
   }
 
@@ -1086,33 +1125,108 @@ static int csnmp_read_value (struct snmp_session *sess_ptr,
 
 static int csnmp_read_host (host_definition_t *host)
 {
-  struct snmp_session *sess_ptr;
   int i;
 
   DEBUG ("snmp plugin: csnmp_read_host (%s);", host->name);
 
-  sess_ptr = snmp_open (&host->sess);
-  if (sess_ptr == NULL)
-  {
-    snmp_perror ("snmp_open");
-    ERROR ("snmp plugin: snmp_open failed.");
+  if (host->sess_handle == NULL)
+    csnmp_host_open_session (host);
+
+  if (host->sess_handle == NULL)
     return (-1);
-  }
 
   for (i = 0; i < host->data_list_len; i++)
   {
     data_definition_t *data = host->data_list[i];
 
     if (data->is_table)
-      csnmp_read_table (sess_ptr, host, data);
+      csnmp_read_table (host, data);
     else
-      csnmp_read_value (sess_ptr, host, data);
+      csnmp_read_value (host, data);
   }
 
-  snmp_close (sess_ptr);
   return (0);
 } /* int csnmp_read_host */
 
+static void *csnmp_read_thread (void *data)
+{
+  host_definition_t *host;
+
+  pthread_mutex_lock (&host_lock);
+  while (do_shutdown == 0)
+  {
+    pthread_cond_wait (&host_cond, &host_lock);
+
+    for (host = host_head; host != NULL; host = host->next)
+    {
+      if (do_shutdown != 0)
+       break;
+      if (host->state != STATE_WAIT)
+       continue;
+
+      host->state = STATE_BUSY;
+      pthread_mutex_unlock (&host_lock);
+      csnmp_read_host (host);
+      pthread_mutex_lock (&host_lock);
+      host->state = STATE_IDLE;
+    } /* for (host) */
+  } /* while (do_shutdown == 0) */
+  pthread_mutex_unlock (&host_lock);
+
+  pthread_exit ((void *) 0);
+  return ((void *) 0);
+} /* void *csnmp_read_thread */
+
+static int csnmp_init (void)
+{
+  host_definition_t *host;
+  int i;
+
+  if (host_head == NULL)
+    return (-1);
+
+  call_snmp_init_once ();
+
+  threads_num = 0;
+  for (host = host_head; host != NULL; host = host->next)
+  {
+    threads_num++;
+    /* We need to initialize `skip_num' here, because `interval_g' isn't
+     * initialized during `configure'. */
+    host->skip_left = interval_g;
+    if (host->skip_num == 0)
+    {
+      host->skip_num = interval_g;
+    }
+    else if (host->skip_num < interval_g)
+    {
+      host->skip_num = interval_g;
+      WARNING ("snmp plugin: Data for host `%s' will be collected every %i seconds.",
+         host->name, host->skip_num);
+    }
+
+    csnmp_host_open_session (host);
+  } /* for (host) */
+
+  /* Now start the reading threads */
+  if (threads_num > 3)
+  {
+    threads_num = 3 + ((threads_num - 3) / 10);
+    if (threads_num > 10)
+      threads_num = 10;
+  }
+
+  threads = (pthread_t *) malloc (threads_num * sizeof (pthread_t));
+  if (threads == NULL)
+    return (-1);
+  memset (threads, '\0', threads_num * sizeof (pthread_t));
+
+  for (i = 0; i < threads_num; i++)
+      pthread_create (threads + i, NULL, csnmp_read_thread, (void *) 0);
+
+  return (0);
+} /* int csnmp_init */
+
 static int csnmp_read (void)
 {
   host_definition_t *host;
@@ -1126,25 +1240,88 @@ static int csnmp_read (void)
 
   now = time (NULL);
 
+  pthread_mutex_lock (&host_lock);
   for (host = host_head; host != NULL; host = host->next)
   {
+    if (host->state != STATE_IDLE)
+      continue;
+
     host->skip_left -= interval_g;
     if (host->skip_left >= interval_g)
       continue;
 
-    csnmp_read_host (host);
+    host->state = STATE_WAIT;
 
     host->skip_left = host->skip_num;
   } /* for (host) */
 
+  pthread_cond_broadcast (&host_cond);
+  pthread_mutex_unlock (&host_lock);
+
   return (0);
 } /* int csnmp_read */
 
+static int csnmp_shutdown (void)
+{
+  host_definition_t *host_this;
+  host_definition_t *host_next;
+
+  data_definition_t *data_this;
+  data_definition_t *data_next;
+
+  int i;
+
+  pthread_mutex_lock (&host_lock);
+  do_shutdown = 1;
+  pthread_cond_broadcast (&host_cond);
+  pthread_mutex_unlock (&host_lock);
+
+  for (i = 0; i < threads_num; i++)
+    pthread_join (threads[i], NULL);
+
+  /* Now that all the threads have exited, let's free all the global variables.
+   * This isn't really neccessary, I guess, but I think it's good stile to do
+   * so anyway. */
+  host_this = host_head;
+  host_head = NULL;
+  while (host_this != NULL)
+  {
+    host_next = host_this->next;
+
+    csnmp_host_close_session (host_this);
+
+    sfree (host_this->name);
+    sfree (host_this->address);
+    sfree (host_this->community);
+    sfree (host_this->data_list);
+    sfree (host_this);
+
+    host_this = host_next;
+  }
+
+  data_this = data_head;
+  data_head = NULL;
+  while (data_this != NULL)
+  {
+    data_next = data_this->next;
+
+    sfree (data_this->name);
+    sfree (data_this->type);
+    sfree (data_this->values);
+    sfree (data_this);
+
+    data_this = data_next;
+  }
+
+  return (0);
+} /* int csnmp_shutdown */
+
 void module_register (void)
 {
   plugin_register_complex_config ("snmp", csnmp_config);
   plugin_register_init ("snmp", csnmp_init);
   plugin_register_read ("snmp", csnmp_read);
+  plugin_register_shutdown ("snmp", csnmp_shutdown);
 } /* void module_register */
 
 /*