]> git.ipfire.org Git - thirdparty/collectd.git/commitdiff
ethstat
authorBartlomiej Kotlowski <bartlomiej.kotlowski@intel.com>
Fri, 8 Oct 2021 10:42:31 +0000 (12:42 +0200)
committerMatthias Runge <mrunge@matthias-runge.de>
Sat, 13 Nov 2021 10:35:32 +0000 (11:35 +0100)
Makefile.am
src/collectd.conf.in
src/collectd.conf.pod
src/ethstat.c
src/ethstat_test.c [new file with mode: 0644]

index 211c0447d35408994c5c40411ee86e09db12419b..b3b04406c657f8a287afdecf0a4f73444441a501 100644 (file)
@@ -1073,6 +1073,13 @@ if BUILD_PLUGIN_ETHSTAT
 pkglib_LTLIBRARIES += ethstat.la
 ethstat_la_SOURCES = src/ethstat.c
 ethstat_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+ethstat_la_LIBADD = libignorelist.la
+
+test_plugin_ethstat_SOURCES = src/ethstat_test.c src/daemon/configfile.c src/daemon/types_list.c
+test_plugin_ethstat_LDFLAGS = $(PLUGIN_LDFLAGS)
+test_plugin_ethstat_LDADD = libplugin_mock.la libignorelist.la libavltree.la liboconfig.la
+check_PROGRAMS += test_plugin_ethstat
+
 endif
 
 if BUILD_PLUGIN_FHCOUNT
index 741c04765586a28cb46f0b44c6a13f332f676909..c3918b54e05d95110ccc0f0011ad4a35a1b65a7a 100644 (file)
 
 #<Plugin ethstat>
 #      Interface "eth0"
+#      EthtoolExcludeMetrics "tx_packets"
+#      UseSysClassNet true
+#      SysClassNetExcludeMetrics "rx_packets"
 #      Map "rx_csum_offload_errors" "if_rx_errors" "checksum_offload"
 #      Map "multicast" "if_multicast"
 #      MappedOnly false
index 145284e1f49d86f39173967289e795a021fc4096..7b261c2142e00c8bf742fdb74c27f7b94f6da122 100644 (file)
@@ -3266,12 +3266,16 @@ at most B<16384> to prevent typos and dumb mistakes.
 =head2 Plugin C<ethstat>
 
 The I<ethstat plugin> collects information about network interface cards (NICs)
-by talking directly with the underlying kernel driver using L<ioctl(2)>.
+by talking directly with the underlying kernel driver using L<ioctl(2)> and
+optional read metrics from sysfs.
 
 B<Synopsis:>
 
  <Plugin "ethstat">
    Interface "eth0"
+   EthtoolExcludeMetrics "tx_packets"
+   UseSysClassNet true
+   SysClassNetExcludeMetrics "rx_packets"
    Map "rx_csum_offload_errors" "if_rx_errors" "checksum_offload"
    Map "multicast" "if_multicast"
  </Plugin>
@@ -3282,7 +3286,40 @@ B<Options:>
 
 =item B<Interface> I<Name>
 
-Collect statistical information about interface I<Name>.
+Collect statistical information about interfaces I<Name>. All configuration
+options below, up to the next interface tag, will apply to all interfaces
+mentioned in this tag outside of mapping
+
+=item B<EthtoolExcludeMetrics> I<Name>
+
+List of metrics passed by ethtool that will not be collected by the plugin
+for the interface.
+
+=item B<UseSysClassNet> B<true>|B<false>
+
+When set to B<true>, metrics will also be collect from sysfs. For metrics from
+two sources (ethtool and sysfs) with the same name, the metric from sysfs
+will be collected. Defaults to B<false>.
+
+metrics collection table:
+
+                     |                                     metric available in                                       |
+metric listed on     | ethtool and sysfs      | only in ethtool        | only in sysfs        | metric not available |
+---------------------+------------------------+------------------------+----------------------|----------------------+
+not listed on any    |                        |                        |                      |                      |
+exclude list         | collected from sysfs   | collected from ethtool | collected from sysfs | metric not collected |
+---------------------+------------------------+------------------------+----------------------+----------------------+
+ethtool exclude list | collected from sysfs   | metric not collected   | collected from sysfs | metric not collected |
+---------------------+------------------------+------------------------+----------------------+----------------------+
+systfs exclude list  | collected from ethtool | collected from ethtool | metric not collected | metric not collected |
+---------------------+------------------------+------------------------+----------------------+----------------------+
+both exclude lsits   | not collected          | metric not collected   | metric not collected | metric not collected |
+---------------------+------------------------+------------------------+----------------------+----------------------+
+
+=item B<SysClassNetExcludeMetrics> I<Name>
+
+List of metrics passed by sysfs that will not be collected by the plugin
+for the interface.
 
 =item B<Map> I<Name> I<Type> [I<TypeInstance>]
 
index f8bc5b540ab29cbebbe269e3e01b819a83952ca9..3236683782aa6af219ed8a15704484a49db45893 100644 (file)
@@ -20,6 +20,7 @@
  * Authors:
  *   Cyril Feraudet <cyril at feraudet.com>
  *   Florian "octo" Forster <octo@collectd.org>
+ *   Bartlomiej Kotlowski <bartlomiej.kotlowski@intel.com>
  **/
 
 #include "collectd.h"
 #include "plugin.h"
 #include "utils/avltree/avltree.h"
 #include "utils/common/common.h"
+#include "utils/ignorelist/ignorelist.h"
 #include "utils_complain.h"
+#include <dirent.h>
+#include <stdbool.h>
 
 #if HAVE_SYS_IOCTL_H
 #include <sys/ioctl.h>
 #include <linux/ethtool.h>
 #endif
 
+#define SOURCE_ETH "ethtool"
+#define SOURCE_SYSFS "sysfs"
+
+#define PATH_SYSFS_INTERFACE "/sys/class/net/"
+#define SIZE_PATH_SYSFS_INTERFACE 15
+#define STAT "/statistics/"
+#define SIZE_STAT 12
+#define MAX_SIZE_METRIC_NAME 256
+#define MAX_SIZE_INTERFACES_NAME DATA_MAX_NAME_LEN
+#define MAX_SIZE_PATH_TO_STAT                                                  \
+  (SIZE_PATH_SYSFS_INTERFACE + MAX_SIZE_INTERFACES_NAME + SIZE_STAT + 1)
+
+#define INVALID_NAME false
+#define VALID_NAME true
+
 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;
 
