Remove ASTERISK_REGISTER_FILE.
[asterisk/asterisk.git] / res / 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                 <configFile name="statsd.conf">
34                         <configObject name="global">
35                                 <synopsis>Global configuration settings</synopsis>
36                                 <configOption name="enabled">
37                                         <synopsis>Enable/disable the statsd module</synopsis>
38                                 </configOption>
39                                 <configOption name="server">
40                                         <synopsis>Address of the statsd server</synopsis>
41                                 </configOption>
42                                 <configOption name="prefix">
43                                         <synopsis>Prefix to prepend to every metric</synopsis>
44                                 </configOption>
45                                 <configOption name="add_newline">
46                                         <synopsis>Append a newline to every event. This is useful if you want to fake out a server using netcat (nc -lu 8125)</synopsis>
47                                 </configOption>
48                         </configObject>
49                 </configFile>
50         </configInfo>
51 ***/
52
53 #include "asterisk.h"
54
55 #include "asterisk/config_options.h"
56 #include "asterisk/module.h"
57 #include "asterisk/netsock2.h"
58
59 #define AST_API_MODULE
60 #include "asterisk/statsd.h"
61
62 #define DEFAULT_STATSD_PORT 8125
63
64 #define MAX_PREFIX 40
65
66 /*! Socket for sending statd messages */
67 static int socket_fd = -1;
68
69 /*! \brief Global configuration options for statsd client. */
70 struct conf_global_options {
71         /*! Enabled by default, disabled if false. */
72         int enabled;
73         /*! Disabled by default, appends newlines to all messages when enabled. */
74         int add_newline;
75         /*! Statsd server address[:port]. */
76         struct ast_sockaddr statsd_server;
77         /*! Prefix to put on every stat. */
78         char prefix[MAX_PREFIX + 1];
79 };
80
81 /*! \brief All configuration options for statsd client. */
82 struct conf {
83         /*! The general section configuration options. */
84         struct conf_global_options *global;
85 };
86
87 /*! \brief Locking container for safe configuration access. */
88 static AO2_GLOBAL_OBJ_STATIC(confs);
89
90 static void conf_server(const struct conf *cfg, struct ast_sockaddr *addr)
91 {
92         *addr = cfg->global->statsd_server;
93         if (ast_sockaddr_port(addr) == 0) {
94                 ast_sockaddr_set_port(addr, DEFAULT_STATSD_PORT);
95         }
96 }
97
98 void AST_OPTIONAL_API_NAME(ast_statsd_log_string)(const char *metric_name,
99         const char *metric_type, const char *value, double sample_rate)
100 {
101         struct conf *cfg;
102         struct ast_str *msg;
103         size_t len;
104         struct ast_sockaddr statsd_server;
105
106         if (socket_fd == -1) {
107                 return;
108         }
109
110         /* Rates <= 0.0 never get logged.
111          * Rates >= 1.0 always get logged.
112          * All others leave it to chance.
113          */
114         if (sample_rate <= 0.0 ||
115                 (sample_rate < 1.0 && sample_rate < ast_random_double())) {
116                 return;
117         }
118
119         cfg = ao2_global_obj_ref(confs);
120         conf_server(cfg, &statsd_server);
121
122         msg = ast_str_create(40);
123         if (!msg) {
124                 ao2_cleanup(cfg);
125                 return;
126         }
127
128         if (!ast_strlen_zero(cfg->global->prefix)) {
129                 ast_str_append(&msg, 0, "%s.", cfg->global->prefix);
130         }
131
132         ast_str_append(&msg, 0, "%s:%s|%s", metric_name, value, metric_type);
133
134         if (sample_rate < 1.0) {
135                 ast_str_append(&msg, 0, "|@%.2f", sample_rate);
136         }
137
138         if (cfg->global->add_newline) {
139                 ast_str_append(&msg, 0, "\n");
140         }
141
142         len = ast_str_strlen(msg);
143
144         ast_debug(6, "Sending statistic %s to StatsD server\n", ast_str_buffer(msg));
145         ast_sendto(socket_fd, ast_str_buffer(msg), len, 0, &statsd_server);
146
147         ao2_cleanup(cfg);
148         ast_free(msg);
149 }
150
151 void AST_OPTIONAL_API_NAME(ast_statsd_log_full)(const char *metric_name,
152         const char *metric_type, intmax_t value, double sample_rate)
153 {
154         char char_value[30];
155         snprintf(char_value, sizeof(char_value), "%jd", value);
156
157         ast_statsd_log_string(metric_name, metric_type, char_value, sample_rate);
158
159 }
160
161 AST_THREADSTORAGE(statsd_buf);
162
163 void AST_OPTIONAL_API_NAME(ast_statsd_log_string_va)(const char *metric_name,
164         const char *metric_type, const char *value, double sample_rate, ...)
165 {
166         struct ast_str *buf;
167         va_list ap;
168         int res;
169
170         buf = ast_str_thread_get(&statsd_buf, 128);
171         if (!buf) {
172                 return;
173         }
174
175         va_start(ap, sample_rate);
176         res = ast_str_set_va(&buf, 0, metric_name, ap);
177         va_end(ap);
178
179         if (res == AST_DYNSTR_BUILD_FAILED) {
180                 return;
181         }
182
183         ast_statsd_log_string(ast_str_buffer(buf), metric_type, value, sample_rate);
184 }
185
186 void AST_OPTIONAL_API_NAME(ast_statsd_log_full_va)(const char *metric_name,
187         const char *metric_type, intmax_t 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_full(ast_str_buffer(buf), metric_type, value, sample_rate);
207 }
208
209 void AST_OPTIONAL_API_NAME(ast_statsd_log)(const char *metric_name,
210         const char *metric_type, intmax_t value)
211 {
212         char char_value[30];
213         snprintf(char_value, sizeof(char_value), "%jd", value);
214
215         ast_statsd_log_string(metric_name, metric_type, char_value, 1.0);
216 }
217
218 void AST_OPTIONAL_API_NAME(ast_statsd_log_sample)(const char *metric_name,
219         intmax_t value, double sample_rate)
220 {
221         char char_value[30];
222         snprintf(char_value, sizeof(char_value), "%jd", value);
223
224         ast_statsd_log_string(metric_name, AST_STATSD_COUNTER, char_value,
225                 sample_rate);
226 }
227
228 /*! \brief Mapping of the statsd conf struct's globals to the
229  *         general context in the config file. */
230 static struct aco_type global_option = {
231         .type = ACO_GLOBAL,
232         .name = "global",
233         .item_offset = offsetof(struct conf, global),
234         .category = "^general$",
235         .category_match = ACO_WHITELIST
236 };
237
238 static struct aco_type *global_options[] = ACO_TYPES(&global_option);
239
240 /*! \brief Disposes of the statsd conf object */
241 static void conf_destructor(void *obj)
242 {
243     struct conf *cfg = obj;
244     ao2_cleanup(cfg->global);
245 }
246
247 /*! \brief Creates the statis http conf object. */
248 static void *conf_alloc(void)
249 {
250     struct conf *cfg;
251
252     if (!(cfg = ao2_alloc(sizeof(*cfg), conf_destructor))) {
253         return NULL;
254     }
255
256     if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), NULL))) {
257         ao2_ref(cfg, -1);
258         return NULL;
259     }
260     return cfg;
261 }
262
263 /*! \brief The conf file that's processed for the module. */
264 static struct aco_file conf_file = {
265         /*! The config file name. */
266         .filename = "statsd.conf",
267         /*! The mapping object types to be processed. */
268         .types = ACO_TYPES(&global_option),
269 };
270
271 CONFIG_INFO_STANDARD(cfg_info, confs, conf_alloc,
272                      .files = ACO_FILES(&conf_file));
273
274 /*! \brief Helper function to check if module is enabled. */
275 static char is_enabled(void)
276 {
277         RAII_VAR(struct conf *, cfg, ao2_global_obj_ref(confs), ao2_cleanup);
278         return cfg->global->enabled;
279 }
280
281 static int statsd_init(void)
282 {
283         RAII_VAR(struct conf *, cfg, ao2_global_obj_ref(confs), ao2_cleanup);
284         char *server;
285         struct ast_sockaddr statsd_server;
286
287         ast_assert(is_enabled());
288
289         ast_debug(3, "Configuring statsd client.\n");
290
291         if (socket_fd == -1) {
292                 ast_debug(3, "Creating statsd socket.\n");
293                 socket_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
294                 if (socket_fd == -1) {
295                         perror("Error creating statsd socket");
296                         return -1;
297                 }
298         }
299
300         conf_server(cfg, &statsd_server);
301         server = ast_sockaddr_stringify_fmt(&statsd_server,
302                 AST_SOCKADDR_STR_DEFAULT);
303         ast_debug(3, "  statsd server = %s.\n", server);
304         ast_debug(3, "  add newline = %s\n", AST_YESNO(cfg->global->add_newline));
305         ast_debug(3, "  prefix = %s\n", cfg->global->prefix);
306
307         return 0;
308 }
309
310 static void statsd_shutdown(void)
311 {
312         ast_debug(3, "Shutting down statsd client.\n");
313         if (socket_fd != -1) {
314                 close(socket_fd);
315                 socket_fd = -1;
316         }
317 }
318
319 static int load_module(void)
320 {
321         if (aco_info_init(&cfg_info)) {
322                 aco_info_destroy(&cfg_info);
323                 return AST_MODULE_LOAD_DECLINE;
324         }
325
326         aco_option_register(&cfg_info, "enabled", ACO_EXACT, global_options,
327                 "no", OPT_BOOL_T, 1,
328                 FLDSET(struct conf_global_options, enabled));
329
330         aco_option_register(&cfg_info, "add_newline", ACO_EXACT, global_options,
331                 "no", OPT_BOOL_T, 1,
332                 FLDSET(struct conf_global_options, add_newline));
333
334         aco_option_register(&cfg_info, "server", ACO_EXACT, global_options,
335                 "127.0.0.1", OPT_SOCKADDR_T, 0,
336                 FLDSET(struct conf_global_options, statsd_server));
337
338         aco_option_register(&cfg_info, "prefix", ACO_EXACT, global_options,
339                 "", OPT_CHAR_ARRAY_T, 0,
340                 CHARFLDSET(struct conf_global_options, prefix));
341
342         if (aco_process_config(&cfg_info, 0)) {
343                 aco_info_destroy(&cfg_info);
344                 return AST_MODULE_LOAD_DECLINE;
345         }
346
347         if (!is_enabled()) {
348                 return AST_MODULE_LOAD_SUCCESS;
349         }
350
351         if (statsd_init() != 0) {
352                 return AST_MODULE_LOAD_FAILURE;
353         }
354
355         return AST_MODULE_LOAD_SUCCESS;
356 }
357
358 static int unload_module(void)
359 {
360         statsd_shutdown();
361         aco_info_destroy(&cfg_info);
362         ao2_global_obj_release(confs);
363         return 0;
364 }
365
366 static int reload_module(void)
367 {
368         if (aco_process_config(&cfg_info, 1)) {
369                 return AST_MODULE_LOAD_DECLINE;
370         }
371
372         if (is_enabled()) {
373                 return statsd_init();
374         } else {
375                 statsd_shutdown();
376                 return AST_MODULE_LOAD_SUCCESS;
377         }
378 }
379
380 /* The priority of this module is set to be as low as possible, since it could
381  * be used by any other sort of module.
382  */
383 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Statsd client support",
384         .support_level = AST_MODULE_SUPPORT_EXTENDED,
385         .load = load_module,
386         .unload = unload_module,
387         .reload = reload_module,
388         .load_pri = 0,
389 );