StatsD: Add sample rate compatibility

Implemented support for the StatsD sample rate parameter,
which is a parameter for determining when to send computed
statistics to a client.

Valid sample rate values are:
Less than or equal to 0.0 will never be sent.
Between 0.0 and 1.0 will randomly be sent.
Greater than or equal to 1.0 will always be sent.

ASTERISK-25419
Reported By: Ashley Sanders

Change-Id: I11d315d0a5034fffeae1178e650aa8264485ed52
changes/71/1571/2
tcambron 10 years ago
parent f12ebe3584
commit 05addf3d8f

@ -1,248 +1,425 @@
/* /*
* Asterisk -- An open source telephony toolkit. * Asterisk -- An open source telephony toolkit.
* *
* Copyright (C) 2015, Digium, Inc. * Copyright (C) 2015, Digium, Inc.
* *
* Tyler Cambron <tcambron@digium.com> * Tyler Cambron <tcambron@digium.com>
* *
* See http://www.asterisk.org for more information about * See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact * the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance; * any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC * the project provides a web site, mailing lists and IRC
* channels for your use. * channels for your use.
* *
* This program is free software, distributed under the terms of * This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file * the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree. * at the top of the source tree.
*/ */
/*** MODULEINFO /*** MODULEINFO
<depend>res_statsd</depend> <depend>res_statsd</depend>
<defaultenabled>no</defaultenabled> <defaultenabled>no</defaultenabled>
<support_level>extended</support_level> <support_level>extended</support_level>
***/ ***/
#include "asterisk.h" #include "asterisk.h"
ASTERISK_REGISTER_FILE() ASTERISK_REGISTER_FILE()
#include <math.h> #include <math.h>
#include "asterisk/module.h" #include "asterisk/module.h"
#include "asterisk/logger.h" #include "asterisk/logger.h"
#include "asterisk/app.h" #include "asterisk/app.h"
#include "asterisk/pbx.h" #include "asterisk/pbx.h"
#include "asterisk/strings.h" #include "asterisk/strings.h"
#include "asterisk/statsd.h" #include "asterisk/statsd.h"
/*** DOCUMENTATION /*** DOCUMENTATION
<application name="StatsD" language="en_US"> <application name="StatsD" language="en_US">
<synopsis> <synopsis>
Allow statistics to be passed to the StatsD server from the dialplan. Allow statistics to be passed to the StatsD server from the dialplan.
</synopsis> </synopsis>
<syntax> <syntax>
<parameter name="metric_type" required="true"> <parameter name="metric_type" required="true">
<para>The metric type to be sent to StatsD.</para> <para>The metric type to be sent to StatsD. Valid metric types
</parameter> are 'g' for gauge, 'c' for counter, 'ms' for timer, and 's' for
<parameter name="statistic_name" required="true"> sets.</para>
<para>The name of the variable to be sent to StatsD.</para> </parameter>
</parameter> <parameter name="statistic_name" required="true">
<parameter name="value" required="true"> <para>The name of the variable to be sent to StatsD. Statistic
<para>The value of the variable to be sent to StatsD.</para> names cannot contain the pipe (|) character.</para>
</parameter> </parameter>
</syntax> <parameter name="value" required="true">
<description> <para>The value of the variable to be sent to StatsD. Values
<para>This dialplan application sends statistics to the StatsD must be numeric. Values for gauge and counter metrics can be
server specified inside of <literal>statsd.conf</literal>.</para> sent with a '+' or '-' to update a value after the value has
</description> been initialized. Only counters can be initialized as negative.
</application> Sets can send a string as the value parameter, but the string
***/ cannot contain the pipe character.</para>
</parameter>
static const char app[] = "StatsD"; <parameter name="sample_rate">
<para>The value of the sample rate to be sent to StatsD. Sample
/*! rates less than or equal to 0 will never be sent and sample rates
* \brief Check to ensure the value is within the allowed range. greater than or equal to 1 will always be sent. Any rate
* between 1 and 0 will be compared to a randomly generated value,
* \param value The value of the statistic to be sent to StatsD. and if it is greater than the random value, it will be sent.</para>
* \param metric The metric type to be sent to StatsD. </parameter>
* </syntax>
* This function checks to see if the value given to the StatsD daialplan <description>
* application is within the allowed range as specified by StatsD. A counter <para>This dialplan application sends statistics to the StatsD
* is the only metric type allowed to be initialized as a negative number. server specified inside of <literal>statsd.conf</literal>.</para>
* </description>
* \retval zero on success. </application>
* \retval 1 on error. ***/
*/
static int value_in_range(const char *value, const char *metric) static const char app[] = "StatsD";
{
double numerical_value = strtod(value, NULL); /*!
* \brief Check to ensure the value is within the allowed range.
if (!strcmp(metric, "c")) { *
if (numerical_value < pow(-2, 63) || numerical_value > pow(2, 63)) { * \param value The value of the statistic to be sent to StatsD.
ast_log(AST_LOG_WARNING, "Value %lf out of range!\n", numerical_value); *
return 1; * This function checks to see if the value given to the StatsD daialplan
} * application is within the allowed range of [-2^63, 2^63] as specified by StatsD.
} else { *
if (numerical_value < 0 || numerical_value > pow(2, 64)) { * \retval zero on success.
ast_log(AST_LOG_WARNING, "Value %lf out of range!\n", numerical_value); * \retval 1 on error.
return 1; */
} static int value_in_range(const char *value) {
} double numerical_value = strtod(value, NULL);
return 0; if (numerical_value < pow(-2, 63) || numerical_value > pow(2, 63)) {
} ast_log(AST_LOG_WARNING, "Value %lf out of range!\n", numerical_value);
return 1;
/*! }
* \brief Check to ensure the metric type is a valid metric type.
* return 0;
* \param metric The metric type to be sent to StatsD. }
*
* This function checks to see if the metric type given to the StatsD dialplan /*!
* is a valid metric type. Metric types are determined by StatsD. * \brief Check to ensure the value is within the allowed range.
* *
* \retval zero on success. * \param value The value of the statistic to be sent to StatsD.
* \retval 1 on error. *
*/ * This function checks to see if the value given to the StatsD daialplan
static int validate_metric(const char *metric) * application is within the allowed range of [0, 2^64] as specified by StatsD.
{ *
const char *valid_metrics[] = {"g","s","ms","c"}; * \retval zero on success.
int i; * \retval 1 on error.
*/
if (ast_strlen_zero(metric)) { static int non_neg_value_range(const char *value) {
ast_log(AST_LOG_ERROR, "Missing metric type argument.\n"); double numerical_value = strtod(value, NULL);
return 1;
} if (numerical_value < 0 || numerical_value > pow(2, 64)) {
ast_log(AST_LOG_WARNING, "Value %lf out of range!\n", numerical_value);
for (i = 0; i < ARRAY_LEN(valid_metrics); i++) { return 1;
if (!strcmp(valid_metrics[i], metric)) { }
return 0;
} return 0;
} }
ast_log(AST_LOG_ERROR, "Invalid metric type %s.\n", metric); /*!
* \brief Check to ensure the metric type is a valid metric type.
return 1; *
} * \param metric The metric type to be sent to StatsD.
*
/*! * This function checks to see if the metric type given to the StatsD dialplan
* \brief Check to ensure the statistic name is valid. * is a valid metric type. Metric types are determined by StatsD.
* *
* \param name The variable name to be sent to StatsD. * \retval zero on success.
* * \retval 1 on error.
* This function checks to see if the statistic name given to the StatsD */
* dialplan application is valid by ensuring that the name does not have any static int validate_metric(const char *metric)
* invalid characters. {
* const char *valid_metrics[] = {"g","s","ms","c"};
* \retval zero on success. int i;
* \retval 1 on error.
*/ if (ast_strlen_zero(metric)) {
static int validate_name(const char *name) ast_log(AST_LOG_ERROR, "Missing metric type argument.\n");
{ return 1;
if (ast_strlen_zero(name) || (strstr(name, "|") != NULL)) { }
ast_log(AST_LOG_ERROR, "Statistic name %s is missing or contains a pipe (|)"
" character.\n", name); for (i = 0; i < ARRAY_LEN(valid_metrics); i++) {
return 1; if (!strcmp(valid_metrics[i], metric)) {
} return 0;
}
return 0; }
}
ast_log(AST_LOG_ERROR, "Invalid metric type %s.\n", metric);
/*!
* \brief Check to ensure the value is valid. return 1;
* }
* \param value The value of the statistic to be sent to StatsD.
* \param metric The metric type to be sent to StatsD. /*!
* * \brief Check to ensure that a numeric value is valid.
* This function checks to see if the value given to the StatsD daialplan *
* application is valid by testing if it is numeric. A plus or minus is only * \param numeric_value The numeric value to be sent to StatsD.
* allowed at the beginning of the value if it is a counter or a gauge. *
* * This function checks to see if a number to be sent to StatsD is actually
* \retval zero on success. * a valid number. One decimal is allowed.
* \retval 1 on error. *
*/ * \retval zero on success.
static int validate_value(const char *value, const char *metric) * \retval 1 on error.
{ */
const char *actual_value; static int validate_numeric(const char *numeric_value) {
const char *num;
if (ast_strlen_zero(value)) { int decimal_counter = 0;
ast_log(AST_LOG_ERROR, "Missing value argument.\n");
return 1; num = numeric_value;
} while (*num) {
if (!isdigit(*num++)) {
if (!strcmp(metric, "g") || !strcmp(metric, "c")) { if (strstr(numeric_value, ".") != NULL && decimal_counter == 0) {
if ((value[0] == '+') || (value[0] == '-')) { decimal_counter++;
actual_value = &value[1]; continue;
if (ast_strlen_zero(actual_value)) { }
ast_log(AST_LOG_ERROR, "Value argument %s only contains a sign" ast_log(AST_LOG_ERROR, "%s is not a number!\n", numeric_value);
" operator.\n", value); return 1;
return 1; }
} }
} else {
actual_value = &value[0]; return 0;
} }
} else {
actual_value = &value[0]; /*!
} * \brief Determines the actual value of a number by looking for a leading + or -.
*
if (!isdigit(*actual_value)) { * \param raw_value The entire numeric string to be sent to StatsD.
ast_log(AST_LOG_ERROR, "Value of %s is not a valid number!\n", actual_value); *
return 1; * This function checks to see if the numeric string contains valid characters
} * and then isolates the actual number to be sent for validation. Returns the
* result of the numeric validation.
if (value_in_range(actual_value, metric)) { *
return 1; * \retval zero on success.
} * \retval 1 on error.
*/
return 0; static int determine_actual_value(const char *raw_value) {
} const char *actual_value;
static int statsd_exec(struct ast_channel *chan, const char *data) if ((raw_value[0] == '+') || (raw_value[0] == '-')) {
{ actual_value = &raw_value[1];
char *stats; if (ast_strlen_zero(actual_value)) {
double numerical_value; ast_log(AST_LOG_ERROR, "Value argument %s only contains a sign"
" operator.\n", raw_value);
AST_DECLARE_APP_ARGS(args, return 1;
AST_APP_ARG(metric_type); }
AST_APP_ARG(statistic_name); } else {
AST_APP_ARG(value); actual_value = &raw_value[0];
); }
if (!data) { return validate_numeric(actual_value);
ast_log(AST_LOG_ERROR, "No parameters were provided. Correct format is " }
"StatsD(metric_type,statistic_name,value). All parameters are required.\n");
return 1; /*!
} * \brief Check to ensure the statistic name is valid.
*
stats = ast_strdupa(data); * \param name The variable name to be sent to StatsD.
AST_STANDARD_APP_ARGS(args, stats); *
* This function checks to see if the statistic name given to the StatsD
/* If any of the validations fail, emit a warning message. */ * dialplan application is valid by ensuring that the name does not have any
if (validate_metric(args.metric_type) || validate_name(args.statistic_name) * invalid characters.
|| validate_value(args.value, args.metric_type)) { *
ast_log(AST_LOG_WARNING, "Invalid parameters provided. Correct format is " * \retval zero on success.
"StatsD(metric_type,statistic_name,value). All parameters are required.\n"); * \retval 1 on error.
*/
return 1; static int validate_name(const char *name)
} {
if (ast_strlen_zero(name) || (strstr(name, "|") != NULL)) {
/* ast_log(AST_LOG_ERROR, "Statistic name %s is missing or contains a pipe (|)"
* Conversion to a double is safe here since the value would have been validated as a " character.\n", name);
* number in validate_value(). return 1;
*/ }
numerical_value = strtod(args.value, NULL);
ast_statsd_log(args.statistic_name, args.metric_type, numerical_value); return 0;
}
return 0;
}
/*!
static int unload_module(void) * \brief Calls the appropriate functions to validate a gauge metric.
{ *
return ast_unregister_application(app); * \param statistic_name The statistic name to be sent to StatsD.
} * \param value The value to be sent to StatsD.
*
static int load_module(void) * This function calls other validating functions to correctly validate each
{ * input based on allowable input for a gauge metric.
return ast_register_application_xml(app, statsd_exec); *
} * \retval zero on success.
* \retval 1 on error.
AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "StatsD Dialplan Application"); */
static int validate_metric_type_gauge(const char *statistic_name, const char *value) {
if (ast_strlen_zero(value)) {
ast_log(AST_LOG_ERROR, "Missing value argument.\n");
return 1;
}
if (validate_name(statistic_name) || determine_actual_value(value)
|| value_in_range(value)) {
return 1;
}
return 0;
}
/*!
* \brief Calls the appropriate functions to validate a counter metric.
*
* \param statistic_name The statistic name to be sent to StatsD.
* \param value The value to be sent to StatsD.
*
* This function calls other validating functions to correctly validate each
* input based on allowable input for a counter metric.
*
* \retval zero on success.
* \retval 1 on error.
*/
static int validate_metric_type_counter(const char *statistic_name, const char *value) {
if (ast_strlen_zero(value)) {
ast_log(AST_LOG_ERROR, "Missing value argument.\n");
return 1;
}
if (validate_name(statistic_name) || determine_actual_value(value)
|| value_in_range(value)) {
return 1;
}
return 0;
}
/*!
* \brief Calls the appropriate functions to validate a timer metric.
*
* \param statistic_name The statistic name to be sent to StatsD.
* \param value The value to be sent to StatsD.
*
* This function calls other validating functions to correctly validate each
* input based on allowable input for a timer metric.
*
* \retval zero on success.
* \retval 1 on error.
*/
static int validate_metric_type_timer(const char *statistic_name, const char *value) {
if (ast_strlen_zero(value)) {
ast_log(AST_LOG_ERROR, "Missing value argument.\n");
return 1;
}
if (validate_name(statistic_name) || validate_numeric(value)
|| non_neg_value_range(value)) {
return 1;
}
return 0;
}
/*!
* \brief Calls the appropriate functions to validate a set metric.
*
* \param statistic_name The statistic name to be sent to StatsD.
* \param value The value to be sent to StatsD.
*
* This function calls other validating functions to correctly validate each
* input based on allowable input for a set metric.
*
* \retval zero on success.
* \retval 1 on error.
*/
static int validate_metric_type_set(const char *statistic_name, const char *value) {
if (ast_strlen_zero(value)) {
ast_log(AST_LOG_ERROR, "Missing value argument.\n");
return 1;
}
if (validate_name(statistic_name)) {
return 1;
}
if (strstr(value, "|") != NULL) {
ast_log(AST_LOG_ERROR, "Pipe (|) character is not allowed for value %s"
" in a set metric.\n", value);
return 1;
}
return 0;
}
static int statsd_exec(struct ast_channel *chan, const char *data)
{
char *stats;
double numerical_rate = 1.0;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(metric_type);
AST_APP_ARG(statistic_name);
AST_APP_ARG(value);
AST_APP_ARG(sample_rate);
);
if (!data) {
ast_log(AST_LOG_ERROR, "No parameters were provided. Correct format is "
"StatsD(metric_type,statistic_name,value[,sample_rate]). Sample rate is the "
"only optional parameter.\n");
return 1;
}
stats = ast_strdupa(data);
AST_STANDARD_APP_ARGS(args, stats);
if (validate_metric(args.metric_type)) {
return 1;
}
if (!strcmp(args.metric_type, "g")) {
if (validate_metric_type_gauge(args.statistic_name, args.value)) {
ast_log(AST_LOG_ERROR, "Invalid input for a gauge metric.\n");
return 1;
}
}
else if (!strcmp(args.metric_type, "c")) {
if (validate_metric_type_counter(args.statistic_name, args.value)) {
ast_log(AST_LOG_ERROR, "Invalid input for a counter metric.\n");
return 1;
}
}
else if (!strcmp(args.metric_type, "ms")) {
if (validate_metric_type_timer(args.statistic_name, args.value)) {
ast_log(AST_LOG_ERROR, "Invalid input for a timer metric.\n");
return 1;
}
}
else if (!strcmp(args.metric_type, "s")) {
if (validate_metric_type_set(args.statistic_name, args.value)) {
ast_log(AST_LOG_ERROR, "Invalid input for a set metric.\n");
return 1;
}
}
if (args.sample_rate) {
if (validate_numeric(args.sample_rate)) {
return 1;
}
numerical_rate = strtod(args.sample_rate, NULL);
}
ast_statsd_log_string(args.statistic_name, args.metric_type, args.value,
numerical_rate);
return 0;
}
static int unload_module(void)
{
return ast_unregister_application(app);
}
static int load_module(void)
{
return ast_register_application_xml(app, statsd_exec);
}
AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "StatsD Dialplan Application");

Loading…
Cancel
Save