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"
26 #include "common.h"
27 #include "plugin.h"
28 #include "configfile.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 {
47 char type[DATA_MAX_NAME_LEN];
48 char type_instance[DATA_MAX_NAME_LEN];
49 };
50 typedef struct value_map_s value_map_t;
52 static char **interfaces = NULL;
53 static size_t interfaces_num = 0;
55 static c_avl_tree_t *value_map = NULL;
57 static _Bool collect_mapped_only = 0;
59 static int ethstat_add_interface (const oconfig_item_t *ci) /* {{{ */
60 {
61 char **tmp;
62 int status;
64 tmp = realloc (interfaces,
65 sizeof (*interfaces) * (interfaces_num + 1));
66 if (tmp == NULL)
67 return (-1);
68 interfaces = tmp;
69 interfaces[interfaces_num] = NULL;
71 status = cf_util_get_string (ci, interfaces + interfaces_num);
72 if (status != 0)
73 return (status);
75 interfaces_num++;
76 INFO("ethstat plugin: Registered interface %s",
77 interfaces[interfaces_num - 1]);
79 return (0);
80 } /* }}} int ethstat_add_interface */
82 static int ethstat_add_map (const oconfig_item_t *ci) /* {{{ */
83 {
84 value_map_t *map;
85 int status;
86 char *key;
88 if ((ci->values_num < 2)
89 || (ci->values_num > 3)
90 || (ci->values[0].type != OCONFIG_TYPE_STRING)
91 || (ci->values[1].type != OCONFIG_TYPE_STRING)
92 || ((ci->values_num == 3)
93 && (ci->values[2].type != OCONFIG_TYPE_STRING)))
94 {
95 ERROR ("ethstat plugin: The %s option requires "
96 "two or three string arguments.", ci->key);
97 return (-1);
98 }
100 key = strdup (ci->values[0].value.string);
101 if (key == NULL)
102 {
103 ERROR ("ethstat plugin: strdup(3) failed.");
104 return (ENOMEM);
105 }
107 map = calloc (1, sizeof (*map));
108 if (map == NULL)
109 {
110 sfree (key);
111 ERROR ("ethstat plugin: calloc failed.");
112 return (ENOMEM);
113 }
115 sstrncpy (map->type, ci->values[1].value.string, sizeof (map->type));
116 if (ci->values_num == 3)
117 sstrncpy (map->type_instance, ci->values[2].value.string,
118 sizeof (map->type_instance));
120 if (value_map == NULL)
121 {
122 value_map = c_avl_create ((int (*) (const void *, const void *)) strcmp);
123 if (value_map == NULL)
124 {
125 sfree (map);
126 sfree (key);
127 ERROR ("ethstat plugin: c_avl_create() failed.");
128 return (-1);
129 }
130 }
132 status = c_avl_insert (value_map,
133 /* key = */ key,
134 /* value = */ map);
135 if (status != 0)
136 {
137 if (status > 0)
138 ERROR ("ethstat plugin: Multiple mappings for \"%s\".", key);
139 else
140 ERROR ("ethstat plugin: c_avl_insert(\"%s\") failed.", key);
142 sfree (map);
143 sfree (key);
144 return (-1);
145 }
147 return (0);
148 } /* }}} int ethstat_add_map */
150 static int ethstat_config (oconfig_item_t *ci) /* {{{ */
151 {
152 int i;
154 for (i = 0; i < ci->children_num; i++)
155 {
156 oconfig_item_t *child = ci->children + i;
158 if (strcasecmp ("Interface", child->key) == 0)
159 ethstat_add_interface (child);
160 else if (strcasecmp ("Map", child->key) == 0)
161 ethstat_add_map (child);
162 else if (strcasecmp ("MappedOnly", child->key) == 0)
163 (void) cf_util_get_boolean (child, &collect_mapped_only);
164 else
165 WARNING ("ethstat plugin: The config option \"%s\" is unknown.",
166 child->key);
167 }
169 return (0);
170 } /* }}} */
172 static void ethstat_submit_value (const char *device,
173 const char *type_instance, derive_t value)
174 {
175 static c_complain_t complain_no_map = C_COMPLAIN_INIT_STATIC;
177 value_t values[1];
178 value_list_t vl = VALUE_LIST_INIT;
179 value_map_t *map = NULL;
181 if (value_map != NULL)
182 c_avl_get (value_map, type_instance, (void *) &map);
184 /* If the "MappedOnly" option is specified, ignore unmapped values. */
185 if (collect_mapped_only && (map == NULL))
186 {
187 if (value_map == NULL)
188 c_complain (LOG_WARNING, &complain_no_map,
189 "ethstat plugin: The \"MappedOnly\" option has been set to true, "
190 "but no mapping has been configured. All values will be ignored!");
191 return;
192 }
194 values[0].derive = value;
195 vl.values = values;
196 vl.values_len = 1;
198 sstrncpy (vl.host, hostname_g, sizeof (vl.host));
199 sstrncpy (vl.plugin, "ethstat", sizeof (vl.plugin));
200 sstrncpy (vl.plugin_instance, device, sizeof (vl.plugin_instance));
201 if (map != NULL)
202 {
203 sstrncpy (vl.type, map->type, sizeof (vl.type));
204 sstrncpy (vl.type_instance, map->type_instance,
205 sizeof (vl.type_instance));
206 }
207 else
208 {
209 sstrncpy (vl.type, "derive", sizeof (vl.type));
210 sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
211 }
213 plugin_dispatch_values (&vl);
214 }
216 static int ethstat_read_interface (char *device)
217 {
218 int fd;
219 struct ethtool_gstrings *strings;
220 struct ethtool_stats *stats;
221 size_t n_stats;
222 size_t strings_size;
223 size_t stats_size;
224 size_t i;
225 int status;
227 fd = socket(AF_INET, SOCK_DGRAM, /* protocol = */ 0);
228 if (fd < 0)
229 {
230 char errbuf[1024];
231 ERROR("ethstat plugin: Failed to open control socket: %s",
232 sstrerror (errno, errbuf, sizeof (errbuf)));
233 return 1;
234 }
236 struct ethtool_drvinfo drvinfo = {
237 .cmd = ETHTOOL_GDRVINFO
238 };
240 struct ifreq req = {
241 .ifr_data = (void *) &drvinfo
242 };
244 sstrncpy(req.ifr_name, device, sizeof (req.ifr_name));
246 status = ioctl (fd, SIOCETHTOOL, &req);
247 if (status < 0)
248 {
249 char errbuf[1024];
250 close (fd);
251 ERROR ("ethstat plugin: Failed to get driver information "
252 "from %s: %s", device,
253 sstrerror (errno, errbuf, sizeof (errbuf)));
254 return (-1);
255 }
257 n_stats = (size_t) drvinfo.n_stats;
258 if (n_stats < 1)
259 {
260 close (fd);
261 ERROR("ethstat plugin: No stats available for %s", device);
262 return (-1);
263 }
265 strings_size = sizeof (struct ethtool_gstrings)
266 + (n_stats * ETH_GSTRING_LEN);
267 stats_size = sizeof (struct ethtool_stats)
268 + (n_stats * sizeof (uint64_t));
270 strings = malloc (strings_size);
271 stats = malloc (stats_size);
272 if ((strings == NULL) || (stats == NULL))
273 {
274 close (fd);
275 sfree (strings);
276 sfree (stats);
277 ERROR("ethstat plugin: malloc failed.");
278 return (-1);
279 }
281 strings->cmd = ETHTOOL_GSTRINGS;
282 strings->string_set = ETH_SS_STATS;
283 strings->len = n_stats;
284 req.ifr_data = (void *) strings;
285 status = ioctl (fd, SIOCETHTOOL, &req);
286 if (status < 0)
287 {
288 char errbuf[1024];
289 close (fd);
290 free (strings);
291 free (stats);
292 ERROR ("ethstat plugin: Cannot get strings from %s: %s",
293 device,
294 sstrerror (errno, errbuf, sizeof (errbuf)));
295 return (-1);
296 }
298 stats->cmd = ETHTOOL_GSTATS;
299 stats->n_stats = n_stats;
300 req.ifr_data = (void *) stats;
301 status = ioctl (fd, SIOCETHTOOL, &req);
302 if (status < 0)
303 {
304 char errbuf[1024];
305 close (fd);
306 free(strings);
307 free(stats);
308 ERROR("ethstat plugin: Reading statistics from %s failed: %s",
309 device,
310 sstrerror (errno, errbuf, sizeof (errbuf)));
311 return (-1);
312 }
314 for (i = 0; i < n_stats; i++)
315 {
316 char *stat_name;
318 stat_name = (void *) &strings->data[i * ETH_GSTRING_LEN];
319 /* Remove leading spaces in key name */
320 while (isspace ((int) *stat_name))
321 stat_name++;
323 DEBUG("ethstat plugin: device = \"%s\": %s = %"PRIu64,
324 device, stat_name, (uint64_t) stats->data[i]);
325 ethstat_submit_value (device,
326 stat_name, (derive_t) stats->data[i]);
327 }
329 close (fd);
330 sfree (strings);
331 sfree (stats);
333 return (0);
334 } /* }}} ethstat_read_interface */
336 static int ethstat_read(void)
337 {
338 size_t i;
340 for (i = 0; i < interfaces_num; i++)
341 ethstat_read_interface (interfaces[i]);
343 return 0;
344 }
346 static int ethstat_shutdown (void)
347 {
348 void *key = NULL;
349 void *value = NULL;
351 if (value_map == NULL)
352 return (0);
354 while (c_avl_pick (value_map, &key, &value) == 0)
355 {
356 sfree (key);
357 sfree (value);
358 }
360 c_avl_destroy (value_map);
361 value_map = NULL;
363 return (0);
364 }
366 void module_register (void)
367 {
368 plugin_register_complex_config ("ethstat", ethstat_config);
369 plugin_register_read ("ethstat", ethstat_read);
370 plugin_register_shutdown ("ethstat", ethstat_shutdown);
371 }
373 /* vim: set sw=2 sts=2 et fdm=marker : */