Asterisk - The Open Source Telephony Project  21.4.1
res_statsd.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * David M. Lee, II <dlee@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18 
19 /*!
20  * \brief Support for publishing to a StatsD server.
21  *
22  * \author David M. Lee, II <dlee@digium.com>
23  * \since 12
24  */
25 
26 /*** MODULEINFO
27  <support_level>extended</support_level>
28  ***/
29 
30 /*** DOCUMENTATION
31  <configInfo name="res_statsd" language="en_US">
32  <synopsis>StatsD client</synopsis>
33  <description>
34  <para>The <literal>res_statsd</literal> module provides an API that
35  allows Asterisk and its modules to send statistics to a StatsD
36  server. It only provides a means to communicate with a StatsD server
37  and does not send any metrics of its own.</para>
38  <para>An example module, <literal>res_chan_stats</literal>, is
39  provided which uses the API exposed by this module to send channel
40  statistics to the configured StatsD server.</para>
41  <para>More information about StatsD can be found at
42  https://github.com/statsd/statsd</para>
43  </description>
44  <configFile name="statsd.conf">
45  <configObject name="global">
46  <synopsis>Global configuration settings</synopsis>
47  <configOption name="enabled">
48  <synopsis>Enable/disable the StatsD module</synopsis>
49  </configOption>
50  <configOption name="server">
51  <synopsis>Address of the StatsD server</synopsis>
52  </configOption>
53  <configOption name="prefix">
54  <synopsis>Prefix to prepend to every metric</synopsis>
55  </configOption>
56  <configOption name="add_newline">
57  <synopsis>Append a newline to every event. This is useful if
58  you want to fake out a server using netcat
59  (nc -lu 8125)</synopsis>
60  </configOption>
61  <configOption name="meter_support">
62  <synopsis>Enable/disable the non-standard StatsD Meter type,
63  if disabled falls back to counter and will append a "_meter" suffix to the metric name</synopsis>
64  </configOption>
65  </configObject>
66  </configFile>
67  </configInfo>
68 ***/
69 
70 #include "asterisk.h"
71 
73 #include "asterisk/module.h"
74 #include "asterisk/netsock2.h"
75 
76 #define AST_API_MODULE
77 #include "asterisk/statsd.h"
78 
79 #define DEFAULT_STATSD_PORT 8125
80 
81 #define MAX_PREFIX 40
82 
83 /*! Socket for sending statd messages */
84 static int socket_fd = -1;
85 
86 /*! \brief Global configuration options for statsd client. */
88  /*! Enabled by default, disabled if false. */
89  int enabled;
90  /*! Disabled by default, appends newlines to all messages when enabled. */
92  /*! Statsd server address[:port]. */
94  /*! Prefix to put on every stat. */
95  char prefix[MAX_PREFIX + 1];
96  /*! Enabled support for non-standard Meter type by default, falls back to counter if disabled */
98 };
99 
100 /*! \brief All configuration options for statsd client. */
101 struct conf {
102  /*! The general section configuration options. */
104 };
105 
106 /*! \brief Locking container for safe configuration access. */
108 
109 static void conf_server(const struct conf *cfg, struct ast_sockaddr *addr)
110 {
111  *addr = cfg->global->statsd_server;
112  if (ast_sockaddr_port(addr) == 0) {
113  ast_sockaddr_set_port(addr, DEFAULT_STATSD_PORT);
114  }
115 }
116 
117 void AST_OPTIONAL_API_NAME(ast_statsd_log_string)(const char *metric_name,
118  const char *metric_type, const char *value, double sample_rate)
119 {
120  struct conf *cfg;
121  struct ast_str *msg;
122  size_t len;
123  struct ast_sockaddr statsd_server;
124 
125  if (socket_fd == -1) {
126  return;
127  }
128 
129  /* Rates <= 0.0 never get logged.
130  * Rates >= 1.0 always get logged.
131  * All others leave it to chance.
132  */
133  if (sample_rate <= 0.0 ||
134  (sample_rate < 1.0 && sample_rate < ast_random_double())) {
135  return;
136  }
137 
138  cfg = ao2_global_obj_ref(confs);
139  conf_server(cfg, &statsd_server);
140 
141  msg = ast_str_create(40);
142  if (!msg) {
143  ao2_cleanup(cfg);
144  return;
145  }
146 
147  if (!ast_strlen_zero(cfg->global->prefix)) {
148  ast_str_append(&msg, 0, "%s.", cfg->global->prefix);
149  }
150 
151  if (!cfg->global->meter_support && strcmp(metric_type, AST_STATSD_METER)) {
152  ast_str_append(&msg, 0, "%s_meter:%s|%s", metric_name, value, AST_STATSD_COUNTER);
153  } else {
154  ast_str_append(&msg, 0, "%s:%s|%s", metric_name, value, metric_type);
155  }
156 
157  if (sample_rate < 1.0) {
158  ast_str_append(&msg, 0, "|@%.2f", sample_rate);
159  }
160 
161  if (cfg->global->add_newline) {
162  ast_str_append(&msg, 0, "\n");
163  }
164 
165  len = ast_str_strlen(msg);
166 
167  ast_debug(6, "Sending statistic %s to StatsD server\n", ast_str_buffer(msg));
168  ast_sendto(socket_fd, ast_str_buffer(msg), len, 0, &statsd_server);
169 
170  ao2_cleanup(cfg);
171  ast_free(msg);
172 }
173 
174 void AST_OPTIONAL_API_NAME(ast_statsd_log_full)(const char *metric_name,
175  const char *metric_type, intmax_t value, double sample_rate)
176 {
177  char char_value[30];
178  snprintf(char_value, sizeof(char_value), "%jd", value);
179 
180  ast_statsd_log_string(metric_name, metric_type, char_value, sample_rate);
181 
182 }
183 
184 AST_THREADSTORAGE(statsd_buf);
185 
186 void AST_OPTIONAL_API_NAME(ast_statsd_log_string_va)(const char *metric_name,
187  const char *metric_type, const char *value, double sample_rate, ...)
188 {
189  struct ast_str *buf;
190  va_list ap;
191  int res;
192 
193  buf = ast_str_thread_get(&statsd_buf, 128);
194  if (!buf) {
195  return;
196  }
197 
198  va_start(ap, sample_rate);
199  res = ast_str_set_va(&buf, 0, metric_name, ap);
200  va_end(ap);
201 
202  if (res == AST_DYNSTR_BUILD_FAILED) {
203  return;
204  }
205 
206  ast_statsd_log_string(ast_str_buffer(buf), metric_type, value, sample_rate);
207 }
208 
209 void AST_OPTIONAL_API_NAME(ast_statsd_log_full_va)(const char *metric_name,
210  const char *metric_type, intmax_t value, double sample_rate, ...)
211 {
212  struct ast_str *buf;
213  va_list ap;
214  int res;
215 
216  buf = ast_str_thread_get(&statsd_buf, 128);
217  if (!buf) {
218  return;
219  }
220 
221  va_start(ap, sample_rate);
222  res = ast_str_set_va(&buf, 0, metric_name, ap);
223  va_end(ap);
224 
225  if (res == AST_DYNSTR_BUILD_FAILED) {
226  return;
227  }
228 
229  ast_statsd_log_full(ast_str_buffer(buf), metric_type, value, sample_rate);
230 }
231 
232 void AST_OPTIONAL_API_NAME(ast_statsd_log)(const char *metric_name,
233  const char *metric_type, intmax_t value)
234 {
235  char char_value[30];
236  snprintf(char_value, sizeof(char_value), "%jd", value);
237 
238  ast_statsd_log_string(metric_name, metric_type, char_value, 1.0);
239 }
240 
241 void AST_OPTIONAL_API_NAME(ast_statsd_log_sample)(const char *metric_name,
242  intmax_t value, double sample_rate)
243 {
244  char char_value[30];
245  snprintf(char_value, sizeof(char_value), "%jd", value);
246 
247  ast_statsd_log_string(metric_name, AST_STATSD_COUNTER, char_value,
248  sample_rate);
249 }
250 
251 /*! \brief Mapping of the statsd conf struct's globals to the
252  * general context in the config file. */
253 static struct aco_type global_option = {
254  .type = ACO_GLOBAL,
255  .name = "global",
256  .item_offset = offsetof(struct conf, global),
257  .category = "general",
258  .category_match = ACO_WHITELIST_EXACT,
259 };
260 
261 static struct aco_type *global_options[] = ACO_TYPES(&global_option);
262 
263 /*! \brief Disposes of the statsd conf object */
264 static void conf_destructor(void *obj)
265 {
266  struct conf *cfg = obj;
267  ao2_cleanup(cfg->global);
268 }
269 
270 /*! \brief Creates the statis http conf object. */
271 static void *conf_alloc(void)
272 {
273  struct conf *cfg;
274 
275  if (!(cfg = ao2_alloc(sizeof(*cfg), conf_destructor))) {
276  return NULL;
277  }
278 
279  if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), NULL))) {
280  ao2_ref(cfg, -1);
281  return NULL;
282  }
283  return cfg;
284 }
285 
286 /*! \brief The conf file that's processed for the module. */
287 static struct aco_file conf_file = {
288  /*! The config file name. */
289  .filename = "statsd.conf",
290  /*! The mapping object types to be processed. */
291  .types = ACO_TYPES(&global_option),
292 };
293 
295  .files = ACO_FILES(&conf_file));
296 
297 /*! \brief Helper function to check if module is enabled. */
298 static char is_enabled(void)
299 {
300  RAII_VAR(struct conf *, cfg, ao2_global_obj_ref(confs), ao2_cleanup);
301  return cfg->global->enabled;
302 }
303 
304 static int statsd_init(void)
305 {
306  RAII_VAR(struct conf *, cfg, ao2_global_obj_ref(confs), ao2_cleanup);
307  char *server;
308  struct ast_sockaddr statsd_server;
309 
310  ast_assert(is_enabled());
311 
312  ast_debug(3, "Configuring StatsD client.\n");
313 
314  if (socket_fd == -1) {
315  ast_debug(3, "Creating StatsD socket.\n");
316  socket_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
317  if (socket_fd == -1) {
318  perror("Error creating StatsD socket");
319  return -1;
320  }
321  }
322 
323  conf_server(cfg, &statsd_server);
324  server = ast_sockaddr_stringify_fmt(&statsd_server,
325  AST_SOCKADDR_STR_DEFAULT);
326  ast_debug(3, " StatsD server = %s.\n", server);
327  ast_debug(3, " add newline = %s\n", AST_YESNO(cfg->global->add_newline));
328  ast_debug(3, " prefix = %s\n", cfg->global->prefix);
329 
330  return 0;
331 }
332 
333 static void statsd_shutdown(void)
334 {
335  ast_debug(3, "Shutting down StatsD client.\n");
336  if (socket_fd != -1) {
337  close(socket_fd);
338  socket_fd = -1;
339  }
340 }
341 
342 static int unload_module(void)
343 {
344  statsd_shutdown();
345  aco_info_destroy(&cfg_info);
347  return 0;
348 }
349 
350 static int load_module(void)
351 {
352  if (aco_info_init(&cfg_info)) {
353  aco_info_destroy(&cfg_info);
355  }
356 
357  aco_option_register(&cfg_info, "enabled", ACO_EXACT, global_options,
358  "no", OPT_BOOL_T, 1,
360 
361  aco_option_register(&cfg_info, "add_newline", ACO_EXACT, global_options,
362  "no", OPT_BOOL_T, 1,
363  FLDSET(struct conf_global_options, add_newline));
364 
365  aco_option_register(&cfg_info, "server", ACO_EXACT, global_options,
366  "127.0.0.1", OPT_SOCKADDR_T, 0,
367  FLDSET(struct conf_global_options, statsd_server));
368 
369  aco_option_register(&cfg_info, "prefix", ACO_EXACT, global_options,
370  "", OPT_CHAR_ARRAY_T, 0,
371  CHARFLDSET(struct conf_global_options, prefix));
372 
373  aco_option_register(&cfg_info, "meter_support", ACO_EXACT, global_options,
374  "yes", OPT_BOOL_T, 1,
375  FLDSET(struct conf_global_options, meter_support));
376 
377  if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
378  struct conf *cfg;
379 
380  ast_log(LOG_NOTICE, "Could not load statsd config; using defaults\n");
381  cfg = conf_alloc();
382  if (!cfg) {
383  aco_info_destroy(&cfg_info);
385  }
386 
387  if (aco_set_defaults(&global_option, "general", cfg->global)) {
388  ast_log(LOG_ERROR, "Failed to initialize statsd defaults.\n");
389  ao2_ref(cfg, -1);
390  aco_info_destroy(&cfg_info);
392  }
393 
395  ao2_ref(cfg, -1);
396  }
397 
398  if (!is_enabled()) {
400  }
401 
402  if (statsd_init()) {
403  unload_module();
405  }
406 
408 }
409 
410 static int reload_module(void)
411 {
412  switch (aco_process_config(&cfg_info, 1)) {
413  case ACO_PROCESS_OK:
414  break;
417  case ACO_PROCESS_ERROR:
418  default:
420  }
421 
422  if (is_enabled()) {
423  if (statsd_init()) {
425  }
426  } else {
427  statsd_shutdown();
428  }
430 }
431 
432 /* The priority of this module is set just after realtime, since it loads
433  * configuration and could be used by any other sort of module.
434  */
435 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "StatsD client support",
436  .support_level = AST_MODULE_SUPPORT_EXTENDED,
437  .load = load_module,
438  .unload = unload_module,
439  .reload = reload_module,
440  .load_pri = AST_MODPRI_REALTIME_DRIVER + 5,
441 );
Type for default handler for ast_sockaddrs.
#define AST_THREADSTORAGE(name)
Define a thread storage variable.
Definition: threadstorage.h:86
static struct aco_file conf_file
The conf file that's processed for the module.
ssize_t ast_sendto(int sockfd, const void *buf, size_t len, int flags, const struct ast_sockaddr *dest_addr)
Wrapper around sendto(2) that uses ast_sockaddr.
Definition: netsock2.c:614
Asterisk main include file. File version handling, generic pbx functions.
struct conf_global_options * global
Definition: res_statsd.c:103
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
#define aco_option_register(info, name, matchtype, types, default_val, opt_type, flags,...)
Register a config option.
int ast_str_set_va(struct ast_str **buf, ssize_t max_len, const char *fmt, va_list ap)
Set a dynamic string from a va_list.
Definition: strings.h:1030
#define CHARFLDSET(type, field)
A helper macro to pass the appropriate arguments to aco_option_register for OPT_CHAR_ARRAY_T.
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition: strings.h:1139
enum aco_process_status aco_process_config(struct aco_info *info, int reload)
Process a config info via the options registered with an aco_info.
#define ao2_global_obj_ref(holder)
Get a reference to the object stored in the global holder.
Definition: astobj2.h:918
The representation of a single configuration file to be processed.
enum aco_type_t type
Socket address structure.
Definition: netsock2.h:97
#define ACO_TYPES(...)
A helper macro to ensure that aco_info types always have a sentinel.
struct ast_sockaddr statsd_server
Definition: res_statsd.c:93
All configuration options for http media cache.
#define ast_sockaddr_port(addr)
Get the port number of a socket address.
Definition: netsock2.h:517
Type for default option handler for character array strings.
#define FLDSET(type,...)
Convert a struct and list of fields to an argument list of field offsets.
int aco_info_init(struct aco_info *info)
Initialize an aco_info structure.
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
#define ast_debug(level,...)
Log a DEBUG message.
Network socket handling.
The config had not been edited and no changes applied.
Their was an error and no changes were applied.
Configuration option-handling.
Support for dynamic strings.
Definition: strings.h:623
#define ast_sockaddr_set_port(addr, port)
Sets the port number of a socket address.
Definition: netsock2.h:532
void aco_info_destroy(struct aco_info *info)
Destroy an initialized aco_info struct.
#define ao2_global_obj_release(holder)
Release the ao2 object held in the global holder.
Definition: astobj2.h:859
Type for default option handler for bools (ast_true/ast_false)
char * ast_sockaddr_stringify_fmt(const struct ast_sockaddr *addr, int format)
Convert a socket address to a string.
Definition: netsock2.c:65
static void * conf_alloc(void)
Allocate an ast_ari_conf for config parsing.
int aco_set_defaults(struct aco_type *type, const char *category, void *obj)
Set all default options of obj.
The config was processed and applied.
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
static struct aco_type global_option
An aco_type structure to link the "general" category to the skel_global_config type.
Definition: app_skel.c:241
Global configuration options for statsd client.
Definition: res_statsd.c:87
static int is_enabled(void)
Helper function to check if module is enabled.
Definition: res_ari.c:159
#define AST_YESNO(x)
return Yes or No depending on the argument.
Definition: strings.h:143
#define ao2_global_obj_replace_unref(holder, obj)
Replace an ao2 object in the global holder, throwing away any old object.
Definition: astobj2.h:901
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:730
char prefix[MAX_PREFIX+1]
Definition: res_statsd.c:95
Type information about a category-level configurable object.
static int enabled
Whether or not we are storing history.
const char * filename
#define ast_random_double()
Returns a random number between 0.0 and 1.0, inclusive.
Definition: utils.h:624
#define AST_OPTIONAL_API_NAME(name)
Expands to the name of the implementation function.
Definition: optional_api.h:228
struct ast_str * ast_str_thread_get(struct ast_threadstorage *ts, size_t init_len)
Retrieve a thread locally stored dynamic string.
Definition: strings.h:909
#define AO2_GLOBAL_OBJ_STATIC(name)
Define a global object holder to be used to hold an ao2 object, statically initialized.
Definition: astobj2.h:847
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
static void conf_destructor(void *obj)
ast_ari_conf destructor.
Asterisk module definitions.
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941
#define CONFIG_INFO_STANDARD(name, arr, alloc,...)
Declare an aco_info struct with default module and preload values.
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659