Asterisk - The Open Source Telephony Project  18.5.0
res_statsd.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * David M. Lee, II <[email protected]>
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 <[email protected]>
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  </configObject>
62  </configFile>
63  </configInfo>
64 ***/
65 
66 #include "asterisk.h"
67 
69 #include "asterisk/module.h"
70 #include "asterisk/netsock2.h"
71 
72 #define AST_API_MODULE
73 #include "asterisk/statsd.h"
74 
75 #define DEFAULT_STATSD_PORT 8125
76 
77 #define MAX_PREFIX 40
78 
79 /*! Socket for sending statd messages */
80 static int socket_fd = -1;
81 
82 /*! \brief Global configuration options for statsd client. */
84  /*! Enabled by default, disabled if false. */
85  int enabled;
86  /*! Disabled by default, appends newlines to all messages when enabled. */
88  /*! Statsd server address[:port]. */
90  /*! Prefix to put on every stat. */
91  char prefix[MAX_PREFIX + 1];
92 };
93 
94 /*! \brief All configuration options for statsd client. */
95 struct conf {
96  /*! The general section configuration options. */
98 };
99 
100 /*! \brief Locking container for safe configuration access. */
102 
103 static void conf_server(const struct conf *cfg, struct ast_sockaddr *addr)
104 {
105  *addr = cfg->global->statsd_server;
106  if (ast_sockaddr_port(addr) == 0) {
108  }
109 }
110 
111 void AST_OPTIONAL_API_NAME(ast_statsd_log_string)(const char *metric_name,
112  const char *metric_type, const char *value, double sample_rate)
113 {
114  struct conf *cfg;
115  struct ast_str *msg;
116  size_t len;
117  struct ast_sockaddr statsd_server;
118 
119  if (socket_fd == -1) {
120  return;
121  }
122 
123  /* Rates <= 0.0 never get logged.
124  * Rates >= 1.0 always get logged.
125  * All others leave it to chance.
126  */
127  if (sample_rate <= 0.0 ||
128  (sample_rate < 1.0 && sample_rate < ast_random_double())) {
129  return;
130  }
131 
132  cfg = ao2_global_obj_ref(confs);
133  conf_server(cfg, &statsd_server);
134 
135  msg = ast_str_create(40);
136  if (!msg) {
137  ao2_cleanup(cfg);
138  return;
139  }
140 
141  if (!ast_strlen_zero(cfg->global->prefix)) {
142  ast_str_append(&msg, 0, "%s.", cfg->global->prefix);
143  }
144 
145  ast_str_append(&msg, 0, "%s:%s|%s", metric_name, value, metric_type);
146 
147  if (sample_rate < 1.0) {
148  ast_str_append(&msg, 0, "|@%.2f", sample_rate);
149  }
150 
151  if (cfg->global->add_newline) {
152  ast_str_append(&msg, 0, "\n");
153  }
154 
155  len = ast_str_strlen(msg);
156 
157  ast_debug(6, "Sending statistic %s to StatsD server\n", ast_str_buffer(msg));
158  ast_sendto(socket_fd, ast_str_buffer(msg), len, 0, &statsd_server);
159 
160  ao2_cleanup(cfg);
161  ast_free(msg);
162 }
163 
164 void AST_OPTIONAL_API_NAME(ast_statsd_log_full)(const char *metric_name,
165  const char *metric_type, intmax_t value, double sample_rate)
166 {
167  char char_value[30];
168  snprintf(char_value, sizeof(char_value), "%jd", value);
169 
170  ast_statsd_log_string(metric_name, metric_type, char_value, sample_rate);
171 
172 }
173 
175 
176 void AST_OPTIONAL_API_NAME(ast_statsd_log_string_va)(const char *metric_name,
177  const char *metric_type, const char *value, double sample_rate, ...)
178 {
179  struct ast_str *buf;
180  va_list ap;
181  int res;
182 
183  buf = ast_str_thread_get(&statsd_buf, 128);
184  if (!buf) {
185  return;
186  }
187 
188  va_start(ap, sample_rate);
189  res = ast_str_set_va(&buf, 0, metric_name, ap);
190  va_end(ap);
191 
192  if (res == AST_DYNSTR_BUILD_FAILED) {
193  return;
194  }
195 
196  ast_statsd_log_string(ast_str_buffer(buf), metric_type, value, sample_rate);
197 }
198 
199 void AST_OPTIONAL_API_NAME(ast_statsd_log_full_va)(const char *metric_name,
200  const char *metric_type, intmax_t value, double sample_rate, ...)
201 {
202  struct ast_str *buf;
203  va_list ap;
204  int res;
205 
206  buf = ast_str_thread_get(&statsd_buf, 128);
207  if (!buf) {
208  return;
209  }
210 
211  va_start(ap, sample_rate);
212  res = ast_str_set_va(&buf, 0, metric_name, ap);
213  va_end(ap);
214 
215  if (res == AST_DYNSTR_BUILD_FAILED) {
216  return;
217  }
218 
219  ast_statsd_log_full(ast_str_buffer(buf), metric_type, value, sample_rate);
220 }
221 
222 void AST_OPTIONAL_API_NAME(ast_statsd_log)(const char *metric_name,
223  const char *metric_type, intmax_t value)
224 {
225  char char_value[30];
226  snprintf(char_value, sizeof(char_value), "%jd", value);
227 
228  ast_statsd_log_string(metric_name, metric_type, char_value, 1.0);
229 }
230 
231 void AST_OPTIONAL_API_NAME(ast_statsd_log_sample)(const char *metric_name,
232  intmax_t value, double sample_rate)
233 {
234  char char_value[30];
235  snprintf(char_value, sizeof(char_value), "%jd", value);
236 
237  ast_statsd_log_string(metric_name, AST_STATSD_COUNTER, char_value,
238  sample_rate);
239 }
240 
241 /*! \brief Mapping of the statsd conf struct's globals to the
242  * general context in the config file. */
243 static struct aco_type global_option = {
244  .type = ACO_GLOBAL,
245  .name = "global",
246  .item_offset = offsetof(struct conf, global),
247  .category = "general",
248  .category_match = ACO_WHITELIST_EXACT,
249 };
250 
251 static struct aco_type *global_options[] = ACO_TYPES(&global_option);
252 
253 /*! \brief Disposes of the statsd conf object */
254 static void conf_destructor(void *obj)
255 {
256  struct conf *cfg = obj;
257  ao2_cleanup(cfg->global);
258 }
259 
260 /*! \brief Creates the statis http conf object. */
261 static void *conf_alloc(void)
262 {
263  struct conf *cfg;
264 
265  if (!(cfg = ao2_alloc(sizeof(*cfg), conf_destructor))) {
266  return NULL;
267  }
268 
269  if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), NULL))) {
270  ao2_ref(cfg, -1);
271  return NULL;
272  }
273  return cfg;
274 }
275 
276 /*! \brief The conf file that's processed for the module. */
277 static struct aco_file conf_file = {
278  /*! The config file name. */
279  .filename = "statsd.conf",
280  /*! The mapping object types to be processed. */
281  .types = ACO_TYPES(&global_option),
282 };
283 
285  .files = ACO_FILES(&conf_file));
286 
287 /*! \brief Helper function to check if module is enabled. */
288 static char is_enabled(void)
289 {
291  return cfg->global->enabled;
292 }
293 
294 static int statsd_init(void)
295 {
297  char *server;
298  struct ast_sockaddr statsd_server;
299 
301 
302  ast_debug(3, "Configuring StatsD client.\n");
303 
304  if (socket_fd == -1) {
305  ast_debug(3, "Creating StatsD socket.\n");
306  socket_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
307  if (socket_fd == -1) {
308  perror("Error creating StatsD socket");
309  return -1;
310  }
311  }
312 
313  conf_server(cfg, &statsd_server);
314  server = ast_sockaddr_stringify_fmt(&statsd_server,
316  ast_debug(3, " StatsD server = %s.\n", server);
317  ast_debug(3, " add newline = %s\n", AST_YESNO(cfg->global->add_newline));
318  ast_debug(3, " prefix = %s\n", cfg->global->prefix);
319 
320  return 0;
321 }
322 
323 static void statsd_shutdown(void)
324 {
325  ast_debug(3, "Shutting down StatsD client.\n");
326  if (socket_fd != -1) {
327  close(socket_fd);
328  socket_fd = -1;
329  }
330 }
331 
332 static int unload_module(void)
333 {
334  statsd_shutdown();
335  aco_info_destroy(&cfg_info);
337  return 0;
338 }
339 
340 static int load_module(void)
341 {
342  if (aco_info_init(&cfg_info)) {
343  aco_info_destroy(&cfg_info);
345  }
346 
347  aco_option_register(&cfg_info, "enabled", ACO_EXACT, global_options,
348  "no", OPT_BOOL_T, 1,
350 
351  aco_option_register(&cfg_info, "add_newline", ACO_EXACT, global_options,
352  "no", OPT_BOOL_T, 1,
354 
355  aco_option_register(&cfg_info, "server", ACO_EXACT, global_options,
356  "127.0.0.1", OPT_SOCKADDR_T, 0,
358 
359  aco_option_register(&cfg_info, "prefix", ACO_EXACT, global_options,
360  "", OPT_CHAR_ARRAY_T, 0,
362 
363  if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
364  struct conf *cfg;
365 
366  ast_log(LOG_NOTICE, "Could not load statsd config; using defaults\n");
367  cfg = conf_alloc();
368  if (!cfg) {
369  aco_info_destroy(&cfg_info);
371  }
372 
373  if (aco_set_defaults(&global_option, "general", cfg->global)) {
374  ast_log(LOG_ERROR, "Failed to initialize statsd defaults.\n");
375  ao2_ref(cfg, -1);
376  aco_info_destroy(&cfg_info);
378  }
379 
381  ao2_ref(cfg, -1);
382  }
383 
384  if (!is_enabled()) {
386  }
387 
388  if (statsd_init()) {
389  unload_module();
391  }
392 
394 }
395 
396 static int reload_module(void)
397 {
398  switch (aco_process_config(&cfg_info, 1)) {
399  case ACO_PROCESS_OK:
400  break;
403  case ACO_PROCESS_ERROR:
404  default:
406  }
407 
408  if (is_enabled()) {
409  if (statsd_init()) {
411  }
412  } else {
413  statsd_shutdown();
414  }
416 }
417 
418 /* The priority of this module is set just after realtime, since it loads
419  * configuration and could be used by any other sort of module.
420  */
422  .support_level = AST_MODULE_SUPPORT_EXTENDED,
423  .load = load_module,
424  .unload = unload_module,
426  .load_pri = AST_MODPRI_REALTIME_DRIVER + 5,
427 );
Type for default handler for ast_sockaddrs.
#define AST_THREADSTORAGE(name)
Define a thread storage variable.
Definition: threadstorage.h:84
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.
static void * conf_alloc(void)
Creates the statis http conf object.
Definition: res_statsd.c:261
static struct aco_type global
Definition: test_config.c:1445
#define MAX_PREFIX
Definition: res_statsd.c:77
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
struct conf_global_options * global
Definition: res_statsd.c:97
static void statsd_shutdown(void)
Definition: res_statsd.c:323
static char is_enabled(void)
Helper function to check if module is enabled.
Definition: res_statsd.c:288
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:714
void AST_OPTIONAL_API_NAME() ast_statsd_log_full_va(const char *metric_name, const char *metric_type, intmax_t value, double sample_rate,...)
Send a stat to the configured statsd server.
Definition: res_statsd.c:199
#define aco_option_register(info, name, matchtype, types, default_val, opt_type, flags,...)
Register a config option.
void AST_OPTIONAL_API_NAME() ast_statsd_log_sample(const char *metric_name, intmax_t value, double sample_rate)
Send a random sampling of a stat to the configured statsd server.
Definition: res_statsd.c:231
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:982
#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:1091
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.
static int unload_module(void)
Definition: res_statsd.c:332
#define ao2_global_obj_ref(holder)
Definition: astobj2.h:925
#define ast_assert(a)
Definition: utils.h:695
#define NULL
Definition: resample.c:96
static void conf_destructor(void *obj)
Disposes of the statsd conf object.
Definition: res_statsd.c:254
The representation of a single configuration file to be processed.
int value
Definition: syslog.c:37
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.
static struct aco_file conf_file
The conf file that&#39;s processed for the module.
Definition: res_statsd.c:277
#define AST_SOCKADDR_STR_DEFAULT
Definition: netsock2.h:207
struct ast_sockaddr statsd_server
Definition: res_statsd.c:89
void AST_OPTIONAL_API_NAME() ast_statsd_log_full(const char *metric_name, const char *metric_type, intmax_t value, double sample_rate)
Send a stat to the configured statsd server.
Definition: res_statsd.c:164
#define ast_strlen_zero(foo)
Definition: strings.h:52
All configuration options for statsd client.
Definition: res_statsd.c:95
#define ast_sockaddr_port(addr)
Get the port number of a socket address.
Definition: netsock2.h:521
Type for default option handler for character array strings.
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:452
#define ast_log
Definition: astobj2.c:42
#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 RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:911
#define ao2_ref(o, delta)
Definition: astobj2.h:464
static int reload_module(void)
Definition: res_statsd.c:396
Network socket handling.
The config had not been edited and no changes applied.
Their was an error and no changes were applied.
static struct aco_type * global_options[]
Definition: res_statsd.c:251
Configuration option-handling.
#define LOG_ERROR
Definition: logger.h:285
The descriptor of a dynamic string XXX storage will be optimized later if needed We use the ts field ...
Definition: strings.h:584
#define ast_sockaddr_set_port(addr, port)
Sets the port number of a socket address.
Definition: netsock2.h:537
void aco_info_destroy(struct aco_info *info)
Destroy an initialized aco_info struct.
#define ao2_global_obj_release(holder)
Definition: astobj2.h:865
Type for default option handler for bools (ast_true/ast_false)
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
void AST_OPTIONAL_API_NAME() ast_statsd_log_string_va(const char *metric_name, const char *metric_type, const char *value, double sample_rate,...)
Send a stat to the configured statsd server.
Definition: res_statsd.c:176
#define ao2_alloc(data_size, destructor_fn)
Definition: astobj2.h:411
char * ast_sockaddr_stringify_fmt(const struct ast_sockaddr *addr, int format)
Convert a socket address to a string.
Definition: netsock2.c:65
#define LOG_NOTICE
Definition: logger.h:263
#define ast_free(a)
Definition: astmm.h:182
static int reload(void)
Definition: cdr_mysql.c:741
int aco_set_defaults(struct aco_type *type, const char *category, void *obj)
Set all default options of obj.
static AO2_GLOBAL_OBJ_STATIC(confs)
Locking container for safe configuration access.
The config was processed and applied.
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
Global configuration options for statsd client.
Definition: res_statsd.c:83
static struct ast_threadstorage statsd_buf
Definition: res_statsd.c:174
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS|AST_MODFLAG_LOAD_ORDER, "HTTP Phone Provisioning",.support_level=AST_MODULE_SUPPORT_EXTENDED,.load=load_module,.unload=unload_module,.reload=reload,.load_pri=AST_MODPRI_CHANNEL_DEPEND,.requires="http",)
static int load_module(void)
Definition: res_statsd.c:340
void AST_OPTIONAL_API_NAME() ast_statsd_log_string(const char *metric_name, const char *metric_type, const char *value, double sample_rate)
Send a stat to the configured statsd server.
Definition: res_statsd.c:111
static struct aco_type global_option
Mapping of the statsd conf struct&#39;s globals to the general context in the config file.
Definition: res_statsd.c:243
#define AST_YESNO(x)
return Yes or No depending on the argument.
Definition: strings.h:139
#define ao2_global_obj_replace_unref(holder, obj)
Definition: astobj2.h:908
#define ACO_FILES(...)
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:688
char prefix[MAX_PREFIX+1]
Definition: res_statsd.c:91
static int socket_fd
Definition: res_statsd.c:80
#define ao2_cleanup(obj)
Definition: astobj2.h:1958
Type information about a category-level configurable object.
const char * filename
static int statsd_init(void)
Definition: res_statsd.c:294
#define ast_random_double()
Returns a random number between 0.0 and 1.0, inclusive.
Definition: utils.h:599
#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:861
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
static void conf_server(const struct conf *cfg, struct ast_sockaddr *addr)
Definition: res_statsd.c:103
Asterisk module definitions.
#define AST_STATSD_COUNTER
Definition: statsd.h:39
#define DEFAULT_STATSD_PORT
Definition: res_statsd.c:75
CONFIG_INFO_STANDARD(cfg_info, confs, conf_alloc,.files=ACO_FILES(&conf_file))
void AST_OPTIONAL_API_NAME() ast_statsd_log(const char *metric_name, const char *metric_type, intmax_t value)
Send a stat to the configured statsd server.
Definition: res_statsd.c:222
#define ast_str_create(init_len)
Create a malloc&#39;ed dynamic length string.
Definition: strings.h:620