diff --git a/DOCUMENTATION b/DOCUMENTATION index ba06ead..71827f1 100644 --- a/DOCUMENTATION +++ b/DOCUMENTATION @@ -24,6 +24,11 @@ are ways to set the statname via an Apache note, an outgoing header or staticaly in the configuration. See the StatsdStat directive below for details. +Telegraf-style[2] statsd metrics are also supported, in which case +the stat format would be: + + foo.bar<.suffix>,method=GET,response_code=200 + The module is implemented as a logging hook into Apache's runtime, meaning it runs after your request backend request is completed and data is already being sent to the client. @@ -32,12 +37,13 @@ On my very mediocre VM on my laptop, the average overhead per call was 0.4 milliseconds, so any server grade hardware you have should be able to do better than that. -I've written a companion module for Varnish[2] as well called -libvmod-statsd[3] in case you're running Varnish instead/also. +I've written a companion module for Varnish[3] as well called +libvmod-statsd[4] in case you're running Varnish instead/also. [1] http:/github.com/etsy/statsd -[2] http:/varnish-cache.org -[3] https://github.com/jib/libvmod-statsd +[2] https://github.com/influxdata/telegraf/tree/master/plugins/inputs/statsd#influx-statsd +[3] http:/varnish-cache.org +[4] https://github.com/jib/libvmod-statsd ###################### ### Example @@ -61,7 +67,7 @@ are available as well - see the Configuration section below: Because this module is setup as a logging hook, it's not possible to set an outgoing header with the stat, as the response is already sent. To be -able to discern what the module is doing, it sets a Note[4] that you can +able to discern what the module is doing, it sets a Note[5] that you can inspect in your logfiles. The Note field is %{statsd}n, is whitespace separated and will look something like this: @@ -78,7 +84,7 @@ Note that if your Statsd server is not on localhost, there's no way for the kernel to know that the remote host was not available/misconfigured and can't tell if the delivery failed. -[4] http://httpd.apache.org/docs/current/mod/mod_log_config.html +[5] http://httpd.apache.org/docs/current/mod/mod_log_config.html ###################### ### Configuration @@ -259,7 +265,7 @@ sections of the configuration. sends two stats (a counter and a timer) for every request that is received. - In newer versions of Statsd[5] (v0.6.0 and up), a new naming scheme was + In newer versions of Statsd[6] (v0.6.0 and up), a new naming scheme was adopted for stats sent, which, among others, produces counters for every timer that's sent. @@ -271,5 +277,17 @@ sections of the configuration. namespace feature, you probably want to set this directive to 'off' as well. - [5] https://github.com/etsy/statsd/blob/v0.6.0/exampleConfig.js#L57 + [6] https://github.com/etsy/statsd/blob/v0.6.0/exampleConfig.js#L57 + +*** StatsdTelegrafMode directive + Syntax: StatsdTelegrafMode on|off + Default: StatsdTelegrafMode off + + This directive changes the format of the stat to support InfluxDB tags. + The HTTP method and response code will be sent as tags rather than as + part of the stat. + + You will probably also want to set StatsdStat as to avoid a large number + of series in InfluxDB[7] + [7] https://docs.influxdata.com/influxdb/v1.3/concepts/schema_and_data_layout/#don-t-have-too-many-series diff --git a/mod_statsd.c b/mod_statsd.c index e20ac96..973578c 100644 --- a/mod_statsd.c +++ b/mod_statsd.c @@ -71,6 +71,8 @@ typedef struct { int enabled; // module enabled? int legacy_mode; // legacy namespace mode enabled? + int telegraf_mode; + // telegraf tags mode enabled? int divider; // divide the request time by this number int socket; // statsd connection char *host; // statsd host @@ -298,6 +300,8 @@ static int request_hook(request_rec *r) } } + _DEBUG && fprintf( stderr, "key = %s\n", key ); + /* If you're particular about what verbs you want to track separately, here's the spot to filter them */ @@ -338,20 +342,39 @@ static int request_hook(request_rec *r) (apr_time_now() - r->request_time) / cfg->divider); _DEBUG && fprintf( stderr, "duration %s\n", duration ); + _DEBUG && fprintf( stderr, "key = %s\n", key ); + + char *stat; + if( cfg->telegraf_mode ) { + char nkey[strlen(key) - 1]; + apr_cpystrn(nkey, key, strlen(key)); - // The entire stat, to be sent. Once as a timer, once as a counter. - // Looks something like: prefix.keyname.suffix.GET.200 - char *stat = apr_pstrcat( - r->pool, - cfg->prefix, - key, // no dot between key & method because key - verb, // will always end in a dot. - ".", - apr_psprintf(r->pool, "%d", r->status), - cfg->suffix, - NULL - ); + stat = apr_pstrcat( + r->pool, + cfg->prefix, + nkey, + cfg->suffix, + ",method=", + verb, + ",response_code=", + apr_psprintf(r->pool, "%d", r->status), + NULL + ); + } else { + // The entire stat, to be sent. Once as a timer, once as a counter. + // Looks something like: prefix.keyname.suffix.GET.200 + stat = apr_pstrcat( + r->pool, + cfg->prefix, + key, // no dot between key & method because key + verb, // will always end in a dot. + ".", + apr_psprintf(r->pool, "%d", r->status), + cfg->suffix, + NULL + ); + } _DEBUG && fprintf( stderr, "stat: %s\n", stat ); @@ -378,16 +401,35 @@ static int request_hook(request_rec *r) // You may have also asked for an aggregate stat. If so, add it here. if( strlen( cfg->aggregate_stat ) ) { - char *aggregate_stat = apr_pstrcat( - r->pool, - cfg->prefix, - cfg->aggregate_stat, // no dot between key & method because key - r->method, // will always end in a dot. - ".", - apr_psprintf(r->pool, "%d", r->status), - cfg->suffix, // will always end in a dot. - NULL - ); + char *aggregate_stat; + + if( cfg->telegraf_mode ) { + char nastat[strlen(cfg->aggregate_stat) - 1]; + apr_cpystrn(nastat, cfg->aggregate_stat, strlen(cfg->aggregate_stat)); + + aggregate_stat = apr_pstrcat( + r->pool, + cfg->prefix, + nastat, + cfg->suffix, + ",method=", + verb, + ",response_code=", + apr_psprintf(r->pool, "%d", r->status), + NULL + ); + } else { + aggregate_stat = apr_pstrcat( + r->pool, + cfg->prefix, + cfg->aggregate_stat, // no dot between key & method because key + r->method, // will always end in a dot. + ".", + apr_psprintf(r->pool, "%d", r->status), + cfg->suffix, // will always end in a dot. + NULL + ); + } to_send = apr_pstrcat( r->pool, @@ -454,6 +496,7 @@ static void *init_settings(apr_pool_t *p, char *d) cfg->enabled = 0; cfg->legacy_mode = 1; // default to on, like statsd does: // https://github.com/etsy/statsd/blob/v0.6.0/exampleConfig.js#L57 + cfg->telegraf_mode = 0; cfg->divider = 1000; // default to milliseconds for timing cfg->host = "localhost"; cfg->port = "8125"; @@ -602,6 +645,9 @@ static const char *set_config_enable(cmd_parms *cmd, void *mconfig, } else if( strcasecmp(name, "StatsdLegacyMode") == 0 ) { cfg->legacy_mode = value; + } else if( strcasecmp(name, "StatsdTelegrafMode") == 0 ) { + cfg->telegraf_mode = value; + } else { return apr_psprintf(cmd->pool, "No such variable %s", name); } @@ -632,6 +678,8 @@ static const command_rec commands[] = { "A string to suffix to all the stats sent" ), AP_INIT_TAKE1( "StatsdStat", set_config_value, NULL, OR_FILEINFO, "The stat itself to send"), + AP_INIT_TAKE1( "StatsdTelegrafMode", set_config_enable, NULL, OR_FILEINFO, + "Send HTTP verbs and response status as InfluxDB tags"), AP_INIT_ITERATE("StatsdExclude", set_config_value, NULL, OR_FILEINFO, "A list of regexes of path parts to exclude from stats" ), AP_INIT_ITERATE("StatsdHTTPVerbs", set_config_value, NULL, OR_FILEINFO,