Tree wide: Reformat with clang-format.
[collectd.git] / src / ethstat.c
index fbe52c2..bd55252 100644 (file)
@@ -1,6 +1,7 @@
 /**
  * collectd - src/ethstat.c
  * Copyright (C) 2011       Cyril Feraudet
+ * Copyright (C) 2012       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
  *
  * Authors:
  *   Cyril Feraudet <cyril at feraudet.com>
+ *   Florian "octo" Forster <octo@collectd.org>
  **/
 
-#include <stdlib.h>
-#include <string.h>
-#include <math.h>
-#include <time.h>
 #include "collectd.h"
+
 #include "common.h"
 #include "plugin.h"
-#include "configfile.h"
-#include <sys/types.h>
-#include <sys/types.h>
+#include "utils_avltree.h"
+#include "utils_complain.h"
+
+#if HAVE_SYS_IOCTL_H
 #include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <stdio.h>
-#include <string.h>
-#include <errno.h>
+#endif
+#if HAVE_NET_IF_H
 #include <net/if.h>
-#include <linux/sockios.h> 
-#include "ethstat.h"
+#endif
+#if HAVE_LINUX_SOCKIOS_H
+#include <linux/sockios.h>
+#endif
+#if HAVE_LINUX_ETHTOOL_H
+#include <linux/ethtool.h>
+#endif
+
+struct value_map_s {
+  char type[DATA_MAX_NAME_LEN];
+  char type_instance[DATA_MAX_NAME_LEN];
+};
+typedef struct value_map_s value_map_t;
 
-# if KERNEL_LINUX
+static char **interfaces = NULL;
+static size_t interfaces_num = 0;
 
+static c_avl_tree_t *value_map = NULL;
 
-static int ethstat_config (const char *key, const char *value)
+static _Bool collect_mapped_only = 0;
+
+static int ethstat_add_interface(const oconfig_item_t *ci) /* {{{ */
 {
-       if (strcasecmp (key, "Iface") == 0) {
-               ifacelist[ifacenumber] = malloc(strlen(value) + 1);
-               strcpy(ifacelist[ifacenumber++], value);
-               INFO("ethstat: Registred iface %s", value);
-       }
-       return (0);
-}
+  char **tmp;
+  int status;
+
+  tmp = realloc(interfaces, sizeof(*interfaces) * (interfaces_num + 1));
+  if (tmp == NULL)
+    return (-1);
+  interfaces = tmp;
+  interfaces[interfaces_num] = NULL;
 
+  status = cf_util_get_string(ci, interfaces + interfaces_num);
+  if (status != 0)
+    return (status);
 
