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