-static char **interfaces;
-static size_t interfaces_num;
-
 static c_avl_tree_t *value_map;
 
 static bool collect_mapped_only;
 
-static int ethstat_add_interface(const oconfig_item_t *ci) /* {{{ */
-{
-  char **tmp;
+typedef struct node {
+  int val;
+  struct node *next;
+} node_t;
+
+typedef struct interface_metrics {
+  char **interfaces;
+  size_t interfaces_num;
+  ignorelist_t *ignorelist_ethtool;
+  node_t **ethtool_metrics;
+  ignorelist_t *ignorelist_sysfs;
+  bool use_sys_class_net;
+  char **sysfs_metrics;
+  size_t sysfs_metrics_num;
+  size_t sysfs_metrics_size;
+} interface_metrics_t;
+
+interface_metrics_t *interface_metrics;
+size_t interfaces_group_num = 0;
+
+static struct node *getNewNode(size_t val) {
+  struct node *newNode = malloc(sizeof(struct node));
+  if (newNode == NULL) {
+    ERROR("ethstat plugin: malloc failed.");
+    return NULL;
+  }
+  newNode->val = val;
+  newNode->next = NULL;
+  return newNode;
+}
+
+static int push(node_t **head, size_t val) {
+
+  node_t *current = *head;
+  node_t *new_node = getNewNode(val);
+
+  if (new_node == NULL)
+    return ENOMEM;
+
+  if (*head == NULL) {
+    *head = new_node;
+    return 0;
+  }
+
+  while (current->next != NULL) {
+    current = current->next;
+    if (val == current->val) {
+      sfree(new_node);
+      return 0;
+    }
+  }
+  current->next = new_node;
+  return 0;
+}
+
+static bool check_oconfig_type_string(const oconfig_item_t *ci) {
+  for (int i = 0; i < ci->values_num; i++) {
+    if (ci->values[i].type != OCONFIG_TYPE_STRING) {
+      WARNING("ethstat plugin: The %s option requires string argument.",
+              ci->key);
+      return false;
+    }
+  }
+  return true;
+}
+
+static bool check_name(const char *src, size_t size) {
+  if (src == NULL || size == 0) {
+    return INVALID_NAME;
+  }
+
+  while ((*src != '\0' && size > 0)) {
+    if (!isalnum(*src) && !(*src == '-' || *src == '_')) {
+      return INVALID_NAME;
+    };
+    src++;
+    size--;
+  }
+
+  if (*src == '\0') {
+    return VALID_NAME;
+  } else {
+    return INVALID_NAME;
+  }
+}
+
+static int add_sysfs_metric_to_readable(interface_metrics_t *interface_group,
+                                        const char *metric) {
+
+  if (interface_group->sysfs_metrics_num >=
+      interface_group->sysfs_metrics_size) {
+    char **tmp;
+    tmp = realloc(interface_group->sysfs_metrics,
+                  sizeof(*interface_group->sysfs_metrics) *
+                      (interface_group->sysfs_metrics_num + 2));
+    if (tmp == NULL)
+      return -1;
+    interface_group->sysfs_metrics_size += 2;
+    interface_group->sysfs_metrics = tmp;
+    interface_group->sysfs_metrics[interface_group->sysfs_metrics_num] = NULL;
+  }
+  char *metric_to_save;
+  if (metric == NULL)
+    return -1;
+  metric_to_save = strdup(metric);
+  if (metric_to_save == NULL)
+    return -1;
+
+  interface_group->sysfs_metrics[interface_group->sysfs_metrics_num] =
+      metric_to_save;
+  if (check_name(
+          interface_group->sysfs_metrics[interface_group->sysfs_metrics_num],
+          strlen(interface_group
+                     ->sysfs_metrics[interface_group->sysfs_metrics_num])) !=
+      VALID_NAME) {
+
+    ERROR("ethstat plugin: Invalid metric name %s",
+          interface_group->sysfs_metrics[interface_group->sysfs_metrics_num]);
+    sfree(metric_to_save);
+    return -1;
+  }
+
+  interface_group->sysfs_metrics_num++;
+  INFO("ethstat plugin: Registered sysfs metric to read %s",
+       interface_group->sysfs_metrics[interface_group->sysfs_metrics_num - 1]);
+
+  return 0;
+} /* }}} int ethstat_add_sysfs_metric */
+
+static int
+create_arrary_of_sysfs_readable_metrics(const oconfig_item_t *ci,
+                                        interface_metrics_t *interface_group) {
+  if (ci != NULL) {
+    for (int i = 0; i < ci->values_num; i++) {
+      ignorelist_add(interface_group->ignorelist_sysfs,
+                     ci->values[i].value.string);
+    }
+  }
+
   int status;
+  DIR *d;
+  struct dirent *dir;
+  char path[MAX_SIZE_PATH_TO_STAT + MAX_SIZE_METRIC_NAME];
+
+  for (int i = 0; i < interface_group->interfaces_num; i++) {
+    if (check_name(interface_group->interfaces[i],
+                   strlen(interface_group->interfaces[i])) != VALID_NAME) {
+      ERROR("ethstat plugin: Invalid interface name %s",
+            interface_group->interfaces[i]);
+      break;
+    }
+
+    status = ssnprintf(path, sizeof(path), "%s%s%s", PATH_SYSFS_INTERFACE,
+                       interface_group->interfaces[i], STAT);
+    if ((status < 0) || (status >= sizeof(path))) {
+      ERROR("ethstat plugin: The interface name %s is illegal. Probably is too "
+            "long",
+            interface_group->interfaces[i]);
+      break;
+    }
+
+    d = opendir(path);
+    if (d != NULL) {
+      while ((dir = readdir(d)) != NULL) {
+        if (dir->d_type == DT_REG) {
+          if (ignorelist_match(interface_group->ignorelist_sysfs,
+                               dir->d_name) == 0) {
+            status = add_sysfs_metric_to_readable(interface_group, dir->d_name);
+            if (status != 0) {
+              return -1;
+            }
+            ignorelist_add(interface_group->ignorelist_sysfs, dir->d_name);
+          }
+        }
+      }
+      closedir(d);
+    } else {
+      ERROR("ethstat plugin: Can't read sysfs metrics for interface %s",
+            interface_group->interfaces[i]);
+      return -1;
+    }
+  }
+  return 0;
+}
+/* function that adds to the ignorelist_ethtool all metrics that are read from
+   sysfs. As a result, two metrics with the same name will not be collected from
+   two different sources simultaneously */
+void add_readable_sysfs_metrics_to_ethtool_ignore_list(
+    interface_metrics_t *interface_group) {
+  if (interface_group->ignorelist_ethtool == NULL) {
+    interface_group->ignorelist_ethtool = ignorelist_create(0);
+  }
+  for (int i = 0; i < interface_group->sysfs_metrics_num; i++) {
+    if (interface_group->sysfs_metrics[i] != NULL) {
+      ignorelist_add(interface_group->ignorelist_ethtool,
+                     interface_group->sysfs_metrics[i]);
+    }
+  }
+}
+
+static int create_new_interfaces_group(const oconfig_item_t *ci,
+                                       interface_metrics_t *interface_group) {
+
+  interface_group->interfaces_num = ci->values_num;
+  interface_group->use_sys_class_net = false;
+  interface_group->ignorelist_ethtool = ignorelist_create(0);
+  interface_group->ignorelist_sysfs = ignorelist_create(0);
+  if (interface_group->ignorelist_ethtool == NULL ||
+      interface_group->ignorelist_sysfs == NULL) {
+    ERROR("ethstat plugin: can't create ignorelist");
+    return 1;
+  }
 
-  tmp = realloc(interfaces, sizeof(*interfaces) * (interfaces_num + 1));
-  if (tmp == NULL)
+  // standard interface statistics based on struct rtnl_link_stats64 which have
+  // 24 element. If this number increases, the size will increase later in the
+  // program
+  interface_group->sysfs_metrics_size = 24;
+
+  interface_group->sysfs_metrics =
+      malloc(sizeof(*interface_group->sysfs_metrics) *
+             interface_group->sysfs_metrics_size);
+  if (interface_group->sysfs_metrics == NULL) {
+    ERROR("ethstat plugin: malloc failed.");
     return -1;
-  interfaces = tmp;
-  interfaces[interfaces_num] = NULL;
+  }
+  interface_group->sysfs_metrics[0] = NULL;
+  node_t **array_node =
+      malloc(sizeof(node_t *) * interface_group->interfaces_num);
+  if (array_node == NULL) {
+    ERROR("ethstat plugin: calloc failed.");
+    return 1;
+  }
+  interface_group->ethtool_metrics = array_node;
+  for (int i = 0; i < interface_group->interfaces_num; i++) {
+    interface_group->ethtool_metrics[i] = NULL;
+  }
 
-  status = cf_util_get_string(ci, interfaces + interfaces_num);
-  if (status != 0)
-    return status;
+  interface_group->interfaces = malloc(sizeof(*interface_group->interfaces) *
+                                       (interface_group->interfaces_num));
 
-  interfaces_num++;
-  INFO("ethstat plugin: Registered interface %s",
-       interfaces[interfaces_num - 1]);
+  if (interface_group->interfaces == NULL) {
+    ERROR("ethstat plugin: malloc failed.");
+    return ENOMEM;
+  }
+
+  for (int i = 0; i < ci->values_num; i++) {
+    interface_group->interfaces[i] = NULL;
+    char *interface_name;
+    interface_name = strdup(ci->values[i].value.string);
+
+    if (interface_name == NULL) {
+      ERROR("ethstat plugin: Failed to allocate interface name.");
+      sfree(interface_name);
+      return ENOMEM;
+    }
+    interface_group->interfaces[i] = interface_name;
+
+    INFO("ethstat plugin: Registered interface %s",
+         interface_group->interfaces[i]);
+  }
+  interfaces_group_num++;
 
   return 0;
 } /* }}} int ethstat_add_interface */
@@ -138,34 +397,234 @@ static int ethstat_add_map(const oconfig_item_t *ci) /* {{{ */
   return 0;
 } /* }}} int ethstat_add_map */
 
