{GPL, other}: Relicense to MIT license.
[collectd.git] / src / utils_cache.c
index 05db70c..9d86781 100644 (file)
@@ -1,22 +1,27 @@
 /**
  * collectd - src/utils_cache.c
- * Copyright (C) 2007,2008  Florian octo Forster
+ * Copyright (C) 2007-2010  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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * 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.
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
  *
- * 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
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  *
- * Author:
- *   Florian octo Forster <octo at verplant.org>
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
  **/
 
 #include "collectd.h"
@@ -24,7 +29,6 @@
 #include "plugin.h"
 #include "utils_avltree.h"
 #include "utils_cache.h"
-#include "utils_threshold.h"
 #include "meta_data.h"
 
 #include <assert.h>
@@ -44,7 +48,7 @@ typedef struct cache_entry_s
        cdtime_t last_update;
        /* Interval in which the data is collected
         * (for purding old entries) */
-       int interval;
+       cdtime_t interval;
        int state;
        int hits;
 
@@ -69,7 +73,9 @@ static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
 
 static int cache_compare (const cache_entry_t *a, const cache_entry_t *b)
 {
+#if COLLECT_DEBUG
   assert ((a != NULL) && (b != NULL));
+#endif
   return (strcmp (a->name, b->name));
 } /* int cache_compare */
 
@@ -120,80 +126,6 @@ static void cache_free (cache_entry_t *ce)
   sfree (ce);
 } /* void cache_free */
 
