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 **/
25 #include "collectd.h"
27 #include "common.h"
28 #include "plugin.h"
29 #include "utils_avltree.h"
30 #include "utils_complain.h"
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
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;
51 static char **interfaces = NULL;
52 static size_t interfaces_num = 0;
54 static c_avl_tree_t *value_map = NULL;
56 static _Bool collect_mapped_only = 0;
58 static int ethstat_add_interface(const oconfig_item_t *ci) /* {{{ */
59 {
60 char **tmp;
61 int status;
63 tmp = realloc(interfaces, sizeof(*interfaces) * (interfaces_num + 1));
64 if (tmp == NULL)
65 return (-1);
66 interfaces = tmp;
67 interfaces[interfaces_num] = NULL;
69 status = cf_util_get_string(ci, interfaces + interfaces_num);
70 if (status != 0)
71 return (status);
73 interfaces_num++;
74 INFO("ethstat plugin: Registered interface %s",
75 interfaces[interfaces_num - 1]);
77 return (0);
78 } /* }}} int ethstat_add_interface */
80 static int ethstat_add_map(const oconfig_item_t *ci) /* {{{ */
81 {
82 value_map_t *map;
83 int status;
84 char *key;
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 }
96 key = strdup(ci->values[0].value.string);
97 if (key == NULL) {
98 ERROR("ethstat plugin: strdup(3) failed.");
99 return (ENOMEM);
100 }
102 map = calloc(1, sizeof(*map));
103 if (map == NULL) {
104 sfree(key);
105 ERROR("ethstat plugin: calloc failed.");
106 return (ENOMEM);
107 }
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));
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 }
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);
133 sfree(map);
134 sfree(key);
135 return (-1);
136 }
138 return (0);
139 } /* }}} int ethstat_add_map */
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;
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 }
157 return (0);
158 } /* }}} */
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;
164 value_t values[1];
165 value_list_t vl = VALUE_LIST_INIT;
166 value_map_t *map = NULL;
168 if (value_map != NULL)
169 c_avl_get(value_map, type_instance, (void *)&map);
171 /* If the "MappedOnly" option is specified, ignore unmapped values. */
172 if (collect_mapped_only && (map == NULL)) {
173 if (value_map == NULL)
174 c_complain(
175 LOG_WARNING, &complain_no_map,
176 "ethstat plugin: The \"MappedOnly\" option has been set to true, "
177 "but no mapping has been configured. All values will be ignored!");
178 return;
179 }
181 values[0].derive = value;
182 vl.values = values;
183 vl.values_len = 1;
185 sstrncpy(vl.host, hostname_g, sizeof(vl.host));
186 sstrncpy(vl.plugin, "ethstat", sizeof(vl.plugin));
187 sstrncpy(vl.plugin_instance, device, sizeof(vl.plugin_instance));
188 if (map != NULL) {
189 sstrncpy(vl.type, map->type, sizeof(vl.type));
190 sstrncpy(vl.type_instance, map->type_instance, sizeof(vl.type_instance));
191 } else {
192 sstrncpy(vl.type, "derive", sizeof(vl.type));
193 sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
194 }
196 plugin_dispatch_values(&vl);
197 }
199 static int ethstat_read_interface(char *device) {
200 int fd;
201 struct ethtool_gstrings *strings;
202 struct ethtool_stats *stats;
203 size_t n_stats;
204 size_t strings_size;
205 size_t stats_size;
206 int status;
208 fd = socket(AF_INET, SOCK_DGRAM, /* protocol = */ 0);
209 if (fd < 0) {
210 char errbuf[1024];
211 ERROR("ethstat plugin: Failed to open control socket: %s",
212 sstrerror(errno, errbuf, sizeof(errbuf)));
213 return 1;
214 }
216 struct ethtool_drvinfo drvinfo = {.cmd = ETHTOOL_GDRVINFO};
218 struct ifreq req = {.ifr_data = (void *)&drvinfo};
220 sstrncpy(req.ifr_name, device, sizeof(req.ifr_name));
222 status = ioctl(fd, SIOCETHTOOL, &req);
223 if (status < 0) {
224 char errbuf[1024];
225 close(fd);
226 ERROR("ethstat plugin: Failed to get driver information "
227 "from %s: %s",
228 device, sstrerror(errno, errbuf, sizeof(errbuf)));
229 return (-1);
230 }
232 n_stats = (size_t)drvinfo.n_stats;
233 if (n_stats < 1) {
234 close(fd);
235 ERROR("ethstat plugin: No stats available for %s", device);
236 return (-1);
237 }
239 strings_size = sizeof(struct ethtool_gstrings) + (n_stats * ETH_GSTRING_LEN);
240 stats_size = sizeof(struct ethtool_stats) + (n_stats * sizeof(uint64_t));
242 strings = malloc(strings_size);
243 stats = malloc(stats_size);
244 if ((strings == NULL) || (stats == NULL)) {
245 close(fd);
246 sfree(strings);
247 sfree(stats);
248 ERROR("ethstat plugin: malloc failed.");
249 return (-1);
250 }
252 strings->cmd = ETHTOOL_GSTRINGS;
253 strings->string_set = ETH_SS_STATS;
254 strings->len = n_stats;
255 req.ifr_data = (void *)strings;
256 status = ioctl(fd, SIOCETHTOOL, &req);
257 if (status < 0) {
258 char errbuf[1024];
259 close(fd);
260 free(strings);
261 free(stats);
262 ERROR("ethstat plugin: Cannot get strings from %s: %s", device,
263 sstrerror(errno, errbuf, sizeof(errbuf)));
264 return (-1);
265 }
267 stats->cmd = ETHTOOL_GSTATS;
268 stats->n_stats = n_stats;
269 req.ifr_data = (void *)stats;
270 status = ioctl(fd, SIOCETHTOOL, &req);
271 if (status < 0) {
272 char errbuf[1024];
273 close(fd);
274 free(strings);
275 free(stats);
276 ERROR("ethstat plugin: Reading statistics from %s failed: %s", device,
277 sstrerror(errno, errbuf, sizeof(errbuf)));
278 return (-1);
279 }
281 for (size_t i = 0; i < n_stats; i++) {
282 char *stat_name;
284 stat_name = (void *)&strings->data[i * ETH_GSTRING_LEN];
285 /* Remove leading spaces in key name */
286 while (isspace((int)*stat_name))
287 stat_name++;
289 DEBUG("ethstat plugin: device = \"%s\": %s = %" PRIu64, device, stat_name,
290 (uint64_t)stats->data[i]);
291 ethstat_submit_value(device, stat_name, (derive_t)stats->data[i]);
292 }
294 close(fd);
295 sfree(strings);
296 sfree(stats);
298 return (0);
299 } /* }}} ethstat_read_interface */
301 static int ethstat_read(void) {
302 for (size_t i = 0; i < interfaces_num; i++)
303 ethstat_read_interface(interfaces[i]);
305 return 0;
306 }
308 static int ethstat_shutdown(void) {
309 void *key = NULL;
310 void *value = NULL;
312 if (value_map == NULL)
313 return (0);
315 while (c_avl_pick(value_map, &key, &value) == 0) {
316 sfree(key);
317 sfree(value);
318 }
320 c_avl_destroy(value_map);
321 value_map = NULL;
323 return (0);
324 }
326 void module_register(void) {
327 plugin_register_complex_config("ethstat", ethstat_config);
328 plugin_register_read("ethstat", ethstat_read);
329 plugin_register_shutdown("ethstat", ethstat_shutdown);
330 }
332 /* vim: set sw=2 sts=2 et fdm=marker : */