-static void ethstat_submit_value (char *devname, char *counter, unsigned long long value)
+  interfaces_num++;
+  INFO("ethstat plugin: Registered interface %s",
+       interfaces[interfaces_num - 1]);
+
+  return (0);
+} /* }}} int ethstat_add_interface */
+
+static int ethstat_add_map(const oconfig_item_t *ci) /* {{{ */
 {
-       value_t values[1];
-       value_list_t vl = VALUE_LIST_INIT;
+  value_map_t *map;
+  int status;
+  char *key;
+
+  if ((ci->values_num < 2) || (ci->values_num > 3) ||
+      (ci->values[0].type != OCONFIG_TYPE_STRING) ||
+      (ci->values[1].type != OCONFIG_TYPE_STRING) ||
+      ((ci->values_num == 3) && (ci->values[2].type != OCONFIG_TYPE_STRING))) {
+    ERROR("ethstat plugin: The %s option requires "
+          "two or three string arguments.",
+          ci->key);
+    return (-1);
+  }
+
+  key = strdup(ci->values[0].value.string);
+  if (key == NULL) {
+    ERROR("ethstat plugin: strdup(3) failed.");
+    return (ENOMEM);
+  }
+
+  map = calloc(1, sizeof(*map));
+  if (map == NULL) {
+    sfree(key);
+    ERROR("ethstat plugin: calloc failed.");
+    return (ENOMEM);
+  }
+
+  sstrncpy(map->type, ci->values[1].value.string, sizeof(map->type));
+  if (ci->values_num == 3)
+    sstrncpy(map->type_instance, ci->values[2].value.string,
+             sizeof(map->type_instance));
+
+  if (value_map == NULL) {
+    value_map = c_avl_create((int (*)(const void *, const void *))strcmp);
+    if (value_map == NULL) {
+      sfree(map);
+      sfree(key);
+      ERROR("ethstat plugin: c_avl_create() failed.");
+      return (-1);
+    }
+  }
+
+  status = c_avl_insert(value_map,
+                        /* key = */ key,
+                        /* value = */ map);
+  if (status != 0) {
+    if (status > 0)
+      ERROR("ethstat plugin: Multiple mappings for \"%s\".", key);
+    else
+      ERROR("ethstat plugin: c_avl_insert(\"%s\") failed.", key);
+
+    sfree(map);
+    sfree(key);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int ethstat_add_map */
+
+static int ethstat_config(oconfig_item_t *ci) /* {{{ */
+{
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("Interface", child->key) == 0)
+      ethstat_add_interface(child);
+    else if (strcasecmp("Map", child->key) == 0)
+      ethstat_add_map(child);
+    else if (strcasecmp("MappedOnly", child->key) == 0)
+      (void)cf_util_get_boolean(child, &collect_mapped_only);
+    else
+      WARNING("ethstat plugin: The config option \"%s\" is unknown.",
+              child->key);
+  }
+
+  return (0);
+} /* }}} */
+
+static void ethstat_submit_value(const char *device, const char *type_instance,
+                                 derive_t value) {
+  static c_complain_t complain_no_map = C_COMPLAIN_INIT_STATIC;
 
-       values[0].counter = value;
+  value_list_t vl = VALUE_LIST_INIT;
+  value_map_t *map = NULL;
 
-       vl.values = values;
-       vl.values_len = 1;
-       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
-       sstrncpy (vl.plugin, "ethstat", sizeof (vl.plugin));
-       sstrncpy (vl.plugin_instance, devname, sizeof (vl.plugin_instance));
-       sstrncpy (vl.type, "derive", sizeof (vl.type));
-       sstrncpy (vl.type_instance, counter, sizeof (vl.type_instance));
+  if (value_map != NULL)
+    c_avl_get(value_map, type_instance, (void *)&map);
 
-       plugin_dispatch_values (&vl);
+  /* If the "MappedOnly" option is specified, ignore unmapped values. */
+  if (collect_mapped_only && (map == NULL)) {
+    if (value_map == NULL)
+      c_complain(
+          LOG_WARNING, &complain_no_map,
+          "ethstat plugin: The \"MappedOnly\" option has been set to true, "
+          "but no mapping has been configured. All values will be ignored!");
+    return;
+  }
+
+  vl.values = &(value_t){.derive = value};
+  vl.values_len = 1;
+
+  sstrncpy(vl.plugin, "ethstat", sizeof(vl.plugin));
+  sstrncpy(vl.plugin_instance, device, sizeof(vl.plugin_instance));
+  if (map != NULL) {
+    sstrncpy(vl.type, map->type, sizeof(vl.type));
+    sstrncpy(vl.type_instance, map->type_instance, sizeof(vl.type_instance));
+  } else {
+    sstrncpy(vl.type, "derive", sizeof(vl.type));
+    sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
+  }
+
+  plugin_dispatch_values(&vl);
 }
 