-static int uc_send_notification (const char *name)
-{
-  cache_entry_t *ce = NULL;
-  int status;
-
-  char *name_copy;
-  char *host;
-  char *plugin;
-  char *plugin_instance;
-  char *type;
-  char *type_instance;
-
-  notification_t n;
-
-  name_copy = strdup (name);
-  if (name_copy == NULL)
-  {
-    ERROR ("uc_send_notification: strdup failed.");
-    return (-1);
-  }
-
-  status = parse_identifier (name_copy, &host,
-      &plugin, &plugin_instance,
-      &type, &type_instance);
-  if (status != 0)
-  {
-    ERROR ("uc_send_notification: Cannot parse name `%s'", name);
-    return (-1);
-  }
-
-  /* Copy the associative members */
-  notification_init (&n, NOTIF_FAILURE, /* host = */ NULL,
-      host, plugin, plugin_instance, type, type_instance);
-
-  sfree (name_copy);
-  name_copy = host = plugin = plugin_instance = type = type_instance = NULL;
-
-  pthread_mutex_lock (&cache_lock);
-
-  /*
-   * Set the time _after_ getting the lock because we don't know how long
-   * acquiring the lock takes and we will use this time later to decide
-   * whether or not the state is OKAY.
-   */
-  n.time = cdtime ();
-
-  status = c_avl_get (cache_tree, name, (void *) &ce);
-  if (status != 0)
-  {
-    pthread_mutex_unlock (&cache_lock);
-    sfree (name_copy);
-    return (-1);
-  }
-    
-  /* Check if the entry has been updated in the meantime */
-  if ((n.time - ce->last_update) < (2 * ce->interval))
-  {
-    ce->state = STATE_OKAY;
-    pthread_mutex_unlock (&cache_lock);
-    sfree (name_copy);
-    return (-1);
-  }
-
-  ssnprintf (n.message, sizeof (n.message),
-      "%s has not been updated for %.3f seconds.", name,
-      CDTIME_T_TO_DOUBLE (n.time - ce->last_update));
-
-  pthread_mutex_unlock (&cache_lock);
-
-  plugin_dispatch_notification (&n);
-
-  return (0);
-} /* int uc_send_notification */
-
 static void uc_check_range (const data_set_t *ds, cache_entry_t *ce)
 {
   int i;
@@ -258,7 +190,7 @@ static int uc_insert (const data_set_t *ds, const value_list_t *vl,
        ce->values_gauge[i] = NAN;
        if (vl->interval > 0)
          ce->values_gauge[i] = ((double) vl->values[i].absolute)
-           / ((double) vl->interval);
+           / CDTIME_T_TO_DOUBLE (vl->interval);
        ce->values_raw[i].absolute = vl->values[i].absolute;
        break;
        
@@ -304,10 +236,14 @@ int uc_check_timeout (void)
   cache_entry_t *ce;
 
   char **keys = NULL;
+  cdtime_t *keys_time = NULL;
+  cdtime_t *keys_interval = NULL;
   int keys_len = 0;
 
   char *key;
   c_avl_iterator_t *iter;
+
+  int status;
   int i;
   
   pthread_mutex_lock (&cache_lock);
@@ -318,128 +254,111 @@ int uc_check_timeout (void)
   iter = c_avl_get_iterator (cache_tree);
   while (c_avl_iterator_next (iter, (void *) &key, (void *) &ce) == 0)
   {
-    /* If entry has not been updated, add to `keys' array */
-    /* FIXME: Remove macro once "ce->interval" is of type cdtime_t. */
-    if ((now - ce->last_update) >= TIME_T_TO_CDTIME_T (timeout_g * ce->interval))
-    {
-      char **tmp;
+    char **tmp;
+    cdtime_t *tmp_time;
 
-      tmp = (char **) realloc ((void *) keys,
-         (keys_len + 1) * sizeof (char *));
-      if (tmp == NULL)
-      {
-       ERROR ("uc_check_timeout: realloc failed.");
-       c_avl_iterator_destroy (iter);
-       sfree (keys);
-       pthread_mutex_unlock (&cache_lock);
-       return (-1);
-      }
-
-      keys = tmp;
-      keys[keys_len] = strdup (key);
-      if (keys[keys_len] == NULL)
-      {
-       ERROR ("uc_check_timeout: strdup failed.");
-       continue;
-      }
-      keys_len++;
-    }
-  } /* while (c_avl_iterator_next) */
-
-  ce = NULL;
-
-  for (i = 0; i < keys_len; i++)
-  {
-    int status;
-
-    status = ut_check_interesting (keys[i]);
+    /* If the entry is fresh enough, continue. */
+    if ((now - ce->last_update) < (ce->interval * timeout_g))
+      continue;
 
-    if (status < 0)
+    /* If entry has not been updated, add to `keys' array */
+    tmp = (char **) realloc ((void *) keys,
+       (keys_len + 1) * sizeof (char *));
+    if (tmp == NULL)
     {
-      ERROR ("uc_check_timeout: ut_check_interesting failed.");
-      sfree (keys[i]);
+      ERROR ("uc_check_timeout: realloc failed.");
       continue;
     }
-    else if (status == 0) /* ``service'' is uninteresting */
+    keys = tmp;
+
+    tmp_time = realloc (keys_time, (keys_len + 1) * sizeof (*keys_time));
+    if (tmp_time == NULL)
     {
-      DEBUG ("uc_check_timeout: %s is missing but ``uninteresting''",
-         keys[i]);
-      ce = NULL;
-      status = c_avl_remove (cache_tree, keys[i],
-         (void *) &key, (void *) &ce);
-      if (status != 0)
-      {
-       ERROR ("uc_check_timeout: c_avl_remove (%s) failed.", keys[i]);
-      }
-      sfree (keys[i]);
-      sfree (key);
-      if (ce != NULL)
-        cache_free (ce);
+      ERROR ("uc_check_timeout: realloc failed.");
       continue;
     }
+    keys_time = tmp_time;
 
-    /* If we get here, the value is ``interesting''. Query the record from the
-     * cache and update the state field. */
-    if (c_avl_get (cache_tree, keys[i], (void *) &ce) != 0)
+    tmp_time = realloc (keys_interval, (keys_len + 1) * sizeof (*keys_interval));
+    if (tmp_time == NULL)
     {
-      ERROR ("uc_check_timeout: cannot get data for %s from cache", keys[i]);
-      /* Do not free `keys[i]' so a notification is sent further down. */
+      ERROR ("uc_check_timeout: realloc failed.");
       continue;
     }
-    assert (ce != NULL);
+    keys_interval = tmp_time;
 
-    if (status == 2) /* persist */
-    {
-      DEBUG ("uc_check_timeout: %s is missing, sending notification.",
-         keys[i]);
-      ce->state = STATE_MISSING;
-      /* Do not free `keys[i]' so a notification is sent further down. */
-    }
-    else if (status == 1) /* do not persist */
+    keys[keys_len] = strdup (key);
+    if (keys[keys_len] == NULL)
     {
-      if (ce->state == STATE_MISSING)
-      {
-       DEBUG ("uc_check_timeout: %s is missing but "
-           "notification has already been sent.",
-           keys[i]);
-       /* Set `keys[i]' to NULL to no notification is sent. */
-       sfree (keys[i]);
-      }
-      else /* (ce->state != STATE_MISSING) */
-      {
-       DEBUG ("uc_check_timeout: %s is missing, sending one notification.",
-           keys[i]);
-       ce->state = STATE_MISSING;
-       /* Do not free `keys[i]' so a notification is sent further down. */
-      }
-    }
-    else
-    {
-      WARNING ("uc_check_timeout: ut_check_interesting (%s) returned "
-         "invalid status %i.",
-         keys[i], status);
-      sfree (keys[i]);
+      ERROR ("uc_check_timeout: strdup failed.");
+      continue;
     }
+    keys_time[keys_len] = ce->last_time;
+    keys_interval[keys_len] = ce->interval;
 
-    /* Make really sure the next iteration doesn't work with this pointer.
-     * There have been too many bugs in the past.. :/  -- octo */
-    ce = NULL;
-  } /* for (keys[i]) */
+    keys_len++;
+  } /* while (c_avl_iterator_next) */
 
   c_avl_iterator_destroy (iter);
-
   pthread_mutex_unlock (&cache_lock);
 
+  if (keys_len == 0)
+    return (0);
+
+  /* Call the "missing" callback for each value. Do this before removing the
+   * value from the cache, so that callbacks can still access the data stored,
+   * including plugin specific meta data, rates, history, …. This must be done
+   * without holding the lock, otherwise we will run into a deadlock if a
+   * plugin calls the cache interface. */
   for (i = 0; i < keys_len; i++)
   {
-    if (keys[i] == NULL)
+    value_list_t vl = VALUE_LIST_INIT;
+
+    vl.values = NULL;
+    vl.values_len = 0;
+    vl.meta = NULL;
+
+    status = parse_identifier_vl (keys[i], &vl);
+    if (status != 0)
+    {
+      ERROR ("uc_check_timeout: parse_identifier_vl (\"%s\") failed.", keys[i]);
+      cache_free (ce);
       continue;
+    }
+
+    vl.time = keys_time[i];
+    vl.interval = keys_interval[i];
+
+    plugin_dispatch_missing (&vl);
+  } /* for (i = 0; i < keys_len; i++) */
+
+  /* Now actually remove all the values from the cache. We don't re-evaluate
+   * the timestamp again, so in theory it is possible we remove a value after
+   * it is updated here. */
+  pthread_mutex_lock (&cache_lock);
+  for (i = 0; i < keys_len; i++)
+  {
+    key = NULL;
+    ce = NULL;
+
+    status = c_avl_remove (cache_tree, keys[i],
+       (void *) &key, (void *) &ce);
+    if (status != 0)
+    {
+      ERROR ("uc_check_timeout: c_avl_remove (\"%s\") failed.", keys[i]);
+      sfree (keys[i]);
+      continue;
+    }
 
-    uc_send_notification (keys[i]);
     sfree (keys[i]);
-  }
+    sfree (key);
+    cache_free (ce);
+  } /* for (i = 0; i < keys_len; i++) */
+  pthread_mutex_unlock (&cache_lock);
 
   sfree (keys);
