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 "configfile.h"
30 #include "utils_avltree.h"
31 #include "utils_complain.h"
33 #if HAVE_SYS_IOCTL_H
34 # include <sys/ioctl.h>
35 #endif
36 #if HAVE_NET_IF_H
37 # include <net/if.h>
38 #endif
39 #if HAVE_LINUX_SOCKIOS_H
40 # include <linux/sockios.h>
41 #endif
42 #if HAVE_LINUX_ETHTOOL_H
43 # include <linux/ethtool.h>
44 #endif
46 struct value_map_s
47 {
48 char type[DATA_MAX_NAME_LEN];
49 char type_instance[DATA_MAX_NAME_LEN];
50 };
51 typedef struct value_map_s value_map_t;
53 static char **interfaces = NULL;
54 static size_t interfaces_num = 0;
56 static c_avl_tree_t *value_map = NULL;
58 static _Bool collect_mapped_only = 0;
60 static int ethstat_add_interface (const oconfig_item_t *ci) /* {{{ */
61 {
62 char **tmp;
63 int status;
65 tmp = realloc (interfaces,
66 sizeof (*interfaces) * (interfaces_num + 1));
67 if (tmp == NULL)
68 return (-1);
69 interfaces = tmp;
70 interfaces[interfaces_num] = NULL;
72 status = cf_util_get_string (ci, interfaces + interfaces_num);
73 if (status != 0)
74 return (status);
76 interfaces_num++;
77 INFO("ethstat plugin: Registered interface %s",
78 interfaces[interfaces_num - 1]);
80 return (0);
81 } /* }}} int ethstat_add_interface */
83 static int ethstat_add_map (const oconfig_item_t *ci) /* {{{ */
84 {
85 value_map_t *map;
86 int status;
87 char *key;
89 if ((ci->values_num < 2)
90 || (ci->values_num > 3)
91 || (ci->values[0].type != OCONFIG_TYPE_STRING)
92 || (ci->values[1].type != OCONFIG_TYPE_STRING)
93 || ((ci->values_num == 3)
94 && (ci->values[2].type != OCONFIG_TYPE_STRING)))
95 {
96 ERROR ("ethstat plugin: The %s option requires "
97 "two or three string arguments.", ci->key);
98 return (-1);
99 }
101 key = strdup (ci->values[0].value.string);
102 if (key == NULL)
103 {
104 ERROR ("ethstat plugin: strdup(3) failed.");
105 return (ENOMEM);
106 }
108 map = calloc (1, sizeof (*map));
109 if (map == NULL)
110 {
111 sfree (key);
112 ERROR ("ethstat plugin: calloc failed.");
113 return (ENOMEM);
114 }
116 sstrncpy (map->type, ci->values[1].value.string, sizeof (map->type));
117 if (ci->values_num == 3)
118 sstrncpy (map->type_instance, ci->values[2].value.string,
119 sizeof (map->type_instance));
121 if (value_map == NULL)
122 {
123 value_map = c_avl_create ((int (*) (const void *, const void *)) strcmp);
124 if (value_map == NULL)
125 {
126 sfree (map);
127 sfree (key);
128 ERROR ("ethstat plugin: c_avl_create() failed.");
129 return (-1);
130 }
131 }
133 status = c_avl_insert (value_map,
134 /* key = */ key,
135 /* value = */ map);
136 if (status != 0)
137 {
138 if (status > 0)
139 ERROR ("ethstat plugin: Multiple mappings for \"%s\".", key);
140 else
141 ERROR ("ethstat plugin: c_avl_insert(\"%s\") failed.", key);
143 sfree (map);
144 sfree (key);
145 return (-1);
146 }
148 return (0);
149 } /* }}} int ethstat_add_map */
151 static int ethstat_config (oconfig_item_t *ci) /* {{{ */
152 {
153 int i;
155 for (i = 0; i < ci->children_num; i++)
156 {
157 oconfig_item_t *child = ci->children + i;
159 if (strcasecmp ("Interface", child->key) == 0)
160 ethstat_add_interface (child);
161 else if (strcasecmp ("Map", child->key) == 0)
162 ethstat_add_map (child);
163 else if (strcasecmp ("MappedOnly", child->key) == 0)
164 (void) cf_util_get_boolean (child, &collect_mapped_only);
165 else
166 WARNING ("ethstat plugin: The config option \"%s\" is unknown.",
167 child->key);
168 }
170 return (0);
171 } /* }}} */
173 static void ethstat_submit_value (const char *device,
174 const char *type_instance, derive_t value)
175 {
176 static c_complain_t complain_no_map = C_COMPLAIN_INIT_STATIC;
178 value_t values[1];
179 value_list_t vl = VALUE_LIST_INIT;
180 value_map_t *map = NULL;
182 if (value_map != NULL)
183 c_avl_get (value_map, type_instance, (void *) &map);
185 /* If the "MappedOnly" option is specified, ignore unmapped values. */
186 if (collect_mapped_only && (map == NULL))
187 {
188 if (value_map == NULL)
189 c_complain (LOG_WARNING, &complain_no_map,
190 "ethstat plugin: The \"MappedOnly\" option has been set to true, "
191 "but no mapping has been configured. All values will be ignored!");
192 return;
193 }
195 values[0].derive = value;
196 vl.values = values;
197 vl.values_len = 1;
199 sstrncpy (vl.host, hostname_g, sizeof (vl.host));
200 sstrncpy (vl.plugin, "ethstat", sizeof (vl.plugin));
201 sstrncpy (vl.plugin_instance, device, sizeof (vl.plugin_instance));
202 if (map != NULL)
203 {
204 sstrncpy (vl.type, map->type, sizeof (vl.type));
205 sstrncpy (vl.type_instance, map->type_instance,
206 sizeof (vl.type_instance));
207 }
208 else
209 {
210 sstrncpy (vl.type, "derive", sizeof (vl.type));
211 sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
212 }
214 plugin_dispatch_values (&vl);
215 }
217 static int ethstat_read_interface (char *device)
218 {
219 int fd;
220 struct ethtool_gstrings *strings;
221 struct ethtool_stats *stats;
222 size_t n_stats;
223 size_t strings_size;
224 size_t stats_size;
225 size_t i;
226 int status;
228 fd = socket(AF_INET, SOCK_DGRAM, /* protocol = */ 0);
229 if (fd < 0)
230 {
231 char errbuf[1024];
232 ERROR("ethstat plugin: Failed to open control socket: %s",
233 sstrerror (errno, errbuf, sizeof (errbuf)));
234 return 1;
235 }
237 struct ethtool_drvinfo drvinfo = {
238 .cmd = ETHTOOL_GDRVINFO
239 };
241 struct ifreq req = {
242 .ifr_data = (void *) &drvinfo
243 };
245 sstrncpy(req.ifr_name, device, sizeof (req.ifr_name));
247 status = ioctl (fd, SIOCETHTOOL, &req);
248 if (status < 0)
249 {
250 char errbuf[1024];
251 close (fd);
252 ERROR ("ethstat plugin: Failed to get driver information "
253 "from %s: %s", device,
254 sstrerror (errno, errbuf, sizeof (errbuf)));
255 return (-1);
256 }
258 n_stats = (size_t) drvinfo.n_stats;
259 if (n_stats < 1)
260 {
261 close (fd);
262 ERROR("ethstat plugin: No stats available for %s", device);
263 return (-1);
264 }
266 strings_size = sizeof (struct ethtool_gstrings)
267 + (n_stats * ETH_GSTRING_LEN);
268 stats_size = sizeof (struct ethtool_stats)
269 + (n_stats * sizeof (uint64_t));
271 strings = malloc (strings_size);
272 stats = malloc (stats_size);
273 if ((strings == NULL) || (stats == NULL))
274 {
275 close (fd);
276 sfree (strings);
277 sfree (stats);
278 ERROR("ethstat plugin: malloc failed.");
279 return (-1);
280 }
282 strings->cmd = ETHTOOL_GSTRINGS;
283 strings->string_set = ETH_SS_STATS;
284 strings->len = n_stats;
285 req.ifr_data = (void *) strings;
286 status = ioctl (fd, SIOCETHTOOL, &req);
287 if (status < 0)
288 {
289 char errbuf[1024];
290 close (fd);
291 free (strings);
292 free (stats);
293 ERROR ("ethstat plugin: Cannot get strings from %s: %s",
294 device,
295 sstrerror (errno, errbuf, sizeof (errbuf)));
296 return (-1);
297 }
299 stats->cmd = ETHTOOL_GSTATS;
300 stats->n_stats = n_stats;
301 req.ifr_data = (void *) stats;
302 status = ioctl (fd, SIOCETHTOOL, &req);
303 if (status < 0)
304 {
305 char errbuf[1024];
306 close (fd);
307 free(strings);
308 free(stats);
309 ERROR("ethstat plugin: Reading statistics from %s failed: %s",
310 device,
311 sstrerror (errno, errbuf, sizeof (errbuf)));
312 return (-1);
313 }
315 for (i = 0; i < n_stats; i++)
316 {
317 char *stat_name;
319 stat_name = (void *) &strings->data[i * ETH_GSTRING_LEN];
320 /* Remove leading spaces in key name */
321 while (isspace ((int) *stat_name))
322 stat_name++;
324 DEBUG("ethstat plugin: device = \"%s\": %s = %"PRIu64,
325 device, stat_name, (uint64_t) stats->data[i]);
326 ethstat_submit_value (device,
327 stat_name, (derive_t) stats->data[i]);
328 }
330 close (fd);
331 sfree (strings);
332 sfree (stats);
334 return (0);
335 } /* }}} ethstat_read_interface */
337 static int ethstat_read(void)
338 {
339 size_t i;
341 for (i = 0; i < interfaces_num; i++)
342 ethstat_read_interface (interfaces[i]);
344 return 0;
345 }
347 static int ethstat_shutdown (void)
348 {
349 void *key = NULL;
350 void *value = NULL;
352 if (value_map == NULL)
353 return (0);
355 while (c_avl_pick (value_map, &key, &value) == 0)
356 {
357 sfree (key);
358 sfree (value);
359 }
361 c_avl_destroy (value_map);
362 value_map = NULL;
364 return (0);
365 }
367 void module_register (void)
368 {
369 plugin_register_complex_config ("ethstat", ethstat_config);
370 plugin_register_read ("ethstat", ethstat_read);
371 plugin_register_shutdown ("ethstat", ethstat_shutdown);
372 }
374 /* vim: set sw=2 sts=2 et fdm=marker : */