From: Bartlomiej Kotlowski Date: Fri, 8 Oct 2021 10:42:31 +0000 (+0200) Subject: ethstat X-Git-Tag: 6.0.0-rc0~127 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6d90bf4c30a3c9c9b34b70c8b61e26df5fb4d987;p=thirdparty%2Fcollectd.git ethstat --- diff --git a/Makefile.am b/Makefile.am index 211c0447d..b3b04406c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/src/collectd.conf.in b/src/collectd.conf.in index 741c04765..c3918b54e 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -729,6 +729,9 @@ # # 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 diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 145284e1f..7b261c214 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -3266,12 +3266,16 @@ at most B<16384> to prevent typos and dumb mistakes. =head2 Plugin C The I collects information about network interface cards (NICs) -by talking directly with the underlying kernel driver using L. +by talking directly with the underlying kernel driver using L and +optional read metrics from sysfs. B Interface "eth0" + EthtoolExcludeMetrics "tx_packets" + UseSysClassNet true + SysClassNetExcludeMetrics "rx_packets" Map "rx_csum_offload_errors" "if_rx_errors" "checksum_offload" Map "multicast" "if_multicast" @@ -3282,7 +3286,40 @@ B =item B I -Collect statistical information about interface I. +Collect statistical information about interfaces I. All configuration +options below, up to the next interface tag, will apply to all interfaces +mentioned in this tag outside of mapping + +=item B I + +List of metrics passed by ethtool that will not be collected by the plugin +for the interface. + +=item B B|B + +When set to B, 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. + +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 I + +List of metrics passed by sysfs that will not be collected by the plugin +for the interface. =item B I I [I] diff --git a/src/ethstat.c b/src/ethstat.c index f8bc5b540..323668378 100644 --- a/src/ethstat.c +++ b/src/ethstat.c @@ -20,6 +20,7 @@ * Authors: * Cyril Feraudet * Florian "octo" Forster + * Bartlomiej Kotlowski **/ #include "collectd.h" @@ -27,7 +28,10 @@ #include "plugin.h" #include "utils/avltree/avltree.h" #include "utils/common/common.h" +#include "utils/ignorelist/ignorelist.h" #include "utils_complain.h" +#include +#include #if HAVE_SYS_IOCTL_H #include @@ -42,37 +46,292 @@ #include #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 index 000000000..66c87159d --- /dev/null +++ b/src/ethstat_test.c @@ -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 + **/ +#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; +}