* 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 */
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)) {
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;
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;
}
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;
}
--- /dev/null
+/**
+ * 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;
+}