ethstat plugin: Implement the "Map" option.
[collectd.git] / src / ethstat.c
1 /**
2  * collectd - src/ethstat.c
3  * Copyright (C) 2011       Cyril Feraudet
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; either version 2 of the License, or (at your
8  * option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Authors:
20  *   Cyril Feraudet <cyril at feraudet.com>
21  **/
22
23 #include "collectd.h"
24 #include "common.h"
25 #include "plugin.h"
26 #include "configfile.h"
27 #include "utils_avltree.h"
28
29 #if HAVE_SYS_IOCTL_H
30 # include <sys/ioctl.h>
31 #endif
32 #if HAVE_NET_IF_H
33 # include <net/if.h>
34 #endif
35 #if HAVE_LINUX_SOCKIOS_H
36 # include <linux/sockios.h>
37 #endif
38 #if HAVE_LINUX_ETHTOOL_H
39 # include <linux/ethtool.h>
40 #endif
41
42 struct value_map_s
43 {
44   char type[DATA_MAX_NAME_LEN];
45   char type_instance[DATA_MAX_NAME_LEN];
46 };
47 typedef struct value_map_s value_map_t;
48
49 static char **interfaces = NULL;
50 static size_t interfaces_num = 0;
51
52 static c_avl_tree_t *value_map = NULL;
53
54 static int ethstat_add_interface (const oconfig_item_t *ci) /* {{{ */
55 {
56   char **tmp;
57   int status;
58
59   tmp = realloc (interfaces,
60       sizeof (*interfaces) * (interfaces_num + 1));
61   if (tmp == NULL)
62     return (-1);
63   interfaces = tmp;
64
65   status = cf_util_get_string (ci, interfaces + interfaces_num);
66   if (status != 0)
67     return (status);
68
69   interfaces_num++;
70   INFO("ethstat plugin: Registred interface %s",
71       interfaces[interfaces_num - 1]);
72
73   return (0);
74 } /* }}} int ethstat_add_interface */
75
76 static int ethstat_add_map (const oconfig_item_t *ci) /* {{{ */
77 {
78   value_map_t *map;
79   int status;
80
81   if ((ci->values_num < 2)
82       || (ci->values_num > 3)
83       || (ci->values[0].type != OCONFIG_TYPE_STRING)
84       || (ci->values[1].type != OCONFIG_TYPE_STRING)
85       || ((ci->values_num == 3)
86         && (ci->values[2].type != OCONFIG_TYPE_STRING)))
87   {
88     ERROR ("ethstat plugin: The %s option requires "
89         "two or three string arguments.", ci->key);
90     return (-1);
91   }
92
93   map = malloc (sizeof (*map));
94   if (map == NULL)
95   {
96     ERROR ("ethstat plugin: malloc(3) failed.");
97     return (ENOMEM);
98   }
99   memset (map, 0, sizeof (*map));
100
101   sstrncpy (map->type, ci->values[1].value.string, sizeof (map->type));
102   if (ci->values_num == 2)
103     sstrncpy (map->type_instance, ci->values[2].value.string,
104         sizeof (map->type_instance));
105
106   if (value_map == NULL)
107   {
108     value_map = c_avl_create ((void *) strcmp);
109     if (value_map == NULL)
110     {
111       sfree (map);
112       ERROR ("ethstat plugin: c_avl_create() failed.");
113       return (-1);
114     }
115   }
116
117   status = c_avl_insert (value_map,
118       /* key = */ ci->values[0].value.string,
119       /* value = */ map);
120   if (status != 0)
121   {
122     sfree (map);
123     if (status > 0)
124       ERROR ("ethstat plugin: Multiple mappings for \"%s\".",
125           ci->values[0].value.string);
126     else
127       ERROR ("ethstat plugin: c_avl_insert(\"%s\") failed.",
128           ci->values[0].value.string);
129     return (-1);
130   }
131
132   return (0);
133 } /* }}} int ethstat_add_map */
134
135 static int ethstat_config (oconfig_item_t *ci) /* {{{ */
136 {
137   int i;
138
139   for (i = 0; i < ci->children_num; i++)
140   {
141     oconfig_item_t *child = ci->children + i;
142
143     if (strcasecmp ("Interface", child->key) == 0)
144       ethstat_add_interface (child);
145     else if (strcasecmp ("Map", child->key) == 0)
146       ethstat_add_map (child);
147     else
148       WARNING ("ethstat plugin: The config option \"%s\" is unknown.",
149           child->key);
150   }
151
152   return (0);
153 } /* }}} */
154
155 static void ethstat_submit_value (const char *device,
156                 const char *type_instance, derive_t value)
157 {
158         value_t values[1];
159         value_list_t vl = VALUE_LIST_INIT;
160         value_map_t *map = NULL;
161
162         if (value_map != NULL)
163           c_avl_get (value_map, type_instance, (void *) &map);
164
165         values[0].derive = value;
166         vl.values = values;
167         vl.values_len = 1;
168
169         sstrncpy (vl.host, hostname_g, sizeof (vl.host));
170         sstrncpy (vl.plugin, "ethstat", sizeof (vl.plugin));
171         sstrncpy (vl.plugin_instance, device, sizeof (vl.plugin_instance));
172         if (map != NULL)
173         {
174           sstrncpy (vl.type, map->type, sizeof (vl.type));
175           sstrncpy (vl.type_instance, map->type_instance,
176               sizeof (vl.type_instance));
177         }
178         else
179         {
180           sstrncpy (vl.type, "derive", sizeof (vl.type));
181           sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
182         }
183
184         plugin_dispatch_values (&vl);
185 }
186
187 static int ethstat_read_interface (char *device)
188 {
189         int fd;
190         struct ifreq req;
191         struct ethtool_drvinfo drvinfo;
192         struct ethtool_gstrings *strings;
193         struct ethtool_stats *stats;
194         size_t n_stats;
195         size_t strings_size;
196         size_t stats_size;
197         size_t i;
198         int status;
199
200         memset (&req, 0, sizeof (req));
201         sstrncpy(req.ifr_name, device, sizeof (req.ifr_name));
202
203         fd = socket(AF_INET, SOCK_DGRAM, /* protocol = */ 0);
204         if (fd < 0)
205         {
206                 char errbuf[1024];
207                 ERROR("ethstat plugin: Failed to open control socket: %s",
208                                 sstrerror (errno, errbuf, sizeof (errbuf)));
209                 return 1;
210         }
211
212         memset (&drvinfo, 0, sizeof (drvinfo));
213         drvinfo.cmd = ETHTOOL_GDRVINFO;
214         req.ifr_data = (void *) &drvinfo;
215         status = ioctl (fd, SIOCETHTOOL, &req);
216         if (status < 0)
217         {
218                 char errbuf[1024];
219                 close (fd);
220                 ERROR ("ethstat plugin: Failed to get driver information "
221                                 "from %s: %s", device,
222                                 sstrerror (errno, errbuf, sizeof (errbuf)));
223                 return (-1);
224         }
225
226         n_stats = (size_t) drvinfo.n_stats;
227         if (n_stats < 1)
228         {
229                 close (fd);
230                 ERROR("ethstat plugin: No stats available for %s", device);
231                 return (-1);
232         }
233
234         strings_size = sizeof (struct ethtool_gstrings)
235                 + (n_stats * ETH_GSTRING_LEN);
236         stats_size = sizeof (struct ethtool_stats)
237                 + (n_stats * sizeof (uint64_t));
238
239         strings = malloc (strings_size);
240         stats = malloc (stats_size);
241         if ((strings == NULL) || (stats == NULL))
242         {
243                 close (fd);
244                 sfree (strings);
245                 sfree (stats);
246                 ERROR("ethstat plugin: malloc(3) failed.");
247                 return (-1);
248         }
249
250         strings->cmd = ETHTOOL_GSTRINGS;
251         strings->string_set = ETH_SS_STATS;
252         strings->len = n_stats;
253         req.ifr_data = (void *) strings;
254         status = ioctl (fd, SIOCETHTOOL, &req);
255         if (status < 0)
256         {
257                 char errbuf[1024];
258                 close (fd);
259                 free (strings);
260                 free (stats);
261                 ERROR ("ethstat plugin: Cannot get strings from %s: %s",
262                                 device,
263                                 sstrerror (errno, errbuf, sizeof (errbuf)));
264                 return (-1);
265         }
266
267         stats->cmd = ETHTOOL_GSTATS;
268         stats->n_stats = n_stats;
269         req.ifr_data = (void *) stats;
270         status = ioctl (fd, SIOCETHTOOL, &req);
271         if (status < 0)
272         {
273                 char errbuf[1024];
274                 close (fd);
275                 free(strings);
276                 free(stats);
277                 ERROR("ethstat plugin: Reading statistics from %s failed: %s",
278                                 device,
279                                 sstrerror (errno, errbuf, sizeof (errbuf)));
280                 return (-1);
281         }
282
283         for (i = 0; i < n_stats; i++)
284         {
285                 const char *stat_name;
286
287                 stat_name = (void *) &strings->data[i * ETH_GSTRING_LEN],
288                 DEBUG("ethstat plugin: device = \"%s\": %s = %"PRIu64,
289                                 device, stat_name,
290                                 (uint64_t) stats->data[i]);
291                 ethstat_submit_value (device,
292                                 stat_name, (derive_t) stats->data[i]);
293         }
294
295         close (fd);
296         sfree (strings);
297         sfree (stats);
298
299         return (0);
300 } /* }}} ethstat_read_interface */
301
302 static int ethstat_read(void)
303 {
304         size_t i;
305
306         for (i = 0; i < interfaces_num; i++)
307                 ethstat_read_interface (interfaces[i]);
308
309         return 0;
310 }
311
312 void module_register (void)
313 {
314         plugin_register_complex_config ("ethstat", ethstat_config);
315         plugin_register_read ("ethstat", ethstat_read);
316 }
317
318 /* vim: set sw=2 sts=2 et fdm=marker : */