treewide: replace memset to 0 with initializers
[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 #include "common.h"
27 #include "plugin.h"
28 #include "configfile.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 {
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;
51
52 static char **interfaces = NULL;
53 static size_t interfaces_num = 0;
54
55 static c_avl_tree_t *value_map = NULL;
56
57 static _Bool collect_mapped_only = 0;
58
59 static int ethstat_add_interface (const oconfig_item_t *ci) /* {{{ */
60 {
61   char **tmp;
62   int status;
63
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;
70
71   status = cf_util_get_string (ci, interfaces + interfaces_num);
72   if (status != 0)
73     return (status);
74
75   interfaces_num++;
76   INFO("ethstat plugin: Registered interface %s",
77       interfaces[interfaces_num - 1]);
78
79   return (0);
80 } /* }}} int ethstat_add_interface */
81
82 static int ethstat_add_map (const oconfig_item_t *ci) /* {{{ */
83 {
84   value_map_t *map;
85   int status;
86   char *key;
87
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   }
99
100   key = strdup (ci->values[0].value.string);
101   if (key == NULL)
102   {
103     ERROR ("ethstat plugin: strdup(3) failed.");
104     return (ENOMEM);
105   }
106
107   map = calloc (1, sizeof (*map));
108   if (map == NULL)
109   {
110     sfree (key);
111     ERROR ("ethstat plugin: calloc failed.");
112     return (ENOMEM);
113   }
114
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));
119
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   }
131
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);
141
142     sfree (map);
143     sfree (key);
144     return (-1);
145   }
146
147   return (0);
148 } /* }}} int ethstat_add_map */
149
150 static int ethstat_config (oconfig_item_t *ci) /* {{{ */
151 {
152   int i;
153
154   for (i = 0; i < ci->children_num; i++)
155   {
156     oconfig_item_t *child = ci->children + i;
157
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   }
168
169   return (0);
170 } /* }}} */
171
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;
176
177   value_t values[1];
178   value_list_t vl = VALUE_LIST_INIT;
179   value_map_t *map = NULL;
180
181   if (value_map != NULL)
182     c_avl_get (value_map, type_instance, (void *) &map);
183
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   }
193
194   values[0].derive = value;
195   vl.values = values;
196   vl.values_len = 1;
197
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   }
212
213   plugin_dispatch_values (&vl);
214 }
215
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;
226
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   }
235
236   struct ethtool_drvinfo drvinfo = {
237     .cmd = ETHTOOL_GDRVINFO
238   };
239
240   struct ifreq req = {
241     .ifr_data = (void *) &drvinfo
242   };
243
244   sstrncpy(req.ifr_name, device, sizeof (req.ifr_name));
245
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   }
256
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   }
264
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));
269
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   }
280
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   }
297
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   }
313
314   for (i = 0; i < n_stats; i++)
315   {
316     char *stat_name;
317
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++;
322
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   }
328
329   close (fd);
330   sfree (strings);
331   sfree (stats);
332
333   return (0);
334 } /* }}} ethstat_read_interface */
335
336 static int ethstat_read(void)
337 {
338   size_t i;
339
340   for (i = 0; i < interfaces_num; i++)
341     ethstat_read_interface (interfaces[i]);
342
343   return 0;
344 }
345
346 static int ethstat_shutdown (void)
347 {
348   void *key = NULL;
349   void *value = NULL;
350
351   if (value_map == NULL)
352     return (0);
353
354   while (c_avl_pick (value_map, &key, &value) == 0)
355   {
356     sfree (key);
357     sfree (value);
358   }
359
360   c_avl_destroy (value_map);
361   value_map = NULL;
362
363   return (0);
364 }
365
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 }
372
373 /* vim: set sw=2 sts=2 et fdm=marker : */