added all ports summary to tcpconns
[collectd.git] / src / tcpconns.c
index fded093..3f96345 100644 (file)
@@ -273,7 +273,29 @@ static const char *config_keys[] =
 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
 
 static int port_collect_listening = 0;
+static int port_collect_total = 1;
 static port_entry_t *port_list_head = NULL;
+static uint32_t count_total[TCP_STATE_MAX + 1];
+
+#if KERNEL_LINUX
+static uint32_t sequence_number = 0;
+
+enum
+{
+  SRC_DUNNO,
+  SRC_NETLINK,
+  SRC_PROC
+} linux_source = SRC_DUNNO;
+#endif
+
+static void connt_prepare_vl (value_list_t *vl, value_t *values)
+{
+  vl->values = values;
+  vl->values_len = 1;
+  sstrncpy (vl->host, hostname_g, sizeof (vl->host));
+  sstrncpy (vl->plugin, "tcpconns", sizeof (vl->plugin));
+  sstrncpy (vl->type, "tcp_connections", sizeof (vl->type));
+}
 
 static void conn_submit_port_entry (port_entry_t *pe)
 {
@@ -281,11 +303,7 @@ static void conn_submit_port_entry (port_entry_t *pe)
   value_list_t vl = VALUE_LIST_INIT;
   int i;
 
-  vl.values = values;
-  vl.values_len = 1;
-  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
-  sstrncpy (vl.plugin, "tcpconns", sizeof (vl.plugin));
-  sstrncpy (vl.type, "tcp_connections", sizeof (vl.type));
+  connt_prepare_vl (&vl, values);
 
   if (((port_collect_listening != 0) && (pe->flags & PORT_IS_LISTENING))
       || (pe->flags & PORT_COLLECT_LOCAL))
@@ -319,10 +337,33 @@ static void conn_submit_port_entry (port_entry_t *pe)
   }
 } /* void conn_submit */
 
+static void conn_submit_port_total (void)
+{
+  value_t values[1];
+  value_list_t vl = VALUE_LIST_INIT;
+  int i;
+
+  connt_prepare_vl (&vl, values);
+
+  sstrncpy (vl.plugin, "all", sizeof (vl.plugin));
+
+  for (i = 1; i <= TCP_STATE_MAX; i++)
+  {
+    vl.values[0].gauge = count_total[i];
+
+    sstrncpy (vl.type_instance, tcp_state[i], sizeof (vl.type_instance));
+
+    plugin_dispatch_values (&vl);
+  }
+}
+
 static void conn_submit_all (void)
 {
   port_entry_t *pe;
 
+  if (port_collect_total)
+    conn_submit_port_total ();
+
   for (pe = port_list_head; pe != NULL; pe = pe->next)
     conn_submit_port_entry (pe);
 } /* void conn_submit_all */
@@ -361,6 +402,8 @@ static void conn_reset_port_entry (void)
   port_entry_t *prev = NULL;
   port_entry_t *pe = port_list_head;
 
