Code

Merge remote-tracking branch 'github/pr/387'
[collectd.git] / src / memcached.c
index 5cfcc915ff5dcdfad456e79a683be32f90ab2be0..535ea84704b3dac328a5af495f69bccdbdfedb9a 100644 (file)
@@ -1,7 +1,7 @@
 /**
  * collectd - src/memcached.c, based on src/hddtemp.c
  * Copyright (C) 2007       Antony Dovgal
- * Copyright (C) 2007-2010  Florian Forster
+ * Copyright (C) 2007-2012  Florian Forster
  * Copyright (C) 2009       Doug MacEachern
  * Copyright (C) 2009       Franck Lombardi
  * Copyright (C) 2012       Nicolas Szalay
 #include "plugin.h"
 #include "configfile.h"
 
-# include <poll.h>
-# include <netdb.h>
-# include <sys/socket.h>
-# include <sys/un.h>
-# include <netinet/in.h>
-# include <netinet/tcp.h>
-
-/* Hack to work around the missing define in AIX */
-#ifndef MSG_DONTWAIT
-# define MSG_DONTWAIT MSG_NONBLOCK
-#endif
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
 
 #define MEMCACHED_DEF_HOST "127.0.0.1"
 #define MEMCACHED_DEF_PORT "11211"
 
-#define MEMCACHED_RETRY_COUNT 100
-
 struct memcached_s
 {
   char *name;
@@ -57,10 +49,9 @@ struct memcached_s
   char *host;
   char *port;
 };
-
 typedef struct memcached_s memcached_t;
 
-static int memcached_read (user_data_t *user_data);
+static _Bool memcached_have_instances = 0;
 
 static void memcached_free (memcached_t *st)
 {
@@ -73,301 +64,193 @@ static void memcached_free (memcached_t *st)
   sfree (st->port);
 }
 