+static int ethstat_read_interface(char *device) {
+  int fd;
+  struct ethtool_gstrings *strings;
+  struct ethtool_stats *stats;
+  size_t n_stats;
+  size_t strings_size;
+  size_t stats_size;
+  int status;
+
+  fd = socket(AF_INET, SOCK_DGRAM, /* protocol = */ 0);
+  if (fd < 0) {
+    char errbuf[1024];
+    ERROR("ethstat plugin: Failed to open control socket: %s",
+          sstrerror(errno, errbuf, sizeof(errbuf)));
+    return 1;
+  }
+
+  struct ethtool_drvinfo drvinfo = {.cmd = ETHTOOL_GDRVINFO};
+
+  struct ifreq req = {.ifr_data = (void *)&drvinfo};
+
+  sstrncpy(req.ifr_name, device, sizeof(req.ifr_name));
+
+  status = ioctl(fd, SIOCETHTOOL, &req);
+  if (status < 0) {
+    char errbuf[1024];
+    close(fd);
+    ERROR("ethstat plugin: Failed to get driver information "
+          "from %s: %s",
+          device, sstrerror(errno, errbuf, sizeof(errbuf)));
+    return (-1);
+  }
+
+  n_stats = (size_t)drvinfo.n_stats;
+  if (n_stats < 1) {
+    close(fd);
+    ERROR("ethstat plugin: No stats available for %s", device);
+    return (-1);
+  }
+
+  strings_size = sizeof(struct ethtool_gstrings) + (n_stats * ETH_GSTRING_LEN);
+  stats_size = sizeof(struct ethtool_stats) + (n_stats * sizeof(uint64_t));
+
+  strings = malloc(strings_size);
+  stats = malloc(stats_size);
+  if ((strings == NULL) || (stats == NULL)) {
+    close(fd);
+    sfree(strings);
+    sfree(stats);
+    ERROR("ethstat plugin: malloc failed.");
+    return (-1);
+  }
+
+  strings->cmd = ETHTOOL_GSTRINGS;
+  strings->string_set = ETH_SS_STATS;
+  strings->len = n_stats;
+  req.ifr_data = (void *)strings;
+  status = ioctl(fd, SIOCETHTOOL, &req);
+  if (status < 0) {
+    char errbuf[1024];
+    close(fd);
+    free(strings);
+    free(stats);
+    ERROR("ethstat plugin: Cannot get strings from %s: %s", device,
+          sstrerror(errno, errbuf, sizeof(errbuf)));
+    return (-1);
+  }
+
+  stats->cmd = ETHTOOL_GSTATS;
+  stats->n_stats = n_stats;
+  req.ifr_data = (void *)stats;
+  status = ioctl(fd, SIOCETHTOOL, &req);
+  if (status < 0) {
+    char errbuf[1024];
+    close(fd);
+    free(strings);
+    free(stats);
+    ERROR("ethstat plugin: Reading statistics from %s failed: %s", device,
+          sstrerror(errno, errbuf, sizeof(errbuf)));
+    return (-1);
+  }
 
-static int getstats(char *devname, struct ifreq *ifr) {
-        int fd;
-       struct ethtool_drvinfo drvinfo;
-       struct ethtool_gstrings *strings;
-       struct ethtool_stats *stats;
-       unsigned int n_stats, sz_str, sz_stats, i;
-       int err;
-
-
-       fd = socket(AF_INET, SOCK_DGRAM, 0);
-        if (fd < 0) {
-                ERROR("ethstat - %s : Cannot get control socket", devname);
-                return 1;
-        }
-
-        drvinfo.cmd = ETHTOOL_GDRVINFO;
-        ifr->ifr_data = (caddr_t)&drvinfo;
-        err = ioctl(fd, SIOCETHTOOL, ifr);
-        if (err < 0) {
-                ERROR("ethstat - %s : Cannot get driver information", devname);
-                return 1;
-        }
-
-
-        n_stats = drvinfo.n_stats;
-        if (n_stats < 1) {
-                ERROR("ethstat - %s : No stats available", devname);
-                return 1;
-        }
-
-        sz_str = n_stats * ETH_GSTRING_LEN;
-        sz_stats = n_stats * sizeof(u64);
-
-        strings = calloc(1, sz_str + sizeof(struct ethtool_gstrings));
-        stats = calloc(1, sz_stats + sizeof(struct ethtool_stats));
-        if (!strings || !stats) {
-                ERROR("ethstat - %s No memory available", devname);
-                return 1;
-        }
-
-        strings->cmd = ETHTOOL_GSTRINGS;
-        strings->string_set = ETH_SS_STATS;
-        strings->len = n_stats;
-        ifr->ifr_data = (caddr_t) strings;
-        err = ioctl(fd, SIOCETHTOOL, ifr);
-        if (err < 0) {
-                ERROR("ethstat - %s : Cannot get stats strings information", devname);
-                free(strings);
-                free(stats);
-                return 96;
-        }
-
-        stats->cmd = ETHTOOL_GSTATS;
-        stats->n_stats = n_stats;
-        ifr->ifr_data = (caddr_t) stats;
-        err = ioctl(fd, SIOCETHTOOL, ifr);
-        if (err < 0) {
-                ERROR("ethstat - %s : Cannot get stats information", devname);
-                free(strings);
-                free(stats);
-                return 97;
-        }
-
-        for (i = 0; i < n_stats; i++) {
-                DEBUG("ethstat - %s : %s: %llu",
-                       devname,
-                        &strings->data[i * ETH_GSTRING_LEN],
-                        stats->data[i]);
-               ethstat_submit_value (
-                       devname,
-                       (char*)&strings->data[i * ETH_GSTRING_LEN],
-                       stats->data[i]);
-        }
-        free(strings);
-        free(stats);
-
-
-       return 0;
+  for (size_t i = 0; i < n_stats; i++) {
+    char *stat_name;
+
+    stat_name = (void *)&strings->data[i * ETH_GSTRING_LEN];
+    /* Remove leading spaces in key name */
+    while (isspace((int)*stat_name))
+      stat_name++;
+
+    DEBUG("ethstat plugin: device = \"%s\": %s = %" PRIu64, device, stat_name,
+          (uint64_t)stats->data[i]);
+    ethstat_submit_value(device, stat_name, (derive_t)stats->data[i]);
+  }
+
+  close(fd);
+  sfree(strings);
+  sfree(stats);
+
+  return (0);
+} /* }}} ethstat_read_interface */
+
+static int ethstat_read(void) {
+  for (size_t i = 0; i < interfaces_num; i++)
+    ethstat_read_interface(interfaces[i]);
+
+  return 0;
 }
 
+static int ethstat_shutdown(void) {
+  void *key = NULL;
+  void *value = NULL;
 
-static int ethstat_read(void)
-{
-       struct ifreq ifr;
-       int i;
-
-       for (i = 0 ; i < ifacenumber ; i++) {
-               DEBUG("ethstat - Processing : %s\n", ifacelist[i]);
-               memset(&ifr, 0, sizeof(ifr));
-               strcpy(ifr.ifr_name, ifacelist[i]);
-               getstats(ifacelist[i], &ifr);
-       }
-       return 0;
+  if (value_map == NULL)
+    return (0);
+
+  while (c_avl_pick(value_map, &key, &value) == 0) {
+    sfree(key);
+    sfree(value);
+  }
+
+  c_avl_destroy(value_map);
+  value_map = NULL;
+
+  return (0);
 }
 
-void module_register (void)
-{
-       ifacelist = malloc(sizeof(char*));
-       plugin_register_config ("ethstat", ethstat_config,
-                       config_keys, config_keys_num);
-       plugin_register_read ("ethstat", ethstat_read);
+void module_register(void) {
+  plugin_register_complex_config("ethstat", ethstat_config);
+  plugin_register_read("ethstat", ethstat_read);
+  plugin_register_shutdown("ethstat", ethstat_shutdown);
 }
 
-#endif
+/* vim: set sw=2 sts=2 et fdm=marker : */