All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
collectd.c
Go to the documentation of this file.
1 /*
2  * This program is is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 2 of the License, or (at
5  * your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16 
17 /**
18  * $Id: b2672f5944e09d27c7a95b65dc9721fd835b55ea $
19  * @file collectd.c
20  * @brief Helper functions to enabled radsniff to talk to collectd
21  *
22  * @copyright 2013 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
23  */
24 #include <assert.h>
25 #include <ctype.h>
26 
27 #ifdef HAVE_COLLECTDC_H
28 #include <collectd/client.h>
29 #include <freeradius-devel/radsniff.h>
30 
31 /** Copy a 64bit unsigned integer into a double
32  *
33  */
34 /*
35 static void _copy_uint64_to_double(UNUSED rs_t *conf, rs_stats_value_tmpl_t *tmpl)
36 {
37  assert(tmpl->src);
38  assert(tmpl->dst);
39 
40  *((double *) tmpl->dst) = *((uint64_t *) tmpl->src);
41 }
42 */
43 
44 /*
45 static void _copy_uint64_to_uint64(UNUSED rs_t *conf, rs_stats_value_tmpl_t *tmpl)
46 {
47  assert(tmpl->src);
48  assert(tmpl->dst);
49 
50  *((uint64_t *) tmpl->dst) = *((uint64_t *) tmpl->src);
51 }
52 */
53 
54 static void _copy_double_to_double(UNUSED rs_t *conf, rs_stats_value_tmpl_t *tmpl)
55 {
56  assert(tmpl->src);
57  assert(tmpl->dst);
58 
59  *((double *) tmpl->dst) = *((double*) tmpl->src);
60 }
61 
62 
63 /** Allocates a stats template which describes a single guage/counter
64  *
65  * This is just intended to simplify allocating a fairly complex memory structure
66  * src and dst pointers must be set
67  *
68  * @param ctx Context to allocate collectd struct in.
69  * @param conf Radsniff configuration.
70  * @param plugin_instance usually the type of packet (in our case).
71  * @param type string, the name of a collection of stats e.g. exchange
72  * @param type_instance the name of the counter/guage within the collection e.g. latency.
73  * @param stats structure to derive statistics from.
74  * @param values Value templates used to populate lcc_value_list.
75  * @return
76  * - New #rs_stats_tmpl_t on success.
77  * - NULL on failure.
78  */
79 static rs_stats_tmpl_t *rs_stats_collectd_init(TALLOC_CTX *ctx, rs_t *conf,
80  char const *plugin_instance,
81  char const *type, char const *type_instance,
82  void *stats,
83  rs_stats_value_tmpl_t const *values)
84 {
85  static char hostname[255];
86  static char fqdn[LCC_NAME_LEN];
87 
88  size_t len;
89  int i;
90  char *p;
91 
92  rs_stats_tmpl_t *tmpl;
93  lcc_value_list_t *value;
94 
95  assert(conf);
96  assert(type);
97  assert(type_instance);
98 
99  for (len = 0; values[len].src; len++) {} ;
100  assert(len > 0);
101 
102  /*
103  * Initialise hostname once so we don't call gethostname every time
104  */
105  if (*fqdn == '\0') {
106  int ret;
107  struct addrinfo hints, *info = NULL;
108 
109  if (gethostname(hostname, sizeof(hostname)) < 0) {
110  ERROR("Error getting hostname: %s", fr_syserror(errno));
111 
112  return NULL;
113  }
114 
115  memset(&hints, 0, sizeof hints);
116  hints.ai_family = AF_UNSPEC; /*either IPV4 or IPV6*/
117  hints.ai_socktype = SOCK_STREAM;
118  hints.ai_flags = AI_CANONNAME;
119 
120  if ((ret = getaddrinfo(hostname, "radius", &hints, &info)) != 0) {
121  ERROR("Error getting hostname: %s", gai_strerror(ret));
122  return NULL;
123  }
124 
125  strlcpy(fqdn, info->ai_canonname, sizeof(fqdn));
126 
127  freeaddrinfo(info);
128  }
129 
130  tmpl = talloc_zero(ctx, rs_stats_tmpl_t);
131  if (!tmpl) {
132  return NULL;
133  }
134 
135  tmpl->value_tmpl = talloc_zero_array(tmpl, rs_stats_value_tmpl_t, len);
136  if (!tmpl->value_tmpl) {
137  goto error;
138  }
139 
140  tmpl->stats = stats;
141 
142  value = talloc_zero(tmpl, lcc_value_list_t);
143  if (!value) {
144  goto error;
145  }
146  tmpl->value = value;
147 
148  value->interval = conf->stats.interval;
149  value->values_len = len;
150 
151  value->values_types = talloc_zero_array(value, int, len);
152  if (!value->values_types) {
153  goto error;
154  }
155 
156  value->values = talloc_zero_array(value, value_t, len);
157  if (!value->values) {
158  goto error;
159  }
160 
161  for (i = 0; i < (int) len; i++) {
162  assert(values[i].src);
163  assert(values[i].cb);
164 
165  tmpl->value_tmpl[i] = values[i];
166  switch (tmpl->value_tmpl[i].type) {
167  case LCC_TYPE_COUNTER:
168  tmpl->value_tmpl[i].dst = &value->values[i].counter;
169  break;
170 
171  case LCC_TYPE_GAUGE:
172  tmpl->value_tmpl[i].dst = &value->values[i].gauge;
173  break;
174 
175  case LCC_TYPE_DERIVE:
176  tmpl->value_tmpl[i].dst = &value->values[i].derive;
177  break;
178 
179  case LCC_TYPE_ABSOLUTE:
180  tmpl->value_tmpl[i].dst = &value->values[i].absolute;
181  break;
182 
183  default:
184  assert(0);
185  }
186  value->values_types[i] = tmpl->value_tmpl[i].type;
187  }
188 
189  /*
190  * These should be OK as is
191  */
192  strlcpy(value->identifier.host, fqdn, sizeof(value->identifier.host));
193 
194  /*
195  * Plugin is ASCII only and no '/'
196  */
197  fr_snprint(value->identifier.plugin, sizeof(value->identifier.plugin),
198  conf->stats.prefix, strlen(conf->stats.prefix), '\0');
199  for (p = value->identifier.plugin; *p; ++p) {
200  if ((*p == '-') || (*p == '/'))*p = '_';
201  }
202 
203  /*
204  * Plugin instance is ASCII only (assuming printable only) and no '/'
205  */
206  fr_snprint(value->identifier.plugin_instance, sizeof(value->identifier.plugin_instance),
207  plugin_instance, strlen(plugin_instance), '\0');
208  for (p = value->identifier.plugin_instance; *p; ++p) {
209  if ((*p == '-') || (*p == '/')) *p = '_';
210  }
211 
212  /*
213  * Type is ASCII only (assuming printable only) and no '/' or '-'
214  */
215  fr_snprint(value->identifier.type, sizeof(value->identifier.type),
216  type, strlen(type), '\0');
217  for (p = value->identifier.type; *p; ++p) {
218  if ((*p == '-') || (*p == '/')) *p = '_';
219  }
220 
221  fr_snprint(value->identifier.type_instance, sizeof(value->identifier.type_instance),
222  type_instance, strlen(type_instance), '\0');
223  for (p = value->identifier.type_instance; *p; ++p) {
224  if ((*p == '-') || (*p == '/')) *p = '_';
225  }
226 
227 
228  return tmpl;
229 
230 error:
231  talloc_free(tmpl);
232  return NULL;
233 }
234 
235 
236 /** Setup stats templates for latency
237  *
238  */
239 rs_stats_tmpl_t *rs_stats_collectd_init_latency(TALLOC_CTX *ctx, rs_stats_tmpl_t **out, rs_t *conf,
240  char const *type, rs_latency_t *stats, PW_CODE code)
241 {
242  rs_stats_tmpl_t **tmpl, *last;
243  char *p;
244  char buffer[LCC_NAME_LEN];
245  tmpl = out;
246 
247  rs_stats_value_tmpl_t rtx[(RS_RETRANSMIT_MAX + 1) + 1 + 1]; // RTX bins + 0 bin + lost + NULL
248  int i;
249 
250  /* not static so were thread safe */
251  rs_stats_value_tmpl_t const _packet_count[] = {
252  { &stats->interval.received, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
253  { &stats->interval.linked, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
254  { &stats->interval.unlinked, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
255  { &stats->interval.reused, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
256  { NULL, 0, NULL, NULL }
257  };
258 
259  rs_stats_value_tmpl_t const _latency[] = {
260  { &stats->latency_smoothed, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
261  { &stats->interval.latency_average, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
262  { &stats->interval.latency_high, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
263  { &stats->interval.latency_low, LCC_TYPE_GAUGE, _copy_double_to_double, NULL },
264  { NULL, 0, NULL, NULL }
265  };
266 
267 #define INIT_STATS(_ti, _v) do {\
268  strlcpy(buffer, fr_packet_codes[code], sizeof(buffer)); \
269  for (p = buffer; *p; ++p) *p = tolower(*p);\
270  last = *tmpl = rs_stats_collectd_init(ctx, conf, type, _ti, buffer, stats, _v);\
271  if (!*tmpl) {\
272  TALLOC_FREE(*out);\
273  return NULL;\
274  }\
275  tmpl = &(*tmpl)->next;\
276  ctx = *tmpl;\
277  } while (0)
278 
279 
280  INIT_STATS("radius_count", _packet_count);
281  INIT_STATS("radius_latency", _latency);
282 
283  for (i = 0; i < (RS_RETRANSMIT_MAX + 1); i++) {
284  rtx[i].src = &stats->interval.rt[i];
285  rtx[i].type = LCC_TYPE_GAUGE;
286  rtx[i].cb = _copy_double_to_double;
287  rtx[i].dst = NULL;
288  }
289 
290  rtx[i].src = &stats->interval.lost;
291  rtx[i].type = LCC_TYPE_GAUGE;
292  rtx[i].cb = _copy_double_to_double;
293  rtx[i].dst = NULL;
294 
295  memset(&rtx[++i], 0, sizeof(rs_stats_value_tmpl_t));
296 
297  INIT_STATS("radius_rtx", rtx);
298 
299  return last;
300 }
301 
302 /** Refresh and send the stats to the collectd server
303  *
304  */
305 void rs_stats_collectd_do_stats(rs_t *conf, rs_stats_tmpl_t *tmpls, struct timeval *now)
306 {
307  rs_stats_tmpl_t *tmpl = tmpls;
308  char identifier[6 * LCC_NAME_LEN];
309  int i;
310 
311  while (tmpl) {
312  /*
313  * Refresh the value of whatever were sending
314  */
315  for (i = 0; i < (int) tmpl->value->values_len; i++) {
316  tmpl->value_tmpl[i].cb(conf, &tmpl->value_tmpl[i]);
317  }
318 
319  tmpl->value->time = now->tv_sec;
320 
321  lcc_identifier_to_string(conf->stats.handle, identifier, sizeof(identifier), &tmpl->value->identifier);
322 
323  if (lcc_putval(conf->stats.handle, tmpl->value) < 0) {
324  char const *error;
325 
326  error = lcc_strerror(conf->stats.handle);
327  ERROR("Failed PUTVAL \"%s\" interval=%i %" PRIu64 " : %s",
328  identifier,
329  (int) tmpl->value->interval,
330  (uint64_t) tmpl->value->time,
331  error ? error : "unknown error");
332  }
333 
334  tmpl = tmpl->next;
335  }
336 }
337 
338 /** Connect to a collectd server for stats output
339  *
340  * @param[in,out] conf radsniff configuration, we write the generated handle here.
341  * @return
342  * - 0 on success
343  * - -1 on failure.
344  */
345 int rs_stats_collectd_open(rs_t *conf)
346 {
347  assert(conf->stats.collectd);
348 
349  /*
350  * Tear down stale connections gracefully.
351  */
352  rs_stats_collectd_close(conf);
353 
354  /*
355  * There's no way to get the error from the connection handle
356  * because it's freed on failure, before lcc returns.
357  */
358  if (lcc_connect(conf->stats.collectd, &conf->stats.handle) < 0) {
359  ERROR("Failed opening connection to collectd: %s", fr_syserror(errno));
360  return -1;
361  }
362  DEBUG2("Connected to \"%s\"", conf->stats.collectd);
363 
364  assert(conf->stats.handle);
365  return 0;
366 }
367 
368 /** Close connection
369  *
370  * @param[in,out] conf radsniff configuration.
371  * @return
372  * - 0 on success.
373  * - -1 on failure.
374  */
375 int rs_stats_collectd_close(rs_t *conf)
376 {
377  assert(conf->stats.collectd);
378 
379  int ret = 0;
380 
381  if (conf->stats.handle) {
382  ret = lcc_disconnect(conf->stats.handle);
383  conf->stats.handle = NULL;
384  }
385 
386  return ret;
387 }
388 #endif
char const * gai_strerror(int ecode)
Definition: getaddrinfo.c:227
struct rs::@9 stats
#define UNUSED
Definition: libradius.h:134
static char const * hostname(char *buf, size_t buflen, uint32_t ipaddr)
Definition: radwho.c:149
struct rs_latency::@8 interval
size_t fr_snprint(char *out, size_t outlen, char const *in, ssize_t inlen, char quote)
Escape any non printable or non-UTF8 characters in the input string.
Definition: print.c:179
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition: log.c:238
static rc_stats_t stats
Definition: radclient.c:47
#define DEBUG2(fmt,...)
Definition: log.h:176
void freeaddrinfo(struct addrinfo *ai)
Definition: getaddrinfo.c:247
static rs_t * conf
Definition: radsniff.c:46
#define RS_RETRANSMIT_MAX
Maximum number of times we expect to see a packet retransmitted.
Definition: radsniff.h:44
double latency_smoothed
Smoothed moving average.
Definition: radsniff.h:106
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition: strlcpy.c:38
PW_CODE
RADIUS packet codes.
Definition: radius.h:90
int getaddrinfo(char const *hostname, char const *servname, struct addrinfo const *hints, struct addrinfo **res)
Definition: getaddrinfo.c:259
Definition: radsniff.h:247
Stats for a single interval.
Definition: radsniff.h:103
#define ERROR(fmt,...)
Definition: log.h:145