+static int complete_list_of_metrics_read_by_ethtool(
+    char *device, ignorelist_t *ignorelist_ethtool, node_t **ethtool_metrics) {
+
+  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) {
+    ERROR("ethstat plugin: Failed to open control socket: %s", STRERRNO);
+    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) {
+    close(fd);
+    ERROR("ethstat plugin: Failed to get driver information "
+          "from %s: %s",
+          device, STRERRNO);
+    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) {
+    close(fd);
+    free(strings);
+    free(stats);
+    ERROR("ethstat plugin: Cannot get strings from %s: %s", device, STRERRNO);
+    return -1;
+  }
+
+  stats->cmd = ETHTOOL_GSTATS;
+  stats->n_stats = n_stats;
+  req.ifr_data = (void *)stats;
+  status = ioctl(fd, SIOCETHTOOL, &req);
+  if (status < 0) {
+    close(fd);
+    free(strings);
+    free(stats);
+    ERROR("ethstat plugin: Reading statistics from %s failed: %s", device,
+          STRERRNO);
+    return -1;
+  }
+
+  for (size_t i = 0; i < n_stats; i++) {
+    char *stat_name;
+
+    stat_name = (char *)&strings->data[i * ETH_GSTRING_LEN];
+
+    /* Remove leading spaces in key name */
+    while (isspace((int)*stat_name))
+      stat_name++;
+    // if the metric is not on the ignorelist_ethtool, add its number to the
+    // list of read metrics
+    if (ignorelist_match(ignorelist_ethtool, stat_name) == 0) {
+      status = push(ethtool_metrics, i);
+      if (status != 0) {
+        ERROR("ethstat plugin: Unable to add item %s to list", stat_name);
+        return -1;
+      }
+    }
+  }
+
+  close(fd);
+  sfree(strings);
+  sfree(stats);
+
+  return 0;
+};
+
 static int ethstat_config(oconfig_item_t *ci) /* {{{ */
 {
+  int ret;
   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)
