Merge branch 'collectd-5.7' into collectd-5.8
[collectd.git] / src / ethstat.c
1 /**
2  * collectd - src/ethstat.c
3  * Copyright (C) 2011       Cyril Feraudet
4  * Copyright (C) 2012       Florian "octo" Forster
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; either version 2 of the License, or (at your
9  * option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19  *
20  * Authors:
21  *   Cyril Feraudet <cyril at feraudet.com>
22  *   Florian "octo" Forster <octo@collectd.org>
23  **/
24
25 #include "collectd.h"
26
27 #include "common.h"
28 #include "plugin.h"
29 #include "utils_avltree.h"
30 #include "utils_complain.h"
31
32 #if HAVE_SYS_IOCTL_H
33 #include <sys/ioctl.h>
34 #endif
35 #if HAVE_NET_IF_H
36 #include <net/if.h>
37 #endif
38 #if HAVE_LINUX_SOCKIOS_H
39 #include <linux/sockios.h>
40 #endif
41 #if HAVE_LINUX_ETHTOOL_H
42 #include <linux/ethtool.h>
43 #endif
44
45 struct value_map_s {
46   char type[DATA_MAX_NAME_LEN];
47   char type_instance[DATA_MAX_NAME_LEN];
48 };
49 typedef struct value_map_s value_map_t;
50
51 static char **interfaces = NULL;
52 static size_t interfaces_num = 0;
53
54 static c_avl_tree_t *value_map = NULL;
55
56 static _Bool collect_mapped_only = 0;
57
58 static int ethstat_add_interface(const oconfig_item_t *ci) /* {{{ */
59 {
60   char **tmp;
61   int status;
62
63   tmp = realloc(interfaces, sizeof(*interfaces) * (interfaces_num + 1));
64   if (tmp == NULL)
65     return -1;
66   interfaces = tmp;
67   interfaces[interfaces_num] = NULL;
68
69   status = cf_util_get_string(ci, interfaces + interfaces_num);
70   if (status != 0)
71     return status;
72
73   interfaces_num++;
74   INFO("ethstat plugin: Registered interface %s",
75        interfaces[interfaces_num - 1]);
76
77   return 0;
78 } /* }}} int ethstat_add_interface */
79
80 static int ethstat_add_map(const oconfig_item_t *ci) /* {{{ */
81 {
82   value_map_t *map;
83   int status;
84   char *key;
85
86   if ((ci->values_num < 2) || (ci->values_num > 3) ||
87       (ci->values[0].type != OCONFIG_TYPE_STRING) ||
88       (ci->values[1].type != OCONFIG_TYPE_STRING) ||
89       ((ci->values_num == 3) && (ci->values[2].type != OCONFIG_TYPE_STRING))) {
90     ERROR("ethstat plugin: The %s option requires "
91           "two or three string arguments.",
92           ci->key);
93     return -1;
94   }
95
96   key = strdup(ci->values[0].value.string);
97   if (key == NULL) {
98     ERROR("ethstat plugin: strdup(3) failed.");
99     return ENOMEM;
100   }
101
102   map = calloc(1, sizeof(*map));
103   if (map == NULL) {
104     sfree(key);
105     ERROR("ethstat plugin: calloc failed.");
106     return ENOMEM;
107   }
108
109   sstrncpy(map->type, ci->values[1].value.string, sizeof(map->type));
110   if (ci->values_num == 3)
111     sstrncpy(map->type_instance, ci->values[2].value.string,
112              sizeof(map->type_instance));
113
114   if (value_map == NULL) {
115     value_map = c_avl_create((int (*)(const void *, const void *))strcmp);
116     if (value_map == NULL) {
117       sfree(map);
118       sfree(key);
119       ERROR("ethstat plugin: c_avl_create() failed.");
120       return -1;
121     }
122   }
123
124   status = c_avl_insert(value_map,
125                         /* key = */ key,
126                         /* value = */ map);
127   if (status != 0) {
128     if (status > 0)
129       ERROR("ethstat plugin: Multiple mappings for \"%s\".", key);
130     else
131       ERROR("ethstat plugin: c_avl_insert(\"%s\") failed.", key);
132
133     sfree(map);
134     sfree(key);
135     return -1;
136   }
137
138   return 0;
139 } /* }}} int ethstat_add_map */
140
141 static int ethstat_config(oconfig_item_t *ci) /* {{{ */
142 {
143   for (int i = 0; i < ci->children_num; i++) {
144     oconfig_item_t *child = ci->children + i;
145
146     if (strcasecmp("Interface", child->key) == 0)
147       ethstat_add_interface(child);
148     else if (strcasecmp("Map", child->key) == 0)
149       ethstat_add_map(child);
150     else if (strcasecmp("MappedOnly", child->key) == 0)
151       (void)cf_util_get_boolean(child, &collect_mapped_only);
152     else
153       WARNING("ethstat plugin: The config option \"%s\" is unknown.",
154               child->key);
155   }
156
157   return 0;
158 } /* }}} */
159
160 static void ethstat_submit_value(const char *device, const char *type_instance,
161                                  derive_t value) {
162   static c_complain_t complain_no_map = C_COMPLAIN_INIT_STATIC;
163
164   value_list_t vl = VALUE_LIST_INIT;
165   value_map_t *map = NULL;
166
167   if (value_map != NULL)
168     c_avl_get(value_map, type_instance, (void *)&map);
169
170   /* If the "MappedOnly" option is specified, ignore unmapped values. */
171   if (collect_mapped_only && (map == NULL)) {
172     if (value_map == NULL)
173       c_complain(
174           LOG_WARNING, &complain_no_map,
175           "ethstat plugin: The \"MappedOnly\" option has been set to true, "
176           "but no mapping has been configured. All values will be ignored!");
177     return;
178   }
179
180   vl.values = &(value_t){.derive = value};
181   vl.values_len = 1;
182
183   sstrncpy(vl.plugin, "ethstat", sizeof(vl.plugin));
184   sstrncpy(vl.plugin_instance, device, sizeof(vl.plugin_instance));
185   if (map != NULL) {
186     sstrncpy(vl.type, map->type, sizeof(vl.type));
187     sstrncpy(vl.type_instance, map->type_instance, sizeof(vl.type_instance));
188   } else {
189     sstrncpy(vl.type, "derive", sizeof(vl.type));
190     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
191   }
192
193   plugin_dispatch_values(&vl);
194 }
195
196 static int ethstat_read_interface(char *device) {
197   int fd;
198   struct ethtool_gstrings *strings;
199   struct ethtool_stats *stats;
200   size_t n_stats;
201   size_t strings_size;
202   size_t stats_size;
203   int status;
204
205   fd = socket(AF_INET, SOCK_DGRAM, /* protocol = */ 0);
206   if (fd < 0) {
207     char errbuf[1024];
208     ERROR("ethstat plugin: Failed to open control socket: %s",
209           sstrerror(errno, errbuf, sizeof(errbuf)));
210     return 1;
211   }
212
213   struct ethtool_drvinfo drvinfo = {.cmd = ETHTOOL_GDRVINFO};
214
215   struct ifreq req = {.ifr_data = (void *)&drvinfo};
216
217   sstrncpy(req.ifr_name, device, sizeof(req.ifr_name));
218
219   status = ioctl(fd, SIOCETHTOOL, &req);
220   if (status < 0) {
221     char errbuf[1024];
222     close(fd);
223     ERROR("ethstat plugin: Failed to get driver information "
224           "from %s: %s",
225           device, sstrerror(errno, errbuf, sizeof(errbuf)));
226     return -1;
227   }
228
229   n_stats = (size_t)drvinfo.n_stats;
230   if (n_stats < 1) {
231     close(fd);
232     ERROR("ethstat plugin: No stats available for %s", device);
233     return -1;
234   }
235
236   strings_size = sizeof(struct ethtool_gstrings) + (n_stats * ETH_GSTRING_LEN);
237   stats_size = sizeof(struct ethtool_stats) + (n_stats * sizeof(uint64_t));
238
239   strings = malloc(strings_size);
240   stats = malloc(stats_size);
241   if ((strings == NULL) || (stats == NULL)) {
242     close(fd);
243     sfree(strings);
244     sfree(stats);
245     ERROR("ethstat plugin: malloc failed.");
246     return -1;
247   }
248
249   strings->cmd = ETHTOOL_GSTRINGS;
250   strings->string_set = ETH_SS_STATS;
251   strings->len = n_stats;
252   req.ifr_data = (void *)strings;
253   status = ioctl(fd, SIOCETHTOOL, &req);
254   if (status < 0) {
255     char errbuf[1024];
256     close(fd);
257     free(strings);
258     free(stats);
259     ERROR("ethstat plugin: Cannot get strings from %s: %s", device,
260           sstrerror(errno, errbuf, sizeof(errbuf)));
261     return -1;
262   }
263
264   stats->cmd = ETHTOOL_GSTATS;
265   stats->n_stats = n_stats;
266   req.ifr_data = (void *)stats;
267   status = ioctl(fd, SIOCETHTOOL, &req);
268   if (status < 0) {
269     char errbuf[1024];
270     close(fd);
271     free(strings);
272     free(stats);
273     ERROR("ethstat plugin: Reading statistics from %s failed: %s", device,
274           sstrerror(errno, errbuf, sizeof(errbuf)));
275     return -1;
276   }
277
278   for (size_t i = 0; i < n_stats; i++) {
279     char *stat_name;
280
281     stat_name = (void *)&strings->data[i * ETH_GSTRING_LEN];
282     /* Remove leading spaces in key name */
283     while (isspace((int)*stat_name))
284       stat_name++;
285
286     DEBUG("ethstat plugin: device = \"%s\": %s = %" PRIu64, device, stat_name,
287           (uint64_t)stats->data[i]);
288     ethstat_submit_value(device, stat_name, (derive_t)stats->data[i]);
289   }
290
291   close(fd);
292   sfree(strings);
293   sfree(stats);
294
295   return 0;
296 } /* }}} ethstat_read_interface */
297
298 static int ethstat_read(void) {
299   for (size_t i = 0; i < interfaces_num; i++)
300     ethstat_read_interface(interfaces[i]);
301
302   return 0;
303 }
304
305 static int ethstat_shutdown(void) {
306   void *key = NULL;
307   void *value = NULL;
308
309   if (value_map == NULL)
310     return 0;
311
312   while (c_avl_pick(value_map, &key, &value) == 0) {
313     sfree(key);
314     sfree(value);
315   }
316
317   c_avl_destroy(value_map);
318   value_map = NULL;
319
320   return 0;
321 }
322
323 void module_register(void) {
324   plugin_register_complex_config("ethstat", ethstat_config);
325   plugin_register_read("ethstat", ethstat_read);
326   plugin_register_shutdown("ethstat", ethstat_shutdown);
327 }