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