+    if (strcasecmp("Interface", child->key) == 0) {
+      if (check_oconfig_type_string(child) == true) {
+        if (interface_metrics == NULL) {
+          interface_metrics =
+              (interface_metrics_t *)calloc(1, sizeof(*interface_metrics));
+          if (interface_metrics == NULL) {
+            ERROR("ethstat plugin: calloc failed.");
+            return -1;
+          }
+        } else {
+          interface_metrics_t *tmp;
+          tmp = realloc(interface_metrics,
+                        sizeof(*tmp) * (interfaces_group_num + 1));
+          if (tmp == NULL) {
+            ERROR("ethstat plugin: realloc failed.");
+            return -1;
+          }
+          interface_metrics = tmp;
+        }
+
+        ret = create_new_interfaces_group(
+            child, &interface_metrics[interfaces_group_num]);
+        if (ret != 0) {
+          ERROR("ethstat plugin: can't read interface config");
+          return ret;
+        }
+      }
+
+    } 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
+    else if (strcasecmp("MappedOnly", child->key) == 0) {
+      ret = cf_util_get_boolean(child, &collect_mapped_only);
+      if (ret != 0) {
+        ERROR("ethstat plugin: Unable to set MappedOnly");
+        return 1;
+      }
+    } else if (strcasecmp("EthtoolExcludeMetrics", child->key) == 0) {
+      if (interfaces_group_num > 0) {
+        if (check_oconfig_type_string(child) == true) {
+          for (int j = 0; j < child->values_num; j++) {
+            ignorelist_add(
+                interface_metrics[interfaces_group_num - 1].ignorelist_ethtool,
+                child->values[j].value.string);
+          }
+        }
+      } else {
+        ERROR("ethstat plugin: Interface names must appear before adding "
+              "EthtoolExcludeMetrics");
+        return 1;
+      }
+    } else if (strcasecmp("UseSysClassNet", child->key) == 0) {
+      if (interfaces_group_num > 0) {
+        ret = cf_util_get_boolean(
+            child,
+            &interface_metrics[interfaces_group_num - 1].use_sys_class_net);
+        if (ret != 0) {
+          ERROR("ethstat plugin: Unable to set UseSysClassNet");
+          return 1;
+        }
+      } else {
+        ERROR(
+            "Interface names must appear before adding EthtoolExcludeMetrics");
+        return 1;
+      }
+    } else if (strcasecmp("SysClassNetExcludeMetrics", child->key) == 0) {
+
+      if (interfaces_group_num > 0) {
+        if (check_oconfig_type_string(child) == true) {
+          ret = create_arrary_of_sysfs_readable_metrics(
+              child, &interface_metrics[interfaces_group_num - 1]);
+          if (ret != 0) {
+            ERROR("ethstat plugin: Unable to create metric reading list from "
+                  "sysfs");
+            return 1;
+          }
+        }
+      } else {
+        ERROR("Interface names must appear before adding "
+              "SysClassNetExcludeMetrics");
+        return 1;
+      }
+    } else
       WARNING("ethstat plugin: The config option \"%s\" is unknown.",
               child->key);
   }
-
+  for (int i = 0; i < interfaces_group_num; i++) {
+    for (int j = 0; j < interface_metrics[i].interfaces_num; j++) {
+      if (interface_metrics[i].use_sys_class_net == true) {
+        if (interface_metrics[i].sysfs_metrics[0] == NULL) {
+          // This mean use_sys_class_net is set to true, but
+          // SysClassNetExcludeMetrics is not used in config
+          ret = create_arrary_of_sysfs_readable_metrics(NULL,
+                                                        &interface_metrics[i]);
+          if (ret != 0) {
+            ERROR("ethstat plugin: Unable to create metric reading list "
+                  "from sysfs");
+            break;
+          }
+        }
+        add_readable_sysfs_metrics_to_ethtool_ignore_list(
+            &interface_metrics[i]);
+      }
+      complete_list_of_metrics_read_by_ethtool(
+          interface_metrics[i].interfaces[j],
+          interface_metrics[i].ignorelist_ethtool,
+          &interface_metrics[i].ethtool_metrics[j]);
+    }
+  }
   return 0;
 } /* }}} */
 
-static void ethstat_submit_value(const char *device, const char *type_instance,
-                                 derive_t value) {
+static void ethstat_submit_value(const char *device, const char *name,
+                                 counter_t value, char *source) {
   static c_complain_t complain_no_map = C_COMPLAIN_INIT_STATIC;
-
-  value_list_t vl = VALUE_LIST_INIT;
   value_map_t *map = NULL;
-
+  char fam_name[256];
   if (value_map != NULL)
-    c_avl_get(value_map, type_instance, (void *)&map);
+    c_avl_get(value_map, name, (void *)&map);
 
   /* If the "MappedOnly" option is specified, ignore unmapped values. */
   if (collect_mapped_only && (map == NULL)) {
@@ -177,23 +636,95 @@ static void ethstat_submit_value(const char *device, const char *type_instance,
     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));
+    ssnprintf(fam_name, sizeof(fam_name), "%s", map->type);
   } else {
-    sstrncpy(vl.type, "derive", sizeof(vl.type));
-    sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
+    ssnprintf(fam_name, sizeof(fam_name), "%s", name);
+  }
+
+  metric_family_t fam = {
+      .name = fam_name,
+      .type = METRIC_TYPE_COUNTER,
+  };
+
+  metric_t m = {
+      .family = &fam,
+      .value = (value_t){.counter = value},
+  };
+
+  metric_label_set(&m, "interface", device);
+  metric_label_set(&m, "plugin", "ethstat");
+  metric_label_set(&m, "source", source);
+
+  if (map != NULL) {
+    metric_label_set(&m, "tag", map->type_instance);
+  }
+
+  metric_family_metric_append(&fam, m);
+  metric_reset(&m);
+
+  int status = plugin_dispatch_metric_family(&fam);
+  if (status != 0) {
+    ERROR("ethstat plugin: plugin_dispatch_metric_family failed: %s",
+          STRERROR(status));
   }
 
-  plugin_dispatch_values(&vl);
+  metric_family_metric_reset(&fam);
+}
+
+static int read_sysfs_metrics(char *device, char **sysfs_metrics,
+                              size_t sysfs_metrics_num) {
+
+  if (sysfs_metrics[0] == NULL) {
+    return 1;
+  }
+
+  FILE *fp;
+  int status;
+  char path_base[MAX_SIZE_PATH_TO_STAT];
+  char path_metric[MAX_SIZE_PATH_TO_STAT + MAX_SIZE_METRIC_NAME];
+  if (check_name(device, strlen(device)) != VALID_NAME) {
+    ERROR("ethstat plugin: Invalid interface name %s", device);
+    return 1;
+  };
+  status = ssnprintf(path_base, sizeof(path_base), "%s%s%s",
+                     PATH_SYSFS_INTERFACE, device, STAT);
+  if ((status < 0) || (status >= sizeof(path_base))) {
+    ERROR("ethstat plugin: The interface name %s is illegal. Probably is too "
+          "long",
+          device);
+    return ENOMEM;
+  }
+
+  uint64_t buff;
+
+  for (int i = 0; i < sysfs_metrics_num; i++) {
+    status = ssnprintf(path_metric, sizeof(path_metric), "%s%s", path_base,
+                       sysfs_metrics[i]);
+    if ((status < 0) || (status >= sizeof(path_metric))) {
+      ERROR(
+          "ethstat plugin: The metric name %s is illegal. Probably is too long",
+          sysfs_metrics[i]);
+      return ENOMEM;
+    }
+
+    fp = fopen(path_metric, "r");
+    if (fp == NULL) {
+      ERROR("ethstat plugin: Can't open file %s", path_metric);
+    } else {
+      if (fscanf(fp, "%" PRIu64, &buff) != 0) {
+        ethstat_submit_value(device, sysfs_metrics[i], (counter_t)buff,
+                             SOURCE_SYSFS);
+      } else {
+        ERROR("ethstat plugin: Can't read metric from %s", path_metric);
+      };
+      fclose(fp);
+    }
+  }
+  return 0;
 }
 
