371020c97b2b4a99cc1c790a7d4afb93239b7550
[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;
52 static size_t interfaces_num = 0;
53
54 static c_avl_tree_t *value_map;
55
56 static bool collect_mapped_only;
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     ERROR("ethstat plugin: Failed to open control socket: %s", STRERRNO);
208     return 1;
209   }
210
211   struct ethtool_drvinfo drvinfo = {.cmd = ETHTOOL_GDRVINFO};
212
213   struct ifreq req = {.ifr_data = (void *)&drvinfo};
214
215   sstrncpy(req.ifr_name, device, sizeof(req.ifr_name));
216
217   status = ioctl(fd, SIOCETHTOOL, &req);
218   if (status < 0) {
219     close(fd);
220     ERROR("ethstat plugin: Failed to get driver information "
221           "from %s: %s",
222           device, STRERRNO);
223     return -1;
224   }
225
226   n_stats = (size_t)drvinfo.n_stats;
227   if (n_stats < 1) {
228     close(fd);
229     ERROR("ethstat plugin: No stats available for %s", device);
230     return -1;
231   }
232
233   strings_size = sizeof(struct ethtool_gstrings) + (n_stats * ETH_GSTRING_LEN);
234   stats_size = sizeof(struct ethtool_stats) + (n_stats * sizeof(uint64_t));
235
236   strings = malloc(strings_size);
237   stats = malloc(stats_size);
238   if ((strings == NULL) || (stats == NULL)) {
239     close(fd);
240     sfree(strings);
241     sfree(stats);
242     ERROR("ethstat plugin: malloc failed.");
243     return -1;
244   }
245
246   strings->cmd = ETHTOOL_GSTRINGS;
247   strings->string_set = ETH_SS_STATS;
248   strings->len = n_stats;
249   req.ifr_data = (void *)strings;
250   status = ioctl(fd, SIOCETHTOOL, &req);
251   if (status < 0) {
252     close(fd);
253     free(strings);
254     free(stats);
255     ERROR("ethstat plugin: Cannot get strings from %s: %s", device, STRERRNO);
256     return -1;
257   }
258
259   stats->cmd = ETHTOOL_GSTATS;
260   stats->n_stats = n_stats;
261   req.ifr_data = (void *)stats;
262   status = ioctl(fd, SIOCETHTOOL, &req);
263   if (status < 0) {
264     close(fd);
265     free(strings);
266     free(stats);
267     ERROR("ethstat plugin: Reading statistics from %s failed: %s", device,
268           STRERRNO);
269     return -1;
270   }
271
272   for (size_t i = 0; i < n_stats; i++) {
273     char *stat_name;
274
275     stat_name = (void *)&strings->data[i * ETH_GSTRING_LEN];
276     /* Remove leading spaces in key name */
277     while (isspace((int)*stat_name))
278       stat_name++;
279
280     DEBUG("ethstat plugin: device = \"%s\": %s = %" PRIu64, device, stat_name,
281           (uint64_t)stats->data[i]);
282     ethstat_submit_value(device, stat_name, (derive_t)stats->data[i]);
283   }
284
285   close(fd);
286   sfree(strings);
287   sfree(stats);
288
289   return 0;
290 } /* }}} ethstat_read_interface */
291
292 static int ethstat_read(void) {
293   for (size_t i = 0; i < interfaces_num; i++)
294     ethstat_read_interface(interfaces[i]);
295
296   return 0;
297 }
298
299 static int ethstat_shutdown(void) {
300   void *key = NULL;
301   void *value = NULL;
302
303   if (value_map == NULL)
304     return 0;
305
306   while (c_avl_pick(value_map, &key, &value) == 0) {
307     sfree(key);
308     sfree(value);
309   }
310
311   c_avl_destroy(value_map);
312   value_map = NULL;
313
314   return 0;
315 }
316
317 void module_register(void) {
318   plugin_register_complex_config("ethstat", ethstat_config);
319   plugin_register_read("ethstat", ethstat_read);
320   plugin_register_shutdown("ethstat", ethstat_shutdown);
321 }