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