+  sfree (keys_time);
+  sfree (keys_interval);
 
   return (0);
 } /* int uc_check_timeout */
@@ -448,9 +367,6 @@ int uc_update (const data_set_t *ds, const value_list_t *vl)
 {
   char name[6 * DATA_MAX_NAME_LEN];
   cache_entry_t *ce = NULL;
-  int send_okay_notification = 0;
-  cdtime_t update_delay = 0;
-  notification_t n;
   int status;
   int i;
 
@@ -484,15 +400,6 @@ int uc_update (const data_set_t *ds, const value_list_t *vl)
     return (-1);
   }
 
-  /* Send a notification (after the lock has been released) if we switch the
-   * state from something else to `okay'. */
-  if (ce->state == STATE_MISSING)
-  {
-    send_okay_notification = 1;
-    ce->state = STATE_OKAY;
-    update_delay = cdtime () - ce->last_update;
-  }
-
   for (i = 0; i < ds->ds_num; i++)
   {
     switch (ds->ds[i].type)
@@ -579,28 +486,6 @@ int uc_update (const data_set_t *ds, const value_list_t *vl)
 
   pthread_mutex_unlock (&cache_lock);
 
-  if (send_okay_notification == 0)
-    return (0);
-
-  /* Do not send okay notifications for uninteresting values, i. e. values for
-   * which no threshold is configured. */
-  status = ut_check_interesting (name);
-  if (status <= 0)
-    return (0);
-
-  /* Initialize the notification */
-  memset (&n, '\0', sizeof (n));
-  NOTIFICATION_INIT_VL (&n, vl, ds);
-
-  n.severity = NOTIF_OKAY;
-  n.time = vl->time;
-
-  ssnprintf (n.message, sizeof (n.message),
-      "Received a value for %s. It was missing for %u seconds.",
-      name, (unsigned int) update_delay);
-
-  plugin_dispatch_notification (&n);
-
   return (0);
 } /* int uc_update */
 
