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;
70 status = cf_util_get_string (ci, interfaces + interfaces_num);
71 if (status != 0)
72 return (status);
74 interfaces_num++;
75 INFO("ethstat plugin: Registred interface %s",
76 interfaces[interfaces_num - 1]);
78 return (0);
79 } /* }}} int ethstat_add_interface */
81 static int ethstat_add_map (const oconfig_item_t *ci) /* {{{ */
82 {
83 value_map_t *map;
84 int status;
86 if ((ci->values_num < 2)
87 || (ci->values_num > 3)
88 || (ci->values[0].type != OCONFIG_TYPE_STRING)
89 || (ci->values[1].type != OCONFIG_TYPE_STRING)
90 || ((ci->values_num == 3)
91 && (ci->values[2].type != OCONFIG_TYPE_STRING)))
92 {
93 ERROR ("ethstat plugin: The %s option requires "
94 "two or three string arguments.", ci->key);
95 return (-1);
96 }
98 map = malloc (sizeof (*map));
99 if (map == NULL)
100 {
101 ERROR ("ethstat plugin: malloc(3) failed.");
102 return (ENOMEM);
103 }
104 memset (map, 0, sizeof (*map));
106 sstrncpy (map->type, ci->values[1].value.string, sizeof (map->type));
107 if (ci->values_num == 2)
108 sstrncpy (map->type_instance, ci->values[2].value.string,
109 sizeof (map->type_instance));
111 if (value_map == NULL)
112 {
113 value_map = c_avl_create ((void *) strcmp);
114 if (value_map == NULL)
115 {
116 sfree (map);
117 ERROR ("ethstat plugin: c_avl_create() failed.");
118 return (-1);
119 }
120 }
122 status = c_avl_insert (value_map,
123 /* key = */ ci->values[0].value.string,
124 /* value = */ map);
125 if (status != 0)
126 {
127 sfree (map);
128 if (status > 0)
129 ERROR ("ethstat plugin: Multiple mappings for \"%s\".",
130 ci->values[0].value.string);
131 else
132 ERROR ("ethstat plugin: c_avl_insert(\"%s\") failed.",
133 ci->values[0].value.string);
134 return (-1);
135 }
137 return (0);
138 } /* }}} int ethstat_add_map */
140 static int ethstat_config (oconfig_item_t *ci) /* {{{ */
141 {
142 int i;
144 for (i = 0; i < ci->children_num; i++)
145 {
146 oconfig_item_t *child = ci->children + i;
148 if (strcasecmp ("Interface", child->key) == 0)
149 ethstat_add_interface (child);
150 else if (strcasecmp ("Map", child->key) == 0)
151 ethstat_add_map (child);
152 else if (strcasecmp ("MappedOnly", child->key) == 0)
153 (void) cf_util_get_boolean (child, &collect_mapped_only);
154 else
155 WARNING ("ethstat plugin: The config option \"%s\" is unknown.",
156 child->key);
157 }
159 return (0);
160 } /* }}} */
162 static void ethstat_submit_value (const char *device,
163 const char *type_instance, derive_t value)
164 {
165 static c_complain_t complain_no_map = C_COMPLAIN_INIT_STATIC;
167 value_t values[1];
168 value_list_t vl = VALUE_LIST_INIT;
169 value_map_t *map = NULL;
171 if (value_map != NULL)
172 c_avl_get (value_map, type_instance, (void *) &map);
174 /* If the "MappedOnly" option is specified, ignore unmapped values. */
175 if (collect_mapped_only && (map == NULL))
176 {
177 if (value_map == NULL)
178 c_complain (LOG_WARNING, &complain_no_map,
179 "ethstat plugin: The \"MappedOnly\" option has been set to true, "
180 "but no mapping has been configured. All values will be ignored!");
181 return;
182 }
184 values[0].derive = value;
185 vl.values = values;
186 vl.values_len = 1;
188 sstrncpy (vl.host, hostname_g, sizeof (vl.host));
189 sstrncpy (vl.plugin, "ethstat", sizeof (vl.plugin));
190 sstrncpy (vl.plugin_instance, device, sizeof (vl.plugin_instance));
191 if (map != NULL)
192 {
193 sstrncpy (vl.type, map->type, sizeof (vl.type));
194 sstrncpy (vl.type_instance, map->type_instance,
195 sizeof (vl.type_instance));
196 }
197 else
198 {
199 sstrncpy (vl.type, "derive", sizeof (vl.type));
200 sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
201 }
203 plugin_dispatch_values (&vl);
204 }
206 static int ethstat_read_interface (char *device)
207 {
208 int fd;
209 struct ifreq req;
210 struct ethtool_drvinfo drvinfo;
211 struct ethtool_gstrings *strings;
212 struct ethtool_stats *stats;
213 size_t n_stats;
214 size_t strings_size;
215 size_t stats_size;
216 size_t i;
217 int status;
219 memset (&req, 0, sizeof (req));
220 sstrncpy(req.ifr_name, device, sizeof (req.ifr_name));
222 fd = socket(AF_INET, SOCK_DGRAM, /* protocol = */ 0);
223 if (fd < 0)
224 {
225 char errbuf[1024];
226 ERROR("ethstat plugin: Failed to open control socket: %s",
227 sstrerror (errno, errbuf, sizeof (errbuf)));
228 return 1;
229 }
231 memset (&drvinfo, 0, sizeof (drvinfo));
232 drvinfo.cmd = ETHTOOL_GDRVINFO;
233 req.ifr_data = (void *) &drvinfo;
234 status = ioctl (fd, SIOCETHTOOL, &req);
235 if (status < 0)
236 {
237 char errbuf[1024];
238 close (fd);
239 ERROR ("ethstat plugin: Failed to get driver information "
240 "from %s: %s", device,
241 sstrerror (errno, errbuf, sizeof (errbuf)));
242 return (-1);
243 }
245 n_stats = (size_t) drvinfo.n_stats;
246 if (n_stats < 1)
247 {
248 close (fd);
249 ERROR("ethstat plugin: No stats available for %s", device);
250 return (-1);
251 }
253 strings_size = sizeof (struct ethtool_gstrings)
254 + (n_stats * ETH_GSTRING_LEN);
255 stats_size = sizeof (struct ethtool_stats)
256 + (n_stats * sizeof (uint64_t));
258 strings = malloc (strings_size);
259 stats = malloc (stats_size);
260 if ((strings == NULL) || (stats == NULL))
261 {
262 close (fd);
263 sfree (strings);
264 sfree (stats);
265 ERROR("ethstat plugin: malloc(3) failed.");
266 return (-1);
267 }
269 strings->cmd = ETHTOOL_GSTRINGS;
270 strings->string_set = ETH_SS_STATS;
271 strings->len = n_stats;
272 req.ifr_data = (void *) strings;
273 status = ioctl (fd, SIOCETHTOOL, &req);
274 if (status < 0)
275 {
276 char errbuf[1024];
277 close (fd);
278 free (strings);
279 free (stats);
280 ERROR ("ethstat plugin: Cannot get strings from %s: %s",
281 device,
282 sstrerror (errno, errbuf, sizeof (errbuf)));
283 return (-1);
284 }
286 stats->cmd = ETHTOOL_GSTATS;
287 stats->n_stats = n_stats;
288 req.ifr_data = (void *) stats;
289 status = ioctl (fd, SIOCETHTOOL, &req);
290 if (status < 0)
291 {
292 char errbuf[1024];
293 close (fd);
294 free(strings);
295 free(stats);
296 ERROR("ethstat plugin: Reading statistics from %s failed: %s",
297 device,
298 sstrerror (errno, errbuf, sizeof (errbuf)));
299 return (-1);
300 }
302 for (i = 0; i < n_stats; i++)
303 {
304 const char *stat_name;
306 stat_name = (void *) &strings->data[i * ETH_GSTRING_LEN];
307 DEBUG("ethstat plugin: device = \"%s\": %s = %"PRIu64,
308 device, stat_name, (uint64_t) stats->data[i]);
309 ethstat_submit_value (device,
310 stat_name, (derive_t) stats->data[i]);
311 }
313 close (fd);
314 sfree (strings);
315 sfree (stats);
317 return (0);
318 } /* }}} ethstat_read_interface */
320 static int ethstat_read(void)
321 {
322 size_t i;
324 for (i = 0; i < interfaces_num; i++)
325 ethstat_read_interface (interfaces[i]);
327 return 0;
328 }
330 void module_register (void)
331 {
332 plugin_register_complex_config ("ethstat", ethstat_config);
333 plugin_register_read ("ethstat", ethstat_read);
334 }
336 /* vim: set sw=2 sts=2 et fdm=marker : */