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