-static int ethstat_read_interface(char *device) {
+static int ethstat_read_interface(char *device, node_t *ethtool_metrics) {
   int fd;
   struct ethtool_gstrings *strings;
   struct ethtool_stats *stats;
@@ -268,31 +799,60 @@ static int ethstat_read_interface(char *device) {
           STRERRNO);
     return -1;
   }
-
-  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]);
+  if (ethtool_metrics->val == -1) {
+    for (size_t i = 0; i < n_stats; i++) {
+      char *stat_name;
+
+      stat_name = (char *)&strings->data[i * ETH_GSTRING_LEN];
+      /* Remove leading spaces in key name */
+      while (isspace((int)*stat_name)) {
+        stat_name++;
+      }
+
+      ethstat_submit_value(device, stat_name, (counter_t)stats->data[i],
+                           SOURCE_ETH);
+    }
+  } else {
+    node_t *current = ethtool_metrics;
+
+    while (current != NULL) {
+      if (current->val < n_stats) {
+        char *stat_name;
+
+        stat_name = (void *)&strings->data[current->val * ETH_GSTRING_LEN];
+        /* Remove leading spaces in key name */
+        while (isspace((int)*stat_name)) {
+          stat_name++;
+        }
+        ethstat_submit_value(device, stat_name,
+                             (counter_t)stats->data[current->val], SOURCE_ETH);
+      }
+      current = current->next;
+    }
   }
 
   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]);
-
+  if (interfaces_group_num == 0) {
+    WARNING("No interface added to read");
+  } else {
+    for (size_t i = 0; i < interfaces_group_num; i++) {
+      for (size_t j = 0; j < interface_metrics[i].interfaces_num; j++) {
+        ethstat_read_interface(interface_metrics[i].interfaces[j],
+                               interface_metrics[i].ethtool_metrics[j]);
+        if (interface_metrics[i].use_sys_class_net) {
+          read_sysfs_metrics(interface_metrics[i].interfaces[j],
+                             interface_metrics[i].sysfs_metrics,
+                             interface_metrics[i].sysfs_metrics_num);
+        }
+      }
+    }
+  }
   return 0;
 }
 
@@ -311,6 +871,34 @@ static int ethstat_shutdown(void) {
   c_avl_destroy(value_map);
   value_map = NULL;
 
+  for (int i = 0; i < interfaces_group_num; i++) {
+
+    for (int j = 0; j < interface_metrics[i].interfaces_num; j++) {
+
+      sfree(interface_metrics[i].interfaces[j]);
+
+      node_t *current = interface_metrics[i].ethtool_metrics[0];
+      while (current != NULL) {
+        node_t *to_remove = current;
+        current = current->next;
+        sfree(to_remove);
+      }
+      sfree(interface_metrics[i].ethtool_metrics);
+    }
+    sfree(interface_metrics[i].interfaces);
+
+    if (interface_metrics[i].ignorelist_sysfs != NULL) {
+      ignorelist_free(interface_metrics[i].ignorelist_sysfs);
+    }
+    if (interface_metrics[i].ignorelist_ethtool != NULL) {
+      ignorelist_free(interface_metrics[i].ignorelist_ethtool);
+    }
+
+    for (size_t j = 0; j < interface_metrics[i].sysfs_metrics_size; j++) {
+      sfree(interface_metrics[i].sysfs_metrics[j]);
+    }
+    sfree(interface_metrics[i].sysfs_metrics);
+  }
   return 0;
 }
 