+  memset (&count_total, '\0', sizeof(count_total));
+
   while (pe != NULL)
   {
     /* If this entry was created while reading the files (ant not when handling
@@ -408,6 +451,8 @@ static int conn_handle_ports (uint16_t port_local, uint16_t port_remote, uint8_t
     return (-1);
   }
 
+  count_total[state]++;
+
   /* Listening sockets */
   if ((state == TCP_STATE_LISTEN) && (port_collect_listening != 0))
   {
@@ -431,6 +476,8 @@ static int conn_handle_ports (uint16_t port_local, uint16_t port_remote, uint8_t
 } /* int conn_handle_ports */
 
 #if KERNEL_LINUX
+/* Returns zero on success, less than zero on socket error and greater than
+ * zero on other errors. */
 static int conn_read_netlink (void)
 {
   int fd;
@@ -440,11 +487,17 @@ static int conn_read_netlink (void)
   struct iovec iov;
   struct inet_diag_msg *r;
   char buf[8192];
-  static uint32_t sequence_number = 0;
 
+  /* If this fails, it's likely a permission problem. We'll fall back to
+   * reading this information from files below. */
   fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_INET_DIAG);
   if (fd < 0)
-    return (1);
+  {
+    ERROR ("tcpconns plugin: conn_read_netlink: socket(AF_NETLINK, SOCK_RAW, "
+       "NETLINK_INET_DIAG) failed: %s",
+       sstrerror (errno, buf, sizeof (buf)));
+    return (-1);
+  }
 
   memset(&nladdr, 0, sizeof(nladdr));
   nladdr.nl_family = AF_NETLINK;
@@ -477,8 +530,10 @@ static int conn_read_netlink (void)
 
   if (sendmsg (fd, &msg, 0) < 0)
   {
+    ERROR ("tcpconns plugin: conn_read_netlink: sendmsg(2) failed: %s",
+       sstrerror (errno, buf, sizeof (buf)));
     close (fd);
-    return (1);
+    return (-1);
   }
 
   iov.iov_base = buf;
@@ -498,44 +553,58 @@ static int conn_read_netlink (void)
     status = recvmsg(fd, (void *) &msg, /* flags = */ 0);
     if (status < 0)
     {
-      if (errno == EINTR)
+      if ((errno == EINTR) || (errno == EAGAIN))
         continue;
+
+      ERROR ("tcpconns plugin: conn_read_netlink: recvmsg(2) failed: %s",
+         sstrerror (errno, buf, sizeof (buf)));
       close (fd);
-      return (1);
+      return (-1);
     }
-
-    if (status == 0)
+    else if (status == 0)
     {
       close (fd);
+      DEBUG ("tcpconns plugin: conn_read_netlink: Unexpected zero-sized "
+         "reply from netlink socket.");
       return (0);
     }
 
     h = (struct nlmsghdr*)buf;
     while (NLMSG_OK(h, status))
     {
-      if (h->nlmsg_seq == sequence_number)
+      if (h->nlmsg_seq != sequence_number)
       {
-        if (h->nlmsg_type == NLMSG_DONE)
-        {
-          close (fd);
-          return (0);
-        }
-        else if (h->nlmsg_type == NLMSG_ERROR)
-        {
-          close (fd);
-          return (1);
-        }
-
-        r = NLMSG_DATA(h);
-
-        /* This code does not (need to) distinguish between IPv4 and IPv6. */
-        conn_handle_ports (ntohs(r->id.idiag_sport),
-            ntohs(r->id.idiag_dport),
-            r->idiag_state);
+       h = NLMSG_NEXT(h, status);
+       continue;
       }
+
+      if (h->nlmsg_type == NLMSG_DONE)
+      {
+       close (fd);
+       return (0);
+      }
+      else if (h->nlmsg_type == NLMSG_ERROR)
+      {
+       struct nlmsgerr *msg_error;
+
+       msg_error = NLMSG_DATA(h);
+       WARNING ("tcpconns plugin: conn_read_netlink: Received error %i.",
+           msg_error->error);
+
+       close (fd);
+       return (1);
+      }
+
+      r = NLMSG_DATA(h);
+
+      /* This code does not (need to) distinguish between IPv4 and IPv6. */
+      conn_handle_ports (ntohs(r->id.idiag_sport),
+         ntohs(r->id.idiag_dport),
+         r->idiag_state);
+
       h = NLMSG_NEXT(h, status);
-    }
-  }
+    } /* while (NLMSG_OK) */
+  } /* while (1) */
 
   /* Not reached because the while() loop above handles the exit condition. */
   return (0);
@@ -675,31 +744,55 @@ static int conn_init (void)
 
 static int conn_read (void)
 {
-  int errors_num = 0;
+  int status;
 
   conn_reset_port_entry ();
 
-  /* Try to use netlink for getting this data, it is _much_ faster on systems
-   * with a large amount of connections. */
-  if (conn_read_netlink () != 0)
+  if (linux_source == SRC_NETLINK)
   {
+    status = conn_read_netlink ();
+  }
+  else if (linux_source == SRC_PROC)
+  {
+    int errors_num = 0;
+
     if (conn_read_file ("/proc/net/tcp") != 0)
       errors_num++;
     if (conn_read_file ("/proc/net/tcp6") != 0)
       errors_num++;
-  }
 
-  if (errors_num < 2)
-  {
-    conn_submit_all ();
+    if (errors_num < 2)
+      status = 0;
+    else
+      status = ENOENT;
   }
-  else
+  else /* if (linux_source == SRC_DUNNO) */
   {
-    ERROR ("tcpconns plugin: Neither /proc/net/tcp nor /proc/net/tcp6 "
-       "could be read.");
-    return (-1);
+    /* Try to use netlink for getting this data, it is _much_ faster on systems
+     * with a large amount of connections. */
+    status = conn_read_netlink ();
+    if (status == 0)
+    {
+      INFO ("tcpconns plugin: Reading from netlink succeeded. "
+         "Will use the netlink method from now on.");
+      linux_source = SRC_NETLINK;
+    }
+    else
+    {
+      INFO ("tcpconns plugin: Reading from netlink failed. "
+         "Will read from /proc from now on.");
+      linux_source = SRC_PROC;
+
+      /* return success here to avoid the "plugin failed" message. */
+      return (0);
+    }
   }
 
+  if (status == 0)
+    conn_submit_all ();
+  else
+    return (status);
+
   return (0);
 } /* int conn_read */
 /* #endif KERNEL_LINUX */