@@ -694,6 +579,7 @@ int uc_get_names (char ***ret_names, cdtime_t **ret_times, size_t *ret_number)
   char **names = NULL;
   cdtime_t *times = NULL;
   size_t number = 0;
+  size_t size_arrays = 0;
 
   int status = 0;
 
@@ -702,42 +588,47 @@ int uc_get_names (char ***ret_names, cdtime_t **ret_times, size_t *ret_number)
 
   pthread_mutex_lock (&cache_lock);
 
+  size_arrays = (size_t) c_avl_size (cache_tree);
+  if (size_arrays < 1)
+  {
+    /* Handle the "no values" case here, to avoid the error message when
+     * calloc() returns NULL. */
+    pthread_mutex_unlock (&cache_lock);
+    return (0);
+  }
+
+  names = calloc (size_arrays, sizeof (*names));
+  times = calloc (size_arrays, sizeof (*times));
+  if ((names == NULL) || (times == NULL))
+  {
+    ERROR ("uc_get_names: calloc failed.");
+    sfree (names);
+    sfree (times);
+    pthread_mutex_unlock (&cache_lock);
+    return (ENOMEM);
+  }
+
   iter = c_avl_get_iterator (cache_tree);
   while (c_avl_iterator_next (iter, (void *) &key, (void *) &value) == 0)
   {
-    char **temp;
-
     /* remove missing values when list values */
     if (value->state == STATE_MISSING)
       continue;
 
-    if (ret_times != NULL)
-    {
-      cdtime_t *tmp_times;
+    /* c_avl_size does not return a number smaller than the number of elements
+     * returned by c_avl_iterator_next. */
+    assert (number < size_arrays);
 
-      tmp_times = (cdtime_t *) realloc (times, sizeof (cdtime_t) * (number + 1));
-      if (tmp_times == NULL)
-      {
-       status = -1;
-       break;
-      }
-      times = tmp_times;
+    if (ret_times != NULL)
       times[number] = value->last_time;
-    }
 
-    temp = (char **) realloc (names, sizeof (char *) * (number + 1));
-    if (temp == NULL)
-    {
-      status = -1;
-      break;
-    }
-    names = temp;
     names[number] = strdup (key);
     if (names[number] == NULL)
     {
       status = -1;
       break;
     }
+
     number++;
   } /* while (c_avl_iterator_next) */