diff --git a/src/ethstat_test.c b/src/ethstat_test.c
new file mode 100644 (file)
index 0000000..66c8715
--- /dev/null
@@ -0,0 +1,464 @@
+/**
+ * collectd - src/ethstat_test.c
+ * MIT License
+ *
+ * Copyright (C) 2021  Intel Corporation. All rights reserved.
+ *
+ * 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:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 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.
+ *
+ * Authors:
+ *   Bartlomiej Kotlowski <bartlomiej.kotlowski@intel.com>
+ **/
+#define plugin_dispatch_metric_family plugin_dispatch_metric_family_ethstat_test
+
+#include "ethstat.c"
+#include "testing.h"
+
+#define NPROCS 2
+#define MAX_SAVE_DATA_SUBMIT 4
+
+// mock
+
+uint64_t matric_in_ethtool = 0;
+int ioctl(int __fd, unsigned long int __request, ...) {
+  va_list valist;
+  va_start(valist, __request);
+  struct ifreq *ifr = va_arg(valist, struct ifreq *);
+  va_end(valist);
+
+  uint32_t ethcmd = (uint32_t)*ifr->ifr_data;
+  void *addr = (void *)ifr->ifr_data;
+
+  // prepare struct to resposne
+  struct ethtool_drvinfo *drvinfo =
+      (struct ethtool_drvinfo *)addr; // ETHTOOL_GDRVINFO
+  struct ethtool_gstrings *strings =
+      (struct ethtool_gstrings *)addr; // ETHTOOL_GSTRINGS
+  struct ethtool_stats *stats = (struct ethtool_stats *)addr; // ETHTOOL_GSTATS
+
+  switch (ethcmd) {
+  case ETHTOOL_GDRVINFO: {
+    drvinfo->n_stats = 1;
+    return 1; // OK
+  }
+
+  case ETHTOOL_GSTRINGS: {
+    memcpy(strings->data, "rx_bytes", 9);
+    return 2; // OK
+  }
+
+  case ETHTOOL_GSTATS: {
+    memcpy(stats->data, &matric_in_ethtool, 8);
+    return 3; // OK
+  }
+
+  default:
+    return -1; // no mock feature
+  }
+  return -2; // something wrong
+};
+
+FILE *fopen(__attribute__((unused)) const char *path,
+            __attribute__((unused)) const char *mode) {
+  static FILE f;
+  return &f;
+}
+int fclose(FILE *__stream) { return 0; }
+
+// this varable can be changed between tests. The value given here will pretend
+// to be the value read from the file
+uint64_t matric_in_file = 0;
+
+int fscanf(FILE *stream, const char *format, ...) {
+  va_list arg;
+  va_start(arg, format);
+  uint64_t *pointer;
+  pointer = va_arg(arg, uint64_t *);
+  *pointer = matric_in_file;
+  va_end(arg);
+  return 1;
+}
+
+int data_submit_iterator = MAX_SAVE_DATA_SUBMIT - 1;
+counter_t data_submit[MAX_SAVE_DATA_SUBMIT];
+
+int plugin_dispatch_metric_family_ethstat_test(metric_family_t const *fam) {
+
+  data_submit_iterator++;
+  if (data_submit_iterator >= MAX_SAVE_DATA_SUBMIT - 1) {
+    data_submit_iterator = 0;
+  }
+
+  data_submit[data_submit_iterator] = fam->metric.ptr->value.counter;
+
+  return 0;
+}
+// END mock
+
+DEF_TEST(getNewNode) {
+  node_t *n_ret;
+  n_ret = getNewNode(1);
+  CHECK_NOT_NULL(n_ret);
+  EXPECT_EQ_UINT64(n_ret->val, 1);
+  OK1(n_ret->next == NULL, "expect NULL");
+  sfree(n_ret);
+
+  n_ret = getNewNode(0);
+  CHECK_NOT_NULL(n_ret);
+  EXPECT_EQ_UINT64(n_ret->val, 0);
+  OK1(n_ret->next == NULL, "expect NULL");
+  sfree(n_ret);
+  return 0;
+}
+
+DEF_TEST(check_name) {
+  bool b_ret;
+  b_ret = check_name("rx_bytes", 8); // size ok
+  OK1(b_ret == VALID_NAME, "expect true (VALID_NAME)");
+
+  b_ret = check_name("rx_bytes", 9); // size too big
+  OK1(b_ret == VALID_NAME, "expect true (VALID_NAME)");
+
+  b_ret = check_name("rx_bytes", 7); // size too small
+  OK1(b_ret == INVALID_NAME, "expect false (INVALID_NAME)");
+
+  b_ret = check_name("../foo/rx_bytes", 15);
+  OK1(b_ret == INVALID_NAME, "expect false (INVALID_NAME)");
+
+  b_ret = check_name(NULL, 11);
+  OK1(b_ret == INVALID_NAME, "expect false (INVALID_NAME)");
+  return 0;
+}
+
+DEF_TEST(add_sysfs_metric_to_readable) {
+  int i_ret;
+  interface_metrics_t *interface_group;
+
+  interface_group =
+      (interface_metrics_t *)calloc(1, sizeof(interface_metrics_t));
+
+  i_ret = add_sysfs_metric_to_readable(interface_group, "rx_bytes_123");
+
+  EXPECT_EQ_INT(i_ret, 0);
+  EXPECT_EQ_STR(interface_group->sysfs_metrics[0], "rx_bytes_123");
+  EXPECT_EQ_INT(interface_group->sysfs_metrics_num, 1);
+  EXPECT_EQ_INT(interface_group->sysfs_metrics_size, 2);
+
+  i_ret = add_sysfs_metric_to_readable(interface_group, NULL);
+  EXPECT_EQ_INT(i_ret, -1);
+  EXPECT_EQ_STR(interface_group->sysfs_metrics[0], "rx_bytes_123");
+  EXPECT_EQ_INT(interface_group->sysfs_metrics_num, 1);
+  EXPECT_EQ_INT(interface_group->sysfs_metrics_size, 2);
+
+  i_ret = add_sysfs_metric_to_readable(interface_group, "");
+  EXPECT_EQ_INT(-1, i_ret);
+  EXPECT_EQ_STR(interface_group->sysfs_metrics[0], "rx_bytes_123");
+  EXPECT_EQ_INT(interface_group->sysfs_metrics_num, 1);
+  EXPECT_EQ_INT(interface_group->sysfs_metrics_size, 2);
+
+  i_ret =
+      add_sysfs_metric_to_readable(interface_group, "../statistic/rx_bytes");
+  EXPECT_EQ_INT(-1, i_ret);
+  EXPECT_EQ_STR(interface_group->sysfs_metrics[0], "rx_bytes_123");
+  EXPECT_EQ_INT(interface_group->sysfs_metrics_num, 1);
+  EXPECT_EQ_INT(interface_group->sysfs_metrics_size, 2);
+
+  sfree(interface_group->sysfs_metrics[0]);
+  sfree(interface_group->sysfs_metrics);
+  sfree(interface_group);
+  return 0;
+}
+
+DEF_TEST(add_readable_sysfs_metrics_to_ethtool_ignore_list) {
+  interface_metrics_t *interface_group;
+  int i_ret;
+  interface_group =
+      (interface_metrics_t *)calloc(1, sizeof(interface_metrics_t));
+
+  i_ret = add_sysfs_metric_to_readable(interface_group, "rx_bytes_123");
+  EXPECT_EQ_INT(i_ret, 0);
+  EXPECT_EQ_INT(
+      0, ignorelist_match(interface_group->ignorelist_ethtool, "rx_bytes_123"));
+  add_readable_sysfs_metrics_to_ethtool_ignore_list(interface_group);
+  EXPECT_EQ_INT(
+      1, ignorelist_match(interface_group->ignorelist_ethtool, "rx_bytes_123"));
+  EXPECT_EQ_INT(
+      0, ignorelist_match(interface_group->ignorelist_ethtool, "*x_bytes_123"));
+  EXPECT_EQ_INT(0,
+                ignorelist_match(interface_group->ignorelist_ethtool, "[.]*"));
+
+  sfree(interface_group->sysfs_metrics[0]);
+  sfree(interface_group->sysfs_metrics);
+  ignorelist_free(interface_group->ignorelist_ethtool);
+  sfree(interface_group);
+  return 0;
+}
+
+DEF_TEST(create_new_interfaces_group) {
+  oconfig_item_t *conf;
+  conf = (oconfig_item_t *)calloc(1, sizeof(oconfig_item_t));
+  conf->values_num = 1;
+  conf->values = (oconfig_value_t *)calloc(1, sizeof(oconfig_value_t));
+  conf->values[0].value.string = strdup("eth");
+  interface_metrics_t *interface_group;
+  interface_group =
+      (interface_metrics_t *)calloc(2, sizeof(interface_metrics_t));
+  create_new_interfaces_group(conf, interface_group);
+
+  EXPECT_EQ_INT(conf->values_num, interface_group->interfaces_num);
+  OK1(interface_group->use_sys_class_net == false, "");
+  CHECK_NOT_NULL(interface_group->ignorelist_ethtool);
+  CHECK_NOT_NULL(interface_group->ignorelist_sysfs);
+  EXPECT_EQ_INT(24, interface_group->sysfs_metrics_size);
+  CHECK_NOT_NULL(interface_group->sysfs_metrics);
+  CHECK_NOT_NULL(interface_group->ethtool_metrics);
+
+  CHECK_NOT_NULL(interface_group->interfaces);
+  for (int i = 0; i < conf->values_num; i++) {
+    EXPECT_EQ_STR(conf->values[i].value.string, interface_group->interfaces[i]);
+  }
+  EXPECT_EQ_INT(1, interfaces_group_num);
+  EXPECT_EQ_INT(
+      0, ignorelist_match(interface_group->ignorelist_ethtool, "rx_bytes"));
+  EXPECT_EQ_INT(
+      0, ignorelist_match(interface_group->ignorelist_ethtool, "*x_bytes"));
+  EXPECT_EQ_INT(0,
+                ignorelist_match(interface_group->ignorelist_ethtool, "[.]*"));
+
+  sfree(interface_group->ignorelist_ethtool);
+  sfree(interface_group->ignorelist_sysfs);
+  sfree(interface_group->interfaces[0]);
+  sfree(interface_group->interfaces);
+  sfree(interface_group->sysfs_metrics);
+  sfree(interface_group->ethtool_metrics);
+  sfree(interface_group);
+
+  sfree(conf->values[0].value.string);
+  sfree(conf->values);
+  sfree(conf);
+  return 0;
+}
+
+DEF_TEST(ethstat_add_map) {
+  value_map_t *map = NULL;
+  oconfig_item_t *conf;
+  conf = (oconfig_item_t *)calloc(1, sizeof(oconfig_item_t));
+  conf->values_num = 2;
+  conf->values = (oconfig_value_t *)calloc(2, sizeof(oconfig_value_t));
+  conf->values[0].value.string = strdup("rx_bytes");
+  conf->values[1].value.string = strdup("RX-bytes");
+  ethstat_add_map(conf);
+  CHECK_NOT_NULL(value_map);
+  c_avl_get(value_map, "rx_bytes", (void *)&map);
+  EXPECT_EQ_STR("RX-bytes", map->type);
+  EXPECT_EQ_STR("", map->type_instance);
+  sfree(conf->values[0].value.string);
+  sfree(conf->values[1].value.string);
+  sfree(conf->values);
+  sfree(conf);
+
+  conf = (oconfig_item_t *)calloc(1, sizeof(oconfig_item_t));
+  conf->values_num = 3;
+  conf->values = (oconfig_value_t *)calloc(3, sizeof(oconfig_value_t));
+  conf->values[0].value.string = strdup("tx_bytes");
+  conf->values[1].value.string = strdup("TX-bytes");
+  conf->values[2].value.string = strdup("foo");
+  ethstat_add_map(conf);
+  CHECK_NOT_NULL(value_map);
+  c_avl_get(value_map, "tx_bytes", (void *)&map);
+  EXPECT_EQ_STR("TX-bytes", map->type);
+  EXPECT_EQ_STR("foo", map->type_instance);
+  sfree(conf->values[0].value.string);
+  sfree(conf->values[1].value.string);
+  sfree(conf->values[2].value.string);
+  sfree(conf->values);
+  sfree(conf);
+  void *key = NULL;
+  void *value = NULL;
+  while (c_avl_pick(value_map, &key, &value) == 0) {
+    sfree(key);
+    sfree(value);
+  }
+  c_avl_destroy(value_map);
+  value_map = NULL;
+  return 0;
+}
+
+DEF_TEST(read_sysfs_metrics) {
+
+  int i_ret = 0;
+  interface_metrics_t *interface_group;
+
+  interface_group =
+      (interface_metrics_t *)calloc(1, sizeof(interface_metrics_t));
+  oconfig_item_t *conf;
+  conf = (oconfig_item_t *)calloc(1, sizeof(oconfig_item_t));
+  conf->values_num = 1;
+  conf->values = (oconfig_value_t *)calloc(1, sizeof(oconfig_value_t));
+  conf->values[0].value.string = strdup("eno112");
+  create_new_interfaces_group(conf, interface_group);
+  interface_group->sysfs_metrics_num = 1;
+  interface_group->sysfs_metrics[0] = strdup("rx_bytes");
+
+  matric_in_file = 0;
+  i_ret = read_sysfs_metrics(interface_group->interfaces[0],
+                             interface_group->sysfs_metrics,
+                             interface_group->sysfs_metrics_num);
+  EXPECT_EQ_INT(0, i_ret);
+  EXPECT_EQ_INT(0, data_submit[data_submit_iterator]);
+
+  matric_in_file = 999;
+  i_ret = read_sysfs_metrics(interface_group->interfaces[0],
+                             interface_group->sysfs_metrics,
+                             interface_group->sysfs_metrics_num);
+  EXPECT_EQ_INT(0, i_ret);
+  EXPECT_EQ_INT(999, data_submit[data_submit_iterator]);
+
+  matric_in_file = 0xFFFFFFFFFFFFFFFF;
+  i_ret = read_sysfs_metrics(interface_group->interfaces[0],
+                             interface_group->sysfs_metrics,
+                             interface_group->sysfs_metrics_num);
+  EXPECT_EQ_INT(0, i_ret);
+  EXPECT_EQ_UINT64(0xFFFFFFFFFFFFFFFF, data_submit[data_submit_iterator]);
+
+  matric_in_file = 0xFFFFFFFFFFFFFFFF;
+  matric_in_file++;
+  i_ret = read_sysfs_metrics(interface_group->interfaces[0],
+                             interface_group->sysfs_metrics,
+                             interface_group->sysfs_metrics_num);
+  EXPECT_EQ_INT(0, i_ret);
+  EXPECT_EQ_UINT64(0, data_submit[data_submit_iterator]);
+
+  sfree(interface_group->sysfs_metrics[0]);
+  sfree(interface_group->sysfs_metrics);
+
+  sfree(conf->values[0].value.string);
+  sfree(conf->values);
+  sfree(conf);
+  sfree(interface_group->ignorelist_ethtool);
+  sfree(interface_group->ethtool_metrics);
+  sfree(interface_group->ignorelist_sysfs);
+  sfree(interface_group->interfaces[0]);
+  sfree(interface_group->interfaces);
+  sfree(interface_group);
+
+  return 0;
+}
+
+DEF_TEST(complete_list_of_metrics_read_by_ethtool) {
+  int i_ret;
+
+  interface_metrics_t *interface_group;
+
+  interface_group =
+      (interface_metrics_t *)calloc(1, sizeof(interface_metrics_t));
+  oconfig_item_t *conf;
+  conf = (oconfig_item_t *)calloc(1, sizeof(oconfig_item_t));
+  conf->values_num = 1;
+  conf->values = (oconfig_value_t *)calloc(1, sizeof(oconfig_value_t));
+  conf->values[0].value.string = strdup("eno112");
+  create_new_interfaces_group(conf, interface_group);
+
+  i_ret = complete_list_of_metrics_read_by_ethtool(
+      interface_group->interfaces[0], interface_group->ignorelist_ethtool,
+      &interface_group->ethtool_metrics[0]);
+  EXPECT_EQ_INT(0, i_ret);
+
+  ignorelist_free(interface_group->ignorelist_ethtool);
+  node_t *current = interface_group->ethtool_metrics[0];
+  while (current != NULL) {
+    node_t *to_remove = current;
+    current = current->next;
+    sfree(to_remove);
+  }
+  sfree(interface_group->ethtool_metrics);
+
+  sfree(interface_group->sysfs_metrics[0]);
+  sfree(interface_group->sysfs_metrics);
+
+  sfree(conf->values[0].value.string);
+  sfree(conf->values);
+  sfree(conf);
+  sfree(interface_group->ignorelist_sysfs);
+  sfree(interface_group->interfaces[0]);
+  sfree(interface_group->interfaces);
+  sfree(interface_group);
+
+  return 0;
+}
+
+DEF_TEST(ethstat_read_interface) {
+  int i_ret;
+
+  interface_metrics_t *interface_group;
+
+  interface_group =
+      (interface_metrics_t *)calloc(1, sizeof(interface_metrics_t));
+  oconfig_item_t *conf;
+  conf = (oconfig_item_t *)calloc(1, sizeof(oconfig_item_t));
+  conf->values_num = 1;
+  conf->values = (oconfig_value_t *)calloc(1, sizeof(oconfig_value_t));
+  conf->values[0].value.string = strdup("eno112");
+  create_new_interfaces_group(conf, interface_group);
+
+  i_ret = complete_list_of_metrics_read_by_ethtool(
+      interface_group->interfaces[0], interface_group->ignorelist_ethtool,
+      &interface_group->ethtool_metrics[0]);
+  EXPECT_EQ_INT(0, i_ret);
+  matric_in_ethtool = 1234;
+  i_ret = ethstat_read_interface(interface_group->interfaces[0],
+                                 interface_group->ethtool_metrics[0]);
+  EXPECT_EQ_INT(0, i_ret);
+  EXPECT_EQ_UINT64(1234, data_submit[data_submit_iterator]);
+
+  ignorelist_free(interface_group->ignorelist_ethtool);
+  node_t *current = interface_group->ethtool_metrics[0];
+  while (current != NULL) {
+    node_t *to_remove = current;
+    current = current->next;
+    sfree(to_remove);
+  }
+  sfree(interface_group->ethtool_metrics);
+
+  sfree(interface_group->sysfs_metrics[0]);
+  sfree(interface_group->sysfs_metrics);
+
+  sfree(conf->values[0].value.string);
+  sfree(conf->values);
+  sfree(conf);
+  sfree(interface_group->ignorelist_sysfs);
+  sfree(interface_group->interfaces[0]);
+  sfree(interface_group->interfaces);
+  sfree(interface_group);
+
+  return 0;
+}
+
+int main(void) {
+  RUN_TEST(getNewNode);
+  RUN_TEST(check_name);
+  RUN_TEST(add_readable_sysfs_metrics_to_ethtool_ignore_list);
+  RUN_TEST(create_new_interfaces_group);
+  RUN_TEST(ethstat_add_map);
+  RUN_TEST(add_sysfs_metric_to_readable);
+  RUN_TEST(read_sysfs_metrics);
+  RUN_TEST(complete_list_of_metrics_read_by_ethtool);
+  RUN_TEST(ethstat_read_interface);
+  END_TEST;
+}