Add new AMI action for app_voicemail
[asterisk/asterisk.git] / apps / app_statsd.c
1 /*\r
2  * Asterisk -- An open source telephony toolkit.\r
3  *\r
4  * Copyright (C) 2015, Digium, Inc.\r
5  *\r
6  * Tyler Cambron <tcambron@digium.com>\r
7  *\r
8  * See http://www.asterisk.org for more information about\r
9  * the Asterisk project. Please do not directly contact\r
10  * any of the maintainers of this project for assistance;\r
11  * the project provides a web site, mailing lists and IRC\r
12  * channels for your use.\r
13  *\r
14  * This program is free software, distributed under the terms of\r
15  * the GNU General Public License Version 2. See the LICENSE file\r
16  * at the top of the source tree.\r
17  */\r
18 \r
19  /*** MODULEINFO\r
20         <depend>res_statsd</depend>\r
21         <defaultenabled>no</defaultenabled>\r
22         <support_level>extended</support_level>\r
23  ***/\r
24 \r
25 #include "asterisk.h"\r
26 \r
27 #include <math.h>\r
28 \r
29 #include "asterisk/module.h"\r
30 #include "asterisk/logger.h"\r
31 #include "asterisk/app.h"\r
32 #include "asterisk/pbx.h"\r
33 #include "asterisk/strings.h"\r
34 #include "asterisk/statsd.h"\r
35 \r
36 /*** DOCUMENTATION\r
37         <application name="StatsD" language="en_US">\r
38                 <synopsis>\r
39                         Allow statistics to be passed to the StatsD server from the dialplan.\r
40                 </synopsis>\r
41                 <syntax>\r
42                         <parameter name="metric_type" required="true">\r
43                                 <para>The metric type to be sent to StatsD. Valid metric types\r
44                                 are 'g' for gauge, 'c' for counter, 'ms' for timer, and 's' for\r
45                                 sets.</para>\r
46                         </parameter>\r
47                         <parameter name="statistic_name" required="true">\r
48                                 <para>The name of the variable to be sent to StatsD. Statistic\r
49                                 names cannot contain the pipe (|) character.</para>\r
50                         </parameter>\r
51                         <parameter name="value" required="true">\r
52                                 <para>The value of the variable to be sent to StatsD. Values\r
53                                 must be numeric. Values for gauge and counter metrics can be\r
54                                 sent with a '+' or '-' to update a value after the value has\r
55                                 been initialized. Only counters can be initialized as negative.\r
56                                 Sets can send a string as the value parameter, but the string\r
57                                 cannot contain the pipe character.</para>\r
58                         </parameter>\r
59                         <parameter name="sample_rate">\r
60                                 <para>The value of the sample rate to be sent to StatsD. Sample\r
61                                 rates less than or equal to 0 will never be sent and sample rates\r
62                                 greater than or equal to 1 will always be sent. Any rate\r
63                                 between 1 and 0 will be compared to a randomly generated value,\r
64                                 and if it is greater than the random value, it will be sent.</para>\r
65                         </parameter>\r
66                 </syntax>\r
67                 <description>\r
68                         <para>This dialplan application sends statistics to the StatsD\r
69                         server specified inside of <literal>statsd.conf</literal>.</para>\r
70                 </description>\r
71         </application>\r
72  ***/\r
73 \r
74 static const char app[] = "StatsD";\r
75 \r
76 /*!\r
77  * \brief Check to ensure the value is within the allowed range.\r
78  *\r
79  * \param value The value of the statistic to be sent to StatsD.\r
80  *\r
81  * This function checks to see if the value given to the StatsD daialplan\r
82  * application is within the allowed range of [-2^63, 2^63] as specified by StatsD.\r
83  *\r
84  * \retval zero on success.\r
85  * \retval 1 on error.\r
86  */\r
87 static int value_in_range(const char *value) {\r
88         double numerical_value = strtod(value, NULL);\r
89 \r
90         if (numerical_value < pow(-2, 63) || numerical_value > pow(2, 63)) {\r
91                 ast_log(AST_LOG_WARNING, "Value %lf out of range!\n", numerical_value);\r
92                 return 1;\r
93         }\r
94 \r
95         return 0;\r
96 }\r
97 \r
98 /*!\r
99  * \brief Check to ensure the value is within the allowed range.\r
100  *\r
101  * \param value The value of the statistic to be sent to StatsD.\r
102  *\r
103  * This function checks to see if the value given to the StatsD daialplan\r
104  * application is within the allowed range of [0, 2^64] as specified by StatsD.\r
105  *\r
106  * \retval zero on success.\r
107  * \retval 1 on error.\r
108  */\r
109 static int non_neg_value_range(const char *value) {\r
110         double numerical_value = strtod(value, NULL);\r
111 \r
112         if (numerical_value < 0 || numerical_value > pow(2, 64)) {\r
113                 ast_log(AST_LOG_WARNING, "Value %lf out of range!\n", numerical_value);\r
114                 return 1;\r
115         }\r
116 \r
117         return 0;\r
118 }\r
119 \r
120 /*!\r
121  * \brief Check to ensure the metric type is a valid metric type.\r
122  *\r
123  * \param metric The metric type to be sent to StatsD.\r
124  *\r
125  * This function checks to see if the metric type given to the StatsD dialplan\r
126  * is a valid metric type. Metric types are determined by StatsD.\r
127  *\r
128  * \retval zero on success.\r
129  * \retval 1 on error.\r
130  */\r
131 static int validate_metric(const char *metric)\r
132 {\r
133         const char *valid_metrics[] = {"g","s","ms","c"};\r
134         int i;\r
135 \r
136         if (ast_strlen_zero(metric)) {\r
137                 ast_log(AST_LOG_ERROR, "Missing metric type argument.\n");\r
138                 return 1;\r
139         }\r
140 \r
141         for (i = 0; i < ARRAY_LEN(valid_metrics); i++) {\r
142                 if (!strcmp(valid_metrics[i], metric)) {\r
143                         return 0;\r
144                 }\r
145         }\r
146 \r
147         ast_log(AST_LOG_ERROR, "Invalid metric type %s.\n", metric);\r
148 \r
149         return 1;\r
150 }\r
151 \r
152 /*!\r
153  * \brief Check to ensure that a numeric value is valid.\r
154  *\r
155  * \param numeric_value The numeric value to be sent to StatsD.\r
156  *\r
157  * This function checks to see if a number to be sent to StatsD is actually\r
158  * a valid number. One decimal is allowed.\r
159  *\r
160  * \retval zero on success.\r
161  * \retval 1 on error.\r
162  */\r
163 static int validate_numeric(const char *numeric_value) {\r
164         const char *num;\r
165         int decimal_counter = 0;\r
166 \r
167         num = numeric_value;\r
168         while (*num) {\r
169                 if (!isdigit(*num++)) {\r
170                         if (strstr(numeric_value, ".") != NULL && decimal_counter == 0) {\r
171                                 decimal_counter++;\r
172                                 continue;\r
173                         }\r
174                         ast_log(AST_LOG_ERROR, "%s is not a number!\n", numeric_value);\r
175                         return 1;\r
176                 }\r
177         }\r
178 \r
179         return 0;\r
180 }\r
181 \r
182 /*!\r
183  * \brief Determines the actual value of a number by looking for a leading + or -.\r
184  *\r
185  * \param raw_value The entire numeric string to be sent to StatsD.\r
186  *\r
187  * This function checks to see if the numeric string contains valid characters\r
188  * and then isolates the actual number to be sent for validation. Returns the\r
189  * result of the numeric validation.\r
190  *\r
191  * \retval zero on success.\r
192  * \retval 1 on error.\r
193  */\r
194 static int determine_actual_value(const char *raw_value) {\r
195         const char *actual_value;\r
196 \r
197         if ((raw_value[0] == '+') || (raw_value[0] == '-')) {\r
198                 actual_value = &raw_value[1];\r
199                 if (ast_strlen_zero(actual_value)) {\r
200                         ast_log(AST_LOG_ERROR, "Value argument %s only contains a sign"\r
201                                 " operator.\n", raw_value);\r
202                         return 1;\r
203                 }\r
204         } else {\r
205                 actual_value = &raw_value[0];\r
206         }\r
207 \r
208         return validate_numeric(actual_value);\r
209 }\r
210 \r
211 /*!\r
212  * \brief Check to ensure the statistic name is valid.\r
213  *\r
214  * \param name The variable name to be sent to StatsD.\r
215  *\r
216  * This function checks to see if the statistic name given to the StatsD\r
217  * dialplan application is valid by ensuring that the name does not have any\r
218  * invalid characters.\r
219  *\r
220  * \retval zero on success.\r
221  * \retval 1 on error.\r
222  */\r
223 static int validate_name(const char *name)\r
224 {\r
225         if (ast_strlen_zero(name) || (strstr(name, "|") != NULL)) {\r
226                 ast_log(AST_LOG_ERROR, "Statistic name %s is missing or contains a pipe (|)"\r
227                         " character.\n", name);\r
228                 return 1;\r
229         }\r
230 \r
231         return 0;\r
232 }\r
233 \r
234 \r
235 /*!\r
236  * \brief Calls the appropriate functions to validate a gauge metric.\r
237  *\r
238  * \param statistic_name The statistic name to be sent to StatsD.\r
239  * \param value The value to be sent to StatsD.\r
240  *\r
241  * This function calls other validating functions to correctly validate each\r
242  * input based on allowable input for a gauge metric.\r
243  *\r
244  * \retval zero on success.\r
245  * \retval 1 on error.\r
246  */\r
247 static int validate_metric_type_gauge(const char *statistic_name, const char *value) {\r
248 \r
249         if (ast_strlen_zero(value)) {\r
250                 ast_log(AST_LOG_ERROR, "Missing value argument.\n");\r
251                 return 1;\r
252         }\r
253 \r
254         if (validate_name(statistic_name) || determine_actual_value(value)\r
255                 || value_in_range(value)) {\r
256                 return 1;\r
257         }\r
258 \r
259         return 0;\r
260 }\r
261 \r
262 /*!\r
263  * \brief Calls the appropriate functions to validate a counter metric.\r
264  *\r
265  * \param statistic_name The statistic name to be sent to StatsD.\r
266  * \param value The value to be sent to StatsD.\r
267  *\r
268  * This function calls other validating functions to correctly validate each\r
269  * input based on allowable input for a counter metric.\r
270  *\r
271  * \retval zero on success.\r
272  * \retval 1 on error.\r
273  */\r
274 static int validate_metric_type_counter(const char *statistic_name, const char *value) {\r
275 \r
276         if (ast_strlen_zero(value)) {\r
277                 ast_log(AST_LOG_ERROR, "Missing value argument.\n");\r
278                 return 1;\r
279         }\r
280 \r
281         if (validate_name(statistic_name) || determine_actual_value(value)\r
282                 || value_in_range(value)) {\r
283                 return 1;\r
284         }\r
285 \r
286         return 0;\r
287 }\r
288 \r
289 /*!\r
290  * \brief Calls the appropriate functions to validate a timer metric.\r
291  *\r
292  * \param statistic_name The statistic name to be sent to StatsD.\r
293  * \param value The value to be sent to StatsD.\r
294  *\r
295  * This function calls other validating functions to correctly validate each\r
296  * input based on allowable input for a timer metric.\r
297  *\r
298  * \retval zero on success.\r
299  * \retval 1 on error.\r
300  */\r
301 static int validate_metric_type_timer(const char *statistic_name, const char *value) {\r
302 \r
303         if (ast_strlen_zero(value)) {\r
304                 ast_log(AST_LOG_ERROR, "Missing value argument.\n");\r
305                 return 1;\r
306         }\r
307 \r
308         if (validate_name(statistic_name) || validate_numeric(value)\r
309                 || non_neg_value_range(value)) {\r
310                 return 1;\r
311         }\r
312 \r
313         return 0;\r
314 }\r
315 \r
316 /*!\r
317  * \brief Calls the appropriate functions to validate a set metric.\r
318  *\r
319  * \param statistic_name The statistic name to be sent to StatsD.\r
320  * \param value The value to be sent to StatsD.\r
321  *\r
322  * This function calls other validating functions to correctly validate each\r
323  * input based on allowable input for a set metric.\r
324  *\r
325  * \retval zero on success.\r
326  * \retval 1 on error.\r
327  */\r
328 static int validate_metric_type_set(const char *statistic_name, const char *value) {\r
329         if (ast_strlen_zero(value)) {\r
330                 ast_log(AST_LOG_ERROR, "Missing value argument.\n");\r
331                 return 1;\r
332         }\r
333 \r
334         if (validate_name(statistic_name)) {\r
335                 return 1;\r
336         }\r
337 \r
338         if (strstr(value, "|") != NULL) {\r
339                 ast_log(AST_LOG_ERROR, "Pipe (|) character is not allowed for value %s"\r
340                         " in a set metric.\n", value);\r
341                 return 1;\r
342         }\r
343 \r
344         return 0;\r
345 }\r
346 \r
347 static int statsd_exec(struct ast_channel *chan, const char *data)\r
348 {\r
349         char *stats;\r
350         double numerical_rate = 1.0;\r
351 \r
352         AST_DECLARE_APP_ARGS(args,\r
353                         AST_APP_ARG(metric_type);\r
354                         AST_APP_ARG(statistic_name);\r
355                         AST_APP_ARG(value);\r
356                         AST_APP_ARG(sample_rate);\r
357         );\r
358 \r
359         if (!data) {\r
360                 ast_log(AST_LOG_ERROR, "No parameters were provided. Correct format is "\r
361                         "StatsD(metric_type,statistic_name,value[,sample_rate]). Sample rate is the "\r
362                         "only optional parameter.\n");\r
363                 return 1;\r
364         }\r
365 \r
366         stats = ast_strdupa(data);\r
367         AST_STANDARD_APP_ARGS(args, stats);\r
368 \r
369         if (validate_metric(args.metric_type)) {\r
370                 return 1;\r
371         }\r
372 \r
373         if (!strcmp(args.metric_type, "g")) {\r
374                 if (validate_metric_type_gauge(args.statistic_name, args.value)) {\r
375                         ast_log(AST_LOG_ERROR, "Invalid input for a gauge metric.\n");\r
376                         return 1;\r
377                 }\r
378         }\r
379         else if (!strcmp(args.metric_type, "c")) {\r
380                 if (validate_metric_type_counter(args.statistic_name, args.value)) {\r
381                         ast_log(AST_LOG_ERROR, "Invalid input for a counter metric.\n");\r
382                         return 1;\r
383                 }\r
384         }\r
385         else if (!strcmp(args.metric_type, "ms")) {\r
386                 if (validate_metric_type_timer(args.statistic_name, args.value)) {\r
387                         ast_log(AST_LOG_ERROR, "Invalid input for a timer metric.\n");\r
388                         return 1;\r
389                 }\r
390         }\r
391         else if (!strcmp(args.metric_type, "s")) {\r
392                 if (validate_metric_type_set(args.statistic_name, args.value)) {\r
393                         ast_log(AST_LOG_ERROR, "Invalid input for a set metric.\n");\r
394                         return 1;\r
395                 }\r
396         }\r
397 \r
398         if (args.sample_rate) {\r
399 \r
400                 if (validate_numeric(args.sample_rate)) {\r
401                         return 1;\r
402                 }\r
403 \r
404                 numerical_rate = strtod(args.sample_rate, NULL);\r
405         }\r
406 \r
407         ast_statsd_log_string(args.statistic_name, args.metric_type, args.value,\r
408                 numerical_rate);\r
409 \r
410         return 0;\r
411 }\r
412 \r
413 static int unload_module(void)\r
414 {\r
415         return ast_unregister_application(app);\r
416 }\r
417 \r
418 static int load_module(void)\r
419 {\r
420         return ast_register_application_xml(app, statsd_exec);\r
421 }\r
422 \r
423 AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "StatsD Dialplan Application");\r