-static int memcached_query_daemon (char *buffer, int buffer_size, user_data_t *user_data)
+static int memcached_connect_unix (memcached_t *st)
 {
-  int fd=-1;
-  ssize_t status;
-  int buffer_fill;
-  int i = 0;
+  struct sockaddr_un serv_addr;
+  int fd;
 
-  memcached_t *st;
-  st = user_data->data;
-  if (st->socket != NULL) {
-    struct sockaddr_un serv_addr;
-
-     memset (&serv_addr, 0, sizeof (serv_addr));
-     serv_addr.sun_family = AF_UNIX;
-     sstrncpy (serv_addr.sun_path, st->socket,
-     sizeof (serv_addr.sun_path));
-
-     /* create our socket descriptor */
-     fd = socket (AF_UNIX, SOCK_STREAM, 0);
-     if (fd < 0) {
-       char errbuf[1024];
-       ERROR ("memcached: unix socket: %s", sstrerror (errno, errbuf,
-       sizeof (errbuf)));
-       return -1;
-     }
-  }
-  else {
-    if (st->port != NULL) {
-      const char *host;
-      const char *port;
+  memset (&serv_addr, 0, sizeof (serv_addr));
+  serv_addr.sun_family = AF_UNIX;
+  sstrncpy (serv_addr.sun_path, st->socket,
+      sizeof (serv_addr.sun_path));
 
-      struct addrinfo  ai_hints;
-      struct addrinfo *ai_list, *ai_ptr;
-      int              ai_return = 0;
+  /* create our socket descriptor */
+  fd = socket (AF_UNIX, SOCK_STREAM, 0);
+  if (fd < 0)
+  {
+    char errbuf[1024];
+    ERROR ("memcached plugin: memcached_connect_unix: socket(2) failed: %s",
+        sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (-1);
+  }
 
-      memset (&ai_hints, '\0', sizeof (ai_hints));
-      ai_hints.ai_flags    = 0;
-#ifdef AI_ADDRCONFIG
-    /*  ai_hints.ai_flags   |= AI_ADDRCONFIG; */
-#endif
-      ai_hints.ai_family   = AF_INET;
-      ai_hints.ai_socktype = SOCK_STREAM;
-      ai_hints.ai_protocol = 0;
-
-      host = st->host;
-      if (host == NULL) {
-        host = MEMCACHED_DEF_HOST;
-      }
-
-      port = st->port;
-      if (strlen (port) == 0) {
-        port = MEMCACHED_DEF_PORT;
-      }
-
-      if ((ai_return = getaddrinfo (host, port, &ai_hints, &ai_list)) != 0) {
-        char errbuf[1024];
-        ERROR ("memcached: getaddrinfo (%s, %s): %s",
-          host, port,
-          (ai_return == EAI_SYSTEM)
-          ? sstrerror (errno, errbuf, sizeof (errbuf))
-          : gai_strerror (ai_return));
-        return -1;
-      }
+  return (fd);
+} /* int memcached_connect_unix */
 
-      fd = -1;
-      for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) {
-        /* create our socket descriptor */
-        fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
-        if (fd < 0) {
-          char errbuf[1024];
-          ERROR ("memcached: socket: %s", sstrerror (errno, errbuf, sizeof (errbuf)));
-          continue;
-        }
-
-        /* connect to the memcached daemon */
-        status = (ssize_t) connect (fd, (struct sockaddr *) ai_ptr->ai_addr, ai_ptr->ai_addrlen);
-        if (status != 0) {
-          shutdown (fd, SHUT_RDWR);
-          close (fd);
-          fd = -1;
-          continue;
-        }
+static int memcached_connect_inet (memcached_t *st)
+{
+  char *host;
+  char *port;
 
-        /* A socket could be opened and connecting succeeded. We're
-         * done. */
-        break;
-      }
+  struct addrinfo  ai_hints;
+  struct addrinfo *ai_list, *ai_ptr;
+  int status;
+  int fd = -1;
 
-      freeaddrinfo (ai_list);
-    }
-  }
+  memset (&ai_hints, 0, sizeof (ai_hints));
+  ai_hints.ai_flags    = 0;
+#ifdef AI_ADDRCONFIG
+  ai_hints.ai_flags   |= AI_ADDRCONFIG;
+#endif
+  ai_hints.ai_family   = AF_UNSPEC;
+  ai_hints.ai_socktype = SOCK_STREAM;
+  ai_hints.ai_protocol = 0;
 
-  if (fd < 0) {
-    ERROR ("memcached: Could not connect to daemon.");
-    return -1;
-  }
+  host = (st->host != NULL) ? st->host : MEMCACHED_DEF_HOST;
+  port = (st->port != NULL) ? st->port : MEMCACHED_DEF_PORT;
 
-  if (send(fd, "stats\r\n", sizeof("stats\r\n") - 1, MSG_DONTWAIT) != (sizeof("stats\r\n") - 1)) {
-    ERROR ("memcached: Could not send command to the memcached daemon.");
-    return -1;
+  ai_list = NULL;
+  status = getaddrinfo (host, port, &ai_hints, &ai_list);
+  if (status != 0)
+  {
+    char errbuf[1024];
+    ERROR ("memcached plugin: memcached_connect_inet: "
+        "getaddrinfo(%s,%s) failed: %s",
+        host, port,
+        (status == EAI_SYSTEM)
+        ? sstrerror (errno, errbuf, sizeof (errbuf))
+        : gai_strerror (status));
+    return (-1);
   }
 
+  for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
   {
-    struct pollfd p;
-    int status;
-
-    memset (&p, 0, sizeof (p));
-    p.fd = fd;
-    p.events = POLLIN | POLLERR | POLLHUP;
-    p.revents = 0;
-
-    status = poll (&p, /* nfds = */ 1,
-        /* timeout = */ CDTIME_T_TO_MS (interval_g));
-    if (status <= 0)
+    /* create our socket descriptor */
+    fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
+    if (fd < 0)
     {
-      if (status == 0)
-      {
-        ERROR ("memcached: poll(2) timed out after %.3f seconds.",
-            CDTIME_T_TO_DOUBLE (interval_g));
-      }
-      else
-      {
-        char errbuf[1024];
-        ERROR ("memcached: poll(2) failed: %s",
-            sstrerror (errno, errbuf, sizeof (errbuf)));
-      }
-      shutdown (fd, SHUT_RDWR);
-      close (fd);
-      return (-1);
-    }
-  }
-
-  /* receive data from the memcached daemon */
-  memset (buffer, '\0', buffer_size);
-
-  buffer_fill = 0;
-  while ((status = recv (fd, buffer + buffer_fill, buffer_size - buffer_fill, MSG_DONTWAIT)) != 0) {
-    if (i > MEMCACHED_RETRY_COUNT) {
-      ERROR("recv() timed out");
-      break;
-    }
-    i++;
-
-    if (status == -1) {
       char errbuf[1024];
-
-      if (errno == EAGAIN) {
-        continue;
-      }
-
-      ERROR ("memcached: Error reading from socket: %s",
+      WARNING ("memcached plugin: memcached_connect_inet: "
+          "socket(2) failed: %s",
           sstrerror (errno, errbuf, sizeof (errbuf)));
-      shutdown(fd, SHUT_RDWR);
-      close (fd);
-      return -1;
+      continue;
     }
-    buffer_fill += status;
 
-    if (buffer_fill > 3 && buffer[buffer_fill-5] == 'E' && buffer[buffer_fill-4] == 'N' && buffer[buffer_fill-3] == 'D') {
-      /* we got all the data */
-      break;
+    /* connect to the memcached daemon */
+    status = (int) connect (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
+    if (status != 0)
+    {
+      shutdown (fd, SHUT_RDWR);
+      close (fd);
+      fd = -1;
+      continue;
     }
-  }
 
-  if (buffer_fill >= buffer_size) {
-    buffer[buffer_size - 1] = '\0';
-    WARNING ("memcached: Message from memcached has been truncated.");
-  } else if (buffer_fill == 0) {
-    WARNING ("memcached: Peer has unexpectedly shut down the socket. "
-        "Buffer: `%s'", buffer);
-    shutdown(fd, SHUT_RDWR);
-    close(fd);
-    return -1;
+    /* A socket could be opened and connecting succeeded. We're done. */
+    break;
   }
 
-  shutdown(fd, SHUT_RDWR);
-  close(fd);
-  return 0;
+  freeaddrinfo (ai_list);
+  return (fd);
+} /* int memcached_connect_inet */
+
+static int memcached_connect (memcached_t *st)
+{
+  if (st->socket != NULL)
+    return (memcached_connect_unix (st));
+  else
+    return (memcached_connect_inet (st));
 }
 
-/* Configuration handling functiions
- * <Plugin memcached>
- *   <Instance "instance_name">
- *     Host foo.zomg.com
- *     Port "1234"
- *   </Instance>
- * </Plugin>
- */
-static int config_add_instance(oconfig_item_t *ci)
+static int memcached_query_daemon (char *buffer, size_t buffer_size, memcached_t *st)
 {
-  memcached_t *st;
-  int i;
+  int fd = -1;
   int status;
+  size_t buffer_fill;
 
-  if ((ci->values_num != 1)
-    || (ci->values[0].type != OCONFIG_TYPE_STRING))
-  {
-    WARNING ("memcached plugin: The `%s' config option "
-      "needs exactly one string argument.", ci->key);
-    return (-1);
+  fd = memcached_connect (st);
+  if (fd < 0) {
+    ERROR ("memcached plugin: Instance \"%s\" could not connect to daemon.",
+        st->name);
+    return -1;
   }
 
-  st = (memcached_t *) malloc (sizeof (*st));
-  if (st == NULL)
+  status = (int) swrite (fd, "stats\r\n", strlen ("stats\r\n"));
+  if (status != 0)
   {
-    ERROR ("memcached plugin: malloc failed.");
+    char errbuf[1024];
+    ERROR ("memcached plugin: write(2) failed: %s",
+        sstrerror (errno, errbuf, sizeof (errbuf)));
+    shutdown(fd, SHUT_RDWR);
+    close (fd);
     return (-1);
   }
 
-  st->name = NULL;
-  st->socket = NULL;
-  st->host = NULL;
-  st->port = NULL;
-  memset (st, 0, sizeof (*st));
+  /* receive data from the memcached daemon */
+  memset (buffer, 0, buffer_size);
 
-  status = cf_util_get_string (ci, &st->name);
-  if (status != 0)
+  buffer_fill = 0;
+  while ((status = (int) recv (fd, buffer + buffer_fill,
+          buffer_size - buffer_fill, /* flags = */ 0)) != 0)
   {
-    sfree (st);
-    return (status);
-  }
-  assert (st->name != NULL);
+    char const end_token[5] = {'E', 'N', 'D', '\r', '\n'};
+    if (status < 0)
+    {
+      char errbuf[1024];
 
-  for (i = 0; i < ci->children_num; i++)
-  {
-    oconfig_item_t *child = ci->children + i;
+      if ((errno == EAGAIN) || (errno == EINTR))
+          continue;
 
-    if (strcasecmp ("Socket", child->key) == 0)
-      status = cf_util_get_string (child, &st->socket);
-    else if (strcasecmp ("Host", child->key) == 0)
-      status = cf_util_get_string (child, &st->host);
-    else if (strcasecmp ("Port", child->key) == 0)
-      status = cf_util_get_service (child, &st->port);
-    else
+      ERROR ("memcached: Error reading from socket: %s",
+          sstrerror (errno, errbuf, sizeof (errbuf)));
+      shutdown(fd, SHUT_RDWR);
+      close (fd);
+      return (-1);
+    }
+
+    buffer_fill += (size_t) status;
+    if (buffer_fill > buffer_size)
     {
-      WARNING ("memcached plugin: Option `%s' not allowed here.",
-          child->key);
-      status = -1;
+      buffer_fill = buffer_size;
+      WARNING ("memcached plugin: Message was truncated.");
+      break;
     }
 
-    if (status != 0)
+    /* If buffer ends in end_token, we have all the data. */
+    if (memcmp (buffer + buffer_fill - sizeof (end_token),
+          end_token, sizeof (end_token)) == 0)
       break;
-  }
+  } /* while (recv) */
 
-  if (status == 0)
+  status = 0;
+  if (buffer_fill == 0)
   {
-    user_data_t ud;
-    char callback_name[3*DATA_MAX_NAME_LEN];
-
-    memset (&ud, 0, sizeof (ud));
-    ud.data = st;
-    ud.free_func = (void *) memcached_free;
-
-    memset (callback_name, 0, sizeof (callback_name));
-    ssnprintf (callback_name, sizeof (callback_name),
-        "memcached/%s/%s",
-        (st->host != NULL) ? st->host : hostname_g,
-        (st->port != NULL) ? st->port : "default"),
-
-    status = plugin_register_complex_read (/* group = */ "memcached",
-        /* name      = */ callback_name,
-        /* callback  = */ memcached_read,
-        /* interval  = */ NULL,
-        /* user_data = */ &ud);
+    WARNING ("memcached plugin: No data returned by memcached.");
+    status = -1;
   }
 
-  if (status != 0)
-  {
-    memcached_free(st);
-    return (-1);
-  }
-
-  return (0);
-}
+  shutdown(fd, SHUT_RDWR);
+  close(fd);
+  return (status);
+} /* int memcached_query_daemon */
 
-static int memcached_config (oconfig_item_t *ci)
+static void memcached_init_vl (value_list_t *vl, memcached_t const *st)
 {
-  int status = 0;
-  int i;
-
-  for (i = 0; i < ci->children_num; i++)
+  sstrncpy (vl->plugin, "memcached", sizeof (vl->plugin));
+  if (strcmp (st->name, "__legacy__") == 0) /* legacy mode */
   {
-    oconfig_item_t *child = ci->children + i;
-
-    if (strcasecmp ("Instance", child->key) == 0)
-      config_add_instance (child);
+    sstrncpy (vl->host, hostname_g, sizeof (vl->host));
+  }
+  else
+  {
+    if (st->socket != NULL)
+      sstrncpy (vl->host, hostname_g, sizeof (vl->host));
     else
-      WARNING ("memcached plugin: The configuration option "
-          "\"%s\" is not allowed here. Did you "
-          "forget to add an <Instance /> block "
-          "around the configuration?",
-          child->key);
-  } /* for (ci->children) */
-
-  return (status);
+      sstrncpy (vl->host,
+          (st->host != NULL) ? st->host : MEMCACHED_DEF_HOST,
+          sizeof (vl->host));
+    sstrncpy (vl->plugin_instance, st->name, sizeof (vl->plugin_instance));
+  }
 }
 
 static void submit_derive (const char *type, const char *type_inst,
@@ -375,15 +258,12 @@ static void submit_derive (const char *type, const char *type_inst,
 {
   value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
+  memcached_init_vl (&vl, st);
 
   values[0].derive = value;
 
   vl.values = values;
   vl.values_len = 1;
-  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
-  sstrncpy (vl.plugin, "memcached", sizeof (vl.plugin));
-  if (st->name != NULL)
-    sstrncpy (vl.plugin_instance, st->name,  sizeof (vl.plugin_instance));
   sstrncpy (vl.type, type, sizeof (vl.type));
   if (type_inst != NULL)
     sstrncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
@@ -396,16 +276,13 @@ static void submit_derive2 (const char *type, const char *type_inst,
 {
   value_t values[2];
   value_list_t vl = VALUE_LIST_INIT;
+  memcached_init_vl (&vl, st);
 
   values[0].derive = value0;
   values[1].derive = value1;
 
   vl.values = values;
   vl.values_len = 2;
-  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
-  sstrncpy (vl.plugin, "memcached", sizeof (vl.plugin));
-  if (st->name != NULL)
-    sstrncpy (vl.plugin_instance, st->name,  sizeof (vl.plugin_instance));
   sstrncpy (vl.type, type, sizeof (vl.type));
   if (type_inst != NULL)
     sstrncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
@@ -418,15 +295,12 @@ static void submit_gauge (const char *type, const char *type_inst,
 {
   value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
+  memcached_init_vl (&vl, st);
 
   values[0].gauge = value;
 
   vl.values = values;
   vl.values_len = 1;
-  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
-  sstrncpy (vl.plugin, "memcached", sizeof (vl.plugin));
-  if (st->name != NULL)
-    sstrncpy (vl.plugin_instance, st->name,  sizeof (vl.plugin_instance));
   sstrncpy (vl.type, type, sizeof (vl.type));
   if (type_inst != NULL)
     sstrncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
@@ -439,16 +313,13 @@ static void submit_gauge2 (const char *type, const char *type_inst,
 {
   value_t values[2];
   value_list_t vl = VALUE_LIST_INIT;
+  memcached_init_vl (&vl, st);
 
   values[0].gauge = value0;
   values[1].gauge = value1;
 
   vl.values = values;
   vl.values_len = 2;
-  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
-  sstrncpy (vl.plugin, "memcached", sizeof (vl.plugin));
-  if (st->name != NULL)
-    sstrncpy (vl.plugin_instance, st->name,  sizeof (vl.plugin_instance));
   sstrncpy (vl.type, type, sizeof (vl.type));
   if (type_inst != NULL)
     sstrncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
@@ -469,6 +340,10 @@ static int memcached_read (user_data_t *user_data)
   gauge_t bytes_total = NAN;
   gauge_t hits = NAN;
   gauge_t gets = NAN;
+  gauge_t incr_hits = NAN;
+  derive_t incr = 0;
+  gauge_t decr_hits = NAN;
+  derive_t decr = 0;
   derive_t rusage_user = 0;
   derive_t rusage_syst = 0;
   derive_t octets_rx = 0;
@@ -478,7 +353,7 @@ static int memcached_read (user_data_t *user_data)
   st = user_data->data;
 
   /* get data from daemon */
-  if (memcached_query_daemon (buf, sizeof (buf), user_data) < 0) {
+  if (memcached_query_daemon (buf, sizeof (buf), st) < 0) {
     return -1;
   }
 
@@ -565,6 +440,36 @@ static int memcached_read (user_data_t *user_data)
         gets = atof (fields[2]);
     }
 
+    /*
+     * Increment/Decrement
+     */
+    else if (FIELD_IS("incr_misses"))
+    {
+      derive_t incr_count = atoll (fields[2]);
+      submit_derive ("memcached_ops", "incr_misses", incr_count, st);
+      incr += incr_count;
+    }
+    else if (FIELD_IS ("incr_hits"))
+    {
+      derive_t incr_count = atoll (fields[2]);
+      submit_derive ("memcached_ops", "incr_hits", incr_count, st);
+      incr_hits = atof (fields[2]);
+      incr += incr_count;
+    }
+    else if (FIELD_IS ("decr_misses"))
+    {
+      derive_t decr_count = atoll (fields[2]);
+      submit_derive ("memcached_ops", "decr_misses", decr_count, st);
+      decr += decr_count;
+    }
+    else if (FIELD_IS ("decr_hits"))
+    {
+      derive_t decr_count = atoll (fields[2]);
+      submit_derive ("memcached_ops", "decr_hits", decr_count, st);
+      decr_hits = atof (fields[2]);
+      decr += decr_count;
+    }
+
     /*
      * Operations on the cache, i. e. cache hits, cache misses and evictions of items
      */
@@ -614,10 +519,179 @@ static int memcached_read (user_data_t *user_data)
     submit_gauge ("percent", "hitratio", rate, st);
   }
 
+  if (!isnan (incr_hits) && incr != 0)
+  {
+    gauge_t incr_rate = 100.0 * incr_hits / incr;
+    submit_gauge ("percent", "incr_hitratio", incr_rate, st);
+    submit_derive ("memcached_ops", "incr", incr, st);
+  }
+
+  if (!isnan (decr_hits) && decr != 0)
+  {
+    gauge_t decr_rate = 100.0 * decr_hits / decr;
+    submit_gauge ("percent", "decr_hitratio", decr_rate, st);
+    submit_derive ("memcached_ops", "decr", decr, st);
+  }
+
   return 0;
+} /* int memcached_read */
+
+static int memcached_add_read_callback (memcached_t *st)
+{
+  user_data_t ud;
+  char callback_name[3*DATA_MAX_NAME_LEN];
+  int status;
+
+  memset (&ud, 0, sizeof (ud));
+  ud.data = st;
+  ud.free_func = (void *) memcached_free;
+
+  assert (st->name != NULL);
+  ssnprintf (callback_name, sizeof (callback_name), "memcached/%s", st->name);
+
+  status = plugin_register_complex_read (/* group = */ "memcached",
+      /* name      = */ callback_name,
+      /* callback  = */ memcached_read,
+      /* interval  = */ NULL,
+      /* user_data = */ &ud);
+  return (status);
+} /* int memcached_add_read_callback */
+
+/* Configuration handling functiions
+ * <Plugin memcached>
+ *   <Instance "instance_name">
+ *     Host foo.zomg.com
+ *     Port "1234"
+ *   </Instance>
+ * </Plugin>
+ */
+static int config_add_instance(oconfig_item_t *ci)
+{
+  memcached_t *st;
+  int i;
+  int status = 0;
+
+  /* Disable automatic generation of default instance in the init callback. */
+  memcached_have_instances = 1;
+
+  st = malloc (sizeof (*st));
+  if (st == NULL)
+  {
+    ERROR ("memcached plugin: malloc failed.");
+    return (-1);
+  }
+
+  memset (st, 0, sizeof (*st));
+  st->name = NULL;
+  st->socket = NULL;
+  st->host = NULL;
+  st->port = NULL;
+
+  if (strcasecmp (ci->key, "Plugin") == 0) /* default instance */
+    st->name = sstrdup ("__legacy__");
+  else /* <Instance /> block */
+    status = cf_util_get_string (ci, &st->name);
+  if (status != 0)
+  {
+    sfree (st);
+    return (status);
+  }
+  assert (st->name != NULL);
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Socket", child->key) == 0)
+      status = cf_util_get_string (child, &st->socket);
+    else if (strcasecmp ("Host", child->key) == 0)
+      status = cf_util_get_string (child, &st->host);
+    else if (strcasecmp ("Port", child->key) == 0)
+      status = cf_util_get_service (child, &st->port);
+    else
+    {
+      WARNING ("memcached plugin: Option `%s' not allowed here.",
+          child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  if (status == 0)
+    status = memcached_add_read_callback (st);
+
+  if (status != 0)
+  {
+    memcached_free(st);
+    return (-1);
+  }
+
+  return (0);
 }
 
+static int memcached_config (oconfig_item_t *ci)
+{
+  int status = 0;
+  _Bool have_instance_block = 0;
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Instance", child->key) == 0)
+    {
+      config_add_instance (child);
+      have_instance_block = 1;
+    }
+    else if (!have_instance_block)
+    {
+      /* Non-instance option: Assume legacy configuration (without <Instance />
+       * blocks) and call config_add_instance() with the <Plugin /> block. */
+      return (config_add_instance (ci));
+    }
+    else
+      WARNING ("memcached plugin: The configuration option "
+          "\"%s\" is not allowed here. Did you "
+          "forget to add an <Instance /> block "
+          "around the configuration?",
+          child->key);
+  } /* for (ci->children) */
+
+  return (status);
+}
+
+static int memcached_init (void)
+{
+  memcached_t *st;
+  int status;
+
+  if (memcached_have_instances)
+    return (0);
+
+  /* No instances were configured, lets start a default instance. */
+  st = malloc (sizeof (*st));
+  if (st == NULL)
+    return (ENOMEM);
+  memset (st, 0, sizeof (*st));
+  st->name = sstrdup ("__legacy__");
+  st->socket = NULL;
+  st->host = NULL;
+  st->port = NULL;
+
+  status = memcached_add_read_callback (st);
+  if (status == 0)
+    memcached_have_instances = 1;
+  else
+    memcached_free (st);
+
+  return (status);
+} /* int memcached_init */
+
 void module_register (void)
 {
   plugin_register_complex_config ("memcached", memcached_config);
+  plugin_register_init ("memcached", memcached_init);
 }