├── LICENSE ├── README.mkd ├── config └── ngx_http_statsd.c /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Zebrafish Labs Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | POSSIBILITY OF SUCH DAMAGE. 25 | 26 | ----------------------------------------------------------------------------- 27 | 28 | The nginx-statsd module was based on code in the nginx-udplog-module 29 | (https://github.com/vkholodkov/nginx-udplog-module), which has the following 30 | license and requirements. 31 | 32 | * Copyright (c) 2010, Valery Kholodkov 33 | * All rights reserved. 34 | * 35 | * Redistribution and use in source and binary forms, with or without 36 | * modification, are permitted provided that the following conditions are met: 37 | * * Redistributions of source code must retain the above copyright 38 | * notice, this list of conditions and the following disclaimer. 39 | * * Redistributions in binary form must reproduce the above copyright 40 | * notice, this list of conditions and the following disclaimer in the 41 | * documentation and/or other materials provided with the distribution. 42 | * * Neither the name of the Valery Kholodkov nor the 43 | * names of its contributors may be used to endorse or promote products 44 | * derived from this software without specific prior written permission. 45 | * 46 | * THIS SOFTWARE IS PROVIDED BY VALERY KHOLODKOV ''AS IS'' AND ANY 47 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 48 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 49 | * DISCLAIMED. IN NO EVENT SHALL VALERY KHOLODKOV BE LIABLE FOR ANY 50 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 51 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 52 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 53 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 54 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 55 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 56 | -------------------------------------------------------------------------------- /README.mkd: -------------------------------------------------------------------------------- 1 | nginx-statsd 2 | ============ 3 | 4 | An nginx module for sending statistics to statsd. 5 | 6 | This is how to use the nginx-statsd module: 7 | 8 | http { 9 | 10 | # Set the server that you want to send stats to. 11 | statsd_server your.statsd.server.com; 12 | 13 | # Randomly sample 10% of requests so that you do not overwhelm your statsd server. 14 | # Defaults to sending all statsd (100%). 15 | statsd_sample_rate 10; # 10% of requests 16 | 17 | 18 | server { 19 | listen 80; 20 | server_name www.your.domain.com; 21 | 22 | # Increment "your_product.requests" by 1 whenever any request hits this server. 23 | statsd_count "your_product.requests" 1; 24 | 25 | location / { 26 | 27 | # Increment the key by 1 when this location is hit. 28 | statsd_count "your_product.pages.index_requests" 1; 29 | 30 | # Increment the key by 1, but only if $request_completion is set to something. 31 | statsd_count "your_product.pages.index_responses" 1 "$request_completion"; 32 | 33 | # Send a timing to "your_product.pages.index_response_time" equal to the value 34 | # returned from the upstream server. If this value evaluates to 0 or empty-string, 35 | # it will not be sent. Thus, there is no need to add a test. 36 | statsd_timing "your_product.pages.index_response_time" "$upstream_response_time"; 37 | 38 | # Increment a key based on the value of a custom header. Only sends the value if 39 | # the custom header exists in the upstream response. 40 | statsd_count "your_product.custom_$upstream_http_x_some_custom_header" 1 41 | "$upstream_http_x_some_custom_header"; 42 | 43 | proxy_pass http://some.other.domain.com; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_statsd 2 | HTTP_MODULES="$HTTP_MODULES ngx_http_statsd_module" 3 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_statsd.c" 4 | CORE_LIBS="$CORE_LIBS -lssl" 5 | -------------------------------------------------------------------------------- /ngx_http_statsd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * nginx-statsd module 3 | * Copyright (C) 2012 Zebrafish Labs Inc. 4 | * 5 | * Much of this source code was derived from nginx-udplog-module which 6 | * has the following copyright. Please refer to the LICENSE file for 7 | * details. 8 | * 9 | * Copyright (C) 2010 Valery Kholodkov 10 | */ 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define STATSD_DEFAULT_PORT 8125 17 | 18 | #define STATSD_TYPE_COUNTER 0x0001 19 | #define STATSD_TYPE_TIMING 0x0002 20 | 21 | #define STATSD_MAX_STR 256 22 | 23 | #define ngx_conf_merge_ptr_value(conf, prev, default) \ 24 | if (conf == NGX_CONF_UNSET_PTR) { \ 25 | conf = (prev == NGX_CONF_UNSET_PTR) ? default : prev; \ 26 | } 27 | 28 | #if defined nginx_version && nginx_version >= 8021 29 | typedef ngx_addr_t ngx_statsd_addr_t; 30 | #else 31 | typedef ngx_peer_addr_t ngx_statsd_addr_t; 32 | #endif 33 | 34 | typedef struct { 35 | ngx_statsd_addr_t peer_addr; 36 | ngx_resolver_connection_t *udp_connection; 37 | ngx_log_t *log; 38 | } ngx_udp_endpoint_t; 39 | 40 | typedef struct { 41 | ngx_array_t *endpoints; 42 | } ngx_http_statsd_main_conf_t; 43 | 44 | typedef struct { 45 | ngx_uint_t type; 46 | 47 | ngx_str_t key; 48 | ngx_uint_t metric; 49 | ngx_flag_t valid; 50 | 51 | ngx_http_complex_value_t *ckey; 52 | ngx_http_complex_value_t *cmetric; 53 | ngx_http_complex_value_t *cvalid; 54 | } ngx_statsd_stat_t; 55 | 56 | typedef struct { 57 | int off; 58 | ngx_udp_endpoint_t *endpoint; 59 | ngx_uint_t sample_rate; 60 | ngx_array_t *stats; 61 | } ngx_http_statsd_conf_t; 62 | 63 | ngx_int_t ngx_udp_connect(ngx_resolver_connection_t *rec); 64 | 65 | static void ngx_statsd_updater_cleanup(void *data); 66 | static ngx_int_t ngx_http_statsd_udp_send(ngx_udp_endpoint_t *l, u_char *buf, size_t len); 67 | 68 | static void *ngx_http_statsd_create_main_conf(ngx_conf_t *cf); 69 | static void *ngx_http_statsd_create_loc_conf(ngx_conf_t *cf); 70 | static char *ngx_http_statsd_merge_loc_conf(ngx_conf_t *cf, void *parent, 71 | void *child); 72 | 73 | static char *ngx_http_statsd_set_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 74 | static char *ngx_http_statsd_add_stat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf, ngx_uint_t type); 75 | static char *ngx_http_statsd_add_count(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 76 | static char *ngx_http_statsd_add_timing(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 77 | 78 | static ngx_str_t ngx_http_statsd_key_get_value(ngx_http_request_t *r, ngx_http_complex_value_t *cv, ngx_str_t v); 79 | static ngx_str_t ngx_http_statsd_key_value(ngx_str_t *str); 80 | static ngx_uint_t ngx_http_statsd_metric_get_value(ngx_http_request_t *r, ngx_http_complex_value_t *cv, ngx_uint_t v); 81 | static ngx_uint_t ngx_http_statsd_metric_value(ngx_str_t *str); 82 | static ngx_flag_t ngx_http_statsd_valid_get_value(ngx_http_request_t *r, ngx_http_complex_value_t *cv, ngx_flag_t v); 83 | static ngx_flag_t ngx_http_statsd_valid_value(ngx_str_t *str); 84 | 85 | uintptr_t ngx_escape_statsd_key(u_char *dst, u_char *src, size_t size); 86 | 87 | static ngx_int_t ngx_http_statsd_init(ngx_conf_t *cf); 88 | 89 | static ngx_command_t ngx_http_statsd_commands[] = { 90 | 91 | { ngx_string("statsd_server"), 92 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 93 | ngx_http_statsd_set_server, 94 | NGX_HTTP_LOC_CONF_OFFSET, 95 | 0, 96 | NULL }, 97 | 98 | { ngx_string("statsd_sample_rate"), 99 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 100 | ngx_conf_set_num_slot, 101 | NGX_HTTP_LOC_CONF_OFFSET, 102 | offsetof(ngx_http_statsd_conf_t, sample_rate), 103 | NULL }, 104 | 105 | { ngx_string("statsd_count"), 106 | NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE23, 107 | ngx_http_statsd_add_count, 108 | NGX_HTTP_LOC_CONF_OFFSET, 109 | 0, 110 | NULL }, 111 | 112 | { ngx_string("statsd_timing"), 113 | NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE23, 114 | ngx_http_statsd_add_timing, 115 | NGX_HTTP_LOC_CONF_OFFSET, 116 | 0, 117 | NULL }, 118 | 119 | ngx_null_command 120 | }; 121 | 122 | 123 | static ngx_http_module_t ngx_http_statsd_module_ctx = { 124 | NULL, /* preconfiguration */ 125 | ngx_http_statsd_init, /* postconfiguration */ 126 | 127 | ngx_http_statsd_create_main_conf, /* create main configuration */ 128 | NULL, /* init main configuration */ 129 | 130 | NULL, /* create server configuration */ 131 | NULL, /* merge server configuration */ 132 | 133 | ngx_http_statsd_create_loc_conf, /* create location configration */ 134 | ngx_http_statsd_merge_loc_conf /* merge location configration */ 135 | }; 136 | 137 | 138 | ngx_module_t ngx_http_statsd_module = { 139 | NGX_MODULE_V1, 140 | &ngx_http_statsd_module_ctx, /* module context */ 141 | ngx_http_statsd_commands, /* module directives */ 142 | NGX_HTTP_MODULE, /* module type */ 143 | NULL, /* init master */ 144 | NULL, /* init module */ 145 | NULL, /* init process */ 146 | NULL, /* init thread */ 147 | NULL, /* exit thread */ 148 | NULL, /* exit process */ 149 | NULL, /* exit master */ 150 | NGX_MODULE_V1_PADDING 151 | }; 152 | 153 | static ngx_str_t 154 | ngx_http_statsd_key_get_value(ngx_http_request_t *r, ngx_http_complex_value_t *cv, ngx_str_t v) 155 | { 156 | ngx_str_t val; 157 | if (cv == NULL) { 158 | return v; 159 | } 160 | 161 | if (ngx_http_complex_value(r, cv, &val) != NGX_OK) { 162 | return (ngx_str_t) ngx_null_string; 163 | }; 164 | 165 | return ngx_http_statsd_key_value(&val); 166 | }; 167 | 168 | static ngx_str_t 169 | ngx_http_statsd_key_value(ngx_str_t *value) 170 | { 171 | return *value; 172 | }; 173 | 174 | static ngx_uint_t 175 | ngx_http_statsd_metric_get_value(ngx_http_request_t *r, ngx_http_complex_value_t *cv, ngx_uint_t v) 176 | { 177 | ngx_str_t val; 178 | if (cv == NULL) { 179 | return v; 180 | } 181 | 182 | if (ngx_http_complex_value(r, cv, &val) != NGX_OK) { 183 | return 0; 184 | }; 185 | 186 | return ngx_http_statsd_metric_value(&val); 187 | }; 188 | 189 | static ngx_uint_t 190 | ngx_http_statsd_metric_value(ngx_str_t *value) 191 | { 192 | ngx_int_t n, m; 193 | 194 | if (value->len == 1 && value->data[0] == '-') { 195 | return (ngx_uint_t) -1; 196 | }; 197 | 198 | /* Hack to convert milliseconds to a number. */ 199 | if (value->len > 4 && value->data[value->len - 4] == '.') { 200 | n = ngx_atoi(value->data, value->len - 4); 201 | m = ngx_atoi(value->data + (value->len - 3), 3); 202 | return (ngx_uint_t) ((n * 1000) + m); 203 | 204 | } else { 205 | n = ngx_atoi(value->data, value->len); 206 | if (n > 0) { 207 | return (ngx_uint_t) n; 208 | }; 209 | }; 210 | 211 | return 0; 212 | }; 213 | 214 | static ngx_flag_t 215 | ngx_http_statsd_valid_get_value(ngx_http_request_t *r, ngx_http_complex_value_t *cv, ngx_flag_t v) 216 | { 217 | ngx_str_t val; 218 | if (cv == NULL) { 219 | return v; 220 | } 221 | 222 | if (ngx_http_complex_value(r, cv, &val) != NGX_OK) { 223 | return 0; 224 | }; 225 | 226 | return ngx_http_statsd_valid_value(&val); 227 | }; 228 | 229 | static ngx_flag_t 230 | ngx_http_statsd_valid_value(ngx_str_t *value) 231 | { 232 | return (ngx_flag_t) (value->len > 0 ? 1 : 0); 233 | }; 234 | 235 | ngx_int_t 236 | ngx_http_statsd_handler(ngx_http_request_t *r) 237 | { 238 | u_char line[STATSD_MAX_STR], *p; 239 | const char * metric_type; 240 | ngx_http_statsd_conf_t *ulcf; 241 | ngx_statsd_stat_t *stats; 242 | ngx_statsd_stat_t stat; 243 | ngx_uint_t c; 244 | ngx_uint_t n; 245 | ngx_str_t s; 246 | ngx_flag_t b; 247 | 248 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 249 | "http statsd handler"); 250 | 251 | ulcf = ngx_http_get_module_loc_conf(r, ngx_http_statsd_module); 252 | 253 | if (ulcf->off == 1 || ulcf->endpoint == NULL) { 254 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "statsd: handler off"); 255 | return NGX_OK; 256 | } 257 | 258 | // Use a random distribution to sample at sample rate. 259 | if (ulcf->sample_rate < 100 && (uint) (ngx_random() % 100) >= ulcf->sample_rate) { 260 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "statsd: skipping sample"); 261 | return NGX_OK; 262 | } 263 | 264 | stats = ulcf->stats->elts; 265 | for (c = 0; c < ulcf->stats->nelts; c++) { 266 | 267 | stat = stats[c]; 268 | s = ngx_http_statsd_key_get_value(r, stat.ckey, stat.key); 269 | ngx_escape_statsd_key(s.data, s.data, s.len); 270 | 271 | n = ngx_http_statsd_metric_get_value(r, stat.cmetric, stat.metric); 272 | b = ngx_http_statsd_valid_get_value(r, stat.cvalid, stat.valid); 273 | 274 | if (b == 0 || s.len == 0 || n <= 0) { 275 | // Do not log if not valid, key is invalid, or valud is lte 0. 276 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "statsd: no value to send"); 277 | continue; 278 | }; 279 | 280 | if (stat.type == STATSD_TYPE_COUNTER) { 281 | metric_type = "c"; 282 | } else if (stat.type == STATSD_TYPE_TIMING) { 283 | metric_type = "ms"; 284 | } else { 285 | metric_type = NULL; 286 | } 287 | 288 | if (metric_type) { 289 | if (ulcf->sample_rate < 100) { 290 | p = ngx_snprintf(line, STATSD_MAX_STR, "%V:%d|%s|@0.%02d", &s, n, metric_type, ulcf->sample_rate); 291 | } else { 292 | p = ngx_snprintf(line, STATSD_MAX_STR, "%V:%d|%s", &s, n, metric_type); 293 | } 294 | ngx_http_statsd_udp_send(ulcf->endpoint, line, p - line); 295 | } 296 | } 297 | 298 | return NGX_OK; 299 | } 300 | 301 | static ngx_int_t ngx_statsd_init_endpoint(ngx_conf_t *cf, ngx_udp_endpoint_t *endpoint) { 302 | ngx_pool_cleanup_t *cln; 303 | ngx_resolver_connection_t *rec; 304 | 305 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, 306 | "statsd: initting endpoint"); 307 | 308 | cln = ngx_pool_cleanup_add(cf->pool, 0); 309 | if(cln == NULL) { 310 | return NGX_ERROR; 311 | } 312 | 313 | cln->handler = ngx_statsd_updater_cleanup; 314 | cln->data = endpoint; 315 | 316 | rec = ngx_calloc(sizeof(ngx_resolver_connection_t), cf->log); 317 | if (rec == NULL) { 318 | return NGX_ERROR; 319 | } 320 | 321 | endpoint->udp_connection = rec; 322 | 323 | rec->sockaddr = endpoint->peer_addr.sockaddr; 324 | rec->socklen = endpoint->peer_addr.socklen; 325 | rec->server = endpoint->peer_addr.name; 326 | 327 | endpoint->log = &cf->cycle->new_log; 328 | 329 | return NGX_OK; 330 | } 331 | 332 | static void 333 | ngx_statsd_updater_cleanup(void *data) 334 | { 335 | ngx_udp_endpoint_t *e = data; 336 | 337 | ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, 338 | "cleanup statsd_updater"); 339 | 340 | if(e->udp_connection) { 341 | if(e->udp_connection->udp) { 342 | ngx_close_connection(e->udp_connection->udp); 343 | } 344 | 345 | ngx_free(e->udp_connection); 346 | } 347 | } 348 | 349 | static void ngx_http_statsd_udp_dummy_handler(ngx_event_t *ev) 350 | { 351 | } 352 | 353 | static ngx_int_t 354 | ngx_http_statsd_udp_send(ngx_udp_endpoint_t *l, u_char *buf, size_t len) 355 | { 356 | ssize_t n; 357 | ngx_resolver_connection_t *rec; 358 | 359 | rec = l->udp_connection; 360 | if (rec->udp == NULL) { 361 | 362 | rec->log = *l->log; 363 | rec->log.handler = NULL; 364 | rec->log.data = NULL; 365 | rec->log.action = "logging"; 366 | 367 | if(ngx_udp_connect(rec) != NGX_OK) { 368 | if(rec->udp != NULL) { 369 | ngx_free_connection(rec->udp); 370 | rec->udp = NULL; 371 | } 372 | 373 | return NGX_ERROR; 374 | } 375 | 376 | rec->udp->data = l; 377 | rec->udp->read->handler = ngx_http_statsd_udp_dummy_handler; 378 | rec->udp->read->resolver = 0; 379 | } 380 | 381 | n = ngx_send(rec->udp, buf, len); 382 | 383 | if (n == -1) { 384 | return NGX_ERROR; 385 | } 386 | 387 | if ((size_t) n != (size_t) len) { 388 | #if defined nginx_version && nginx_version >= 8032 389 | ngx_log_error(NGX_LOG_CRIT, &rec->log, 0, "send() incomplete"); 390 | #else 391 | ngx_log_error(NGX_LOG_CRIT, rec->log, 0, "send() incomplete"); 392 | #endif 393 | return NGX_ERROR; 394 | } 395 | 396 | return NGX_OK; 397 | } 398 | 399 | static void * 400 | ngx_http_statsd_create_main_conf(ngx_conf_t *cf) 401 | { 402 | ngx_http_statsd_main_conf_t *conf; 403 | 404 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_statsd_main_conf_t)); 405 | if (conf == NULL) { 406 | return NGX_CONF_ERROR; 407 | } 408 | 409 | return conf; 410 | } 411 | 412 | static void * 413 | ngx_http_statsd_create_loc_conf(ngx_conf_t *cf) 414 | { 415 | ngx_http_statsd_conf_t *conf; 416 | 417 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_statsd_conf_t)); 418 | if (conf == NULL) { 419 | return NGX_CONF_ERROR; 420 | } 421 | conf->endpoint = NGX_CONF_UNSET_PTR; 422 | conf->off = NGX_CONF_UNSET; 423 | conf->sample_rate = NGX_CONF_UNSET_UINT; 424 | conf->stats = NULL; 425 | 426 | return conf; 427 | } 428 | 429 | static char * 430 | ngx_http_statsd_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 431 | { 432 | ngx_http_statsd_conf_t *prev = parent; 433 | ngx_http_statsd_conf_t *conf = child; 434 | ngx_statsd_stat_t *stat; 435 | ngx_statsd_stat_t prev_stat; 436 | ngx_statsd_stat_t *prev_stats; 437 | ngx_uint_t i; 438 | ngx_uint_t sz; 439 | 440 | ngx_conf_merge_ptr_value(conf->endpoint, prev->endpoint, NULL); 441 | ngx_conf_merge_off_value(conf->off, prev->off, 1); 442 | ngx_conf_merge_uint_value(conf->sample_rate, prev->sample_rate, 100); 443 | 444 | if (conf->stats == NULL) { 445 | sz = (prev->stats != NULL ? prev->stats->nelts : 2); 446 | conf->stats = ngx_array_create(cf->pool, sz, sizeof(ngx_statsd_stat_t)); 447 | if (conf->stats == NULL) { 448 | return NGX_CONF_ERROR; 449 | } 450 | } 451 | if (prev->stats != NULL) { 452 | prev_stats = prev->stats->elts; 453 | for (i = 0; i < prev->stats->nelts; i++) { 454 | stat = ngx_array_push(conf->stats); 455 | ngx_memzero(stat, sizeof(ngx_statsd_stat_t)); 456 | 457 | prev_stat = prev_stats[i]; 458 | 459 | stat->type = prev_stat.type; 460 | stat->key = prev_stat.key; 461 | stat->metric = prev_stat.metric; 462 | stat->ckey = prev_stat.ckey; 463 | stat->cmetric = prev_stat.cmetric; 464 | stat->valid = prev_stat.valid; 465 | stat->cvalid = prev_stat.cvalid; 466 | }; 467 | }; 468 | 469 | return NGX_CONF_OK; 470 | } 471 | 472 | static ngx_udp_endpoint_t * 473 | ngx_http_statsd_add_endpoint(ngx_conf_t *cf, ngx_statsd_addr_t *peer_addr) 474 | { 475 | ngx_http_statsd_main_conf_t *umcf; 476 | ngx_udp_endpoint_t *endpoint; 477 | 478 | umcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_statsd_module); 479 | 480 | if(umcf->endpoints == NULL) { 481 | umcf->endpoints = ngx_array_create(cf->pool, 2, sizeof(ngx_udp_endpoint_t)); 482 | if (umcf->endpoints == NULL) { 483 | return NULL; 484 | } 485 | } 486 | 487 | endpoint = ngx_array_push(umcf->endpoints); 488 | if (endpoint == NULL) { 489 | return NULL; 490 | } 491 | 492 | endpoint->peer_addr = *peer_addr; 493 | 494 | return endpoint; 495 | } 496 | 497 | static char * 498 | ngx_http_statsd_set_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 499 | { 500 | ngx_http_statsd_conf_t *ulcf = conf; 501 | ngx_str_t *value; 502 | ngx_url_t u; 503 | 504 | value = cf->args->elts; 505 | 506 | if (ngx_strcmp(value[1].data, "off") == 0) { 507 | ulcf->off = 1; 508 | return NGX_CONF_OK; 509 | } 510 | ulcf->off = 0; 511 | 512 | ngx_memzero(&u, sizeof(ngx_url_t)); 513 | 514 | u.url = value[1]; 515 | u.default_port = STATSD_DEFAULT_PORT; 516 | u.no_resolve = 0; 517 | 518 | if(ngx_parse_url(cf->pool, &u) != NGX_OK) { 519 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V: %s", &u.host, u.err); 520 | return NGX_CONF_ERROR; 521 | } 522 | 523 | ulcf->endpoint = ngx_http_statsd_add_endpoint(cf, &u.addrs[0]); 524 | if(ulcf->endpoint == NULL) { 525 | return NGX_CONF_ERROR; 526 | } 527 | 528 | return NGX_CONF_OK; 529 | } 530 | 531 | static char * 532 | ngx_http_statsd_add_stat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf, ngx_uint_t type) { 533 | ngx_http_statsd_conf_t *ulcf = conf; 534 | ngx_http_complex_value_t key_cv; 535 | ngx_http_compile_complex_value_t key_ccv; 536 | ngx_http_complex_value_t metric_cv; 537 | ngx_http_compile_complex_value_t metric_ccv; 538 | ngx_http_complex_value_t valid_cv; 539 | ngx_http_compile_complex_value_t valid_ccv; 540 | ngx_str_t *value; 541 | ngx_statsd_stat_t *stat; 542 | ngx_int_t n; 543 | ngx_str_t s; 544 | ngx_flag_t b; 545 | 546 | value = cf->args->elts; 547 | 548 | if (ulcf->stats == NULL) { 549 | ulcf->stats = ngx_array_create(cf->pool, 10, sizeof(ngx_statsd_stat_t)); 550 | if (ulcf->stats == NULL) { 551 | return NGX_CONF_ERROR; 552 | } 553 | } 554 | 555 | stat = ngx_array_push(ulcf->stats); 556 | if (stat == NULL) { 557 | return NGX_CONF_ERROR; 558 | } 559 | 560 | ngx_memzero(stat, sizeof(ngx_statsd_stat_t)); 561 | 562 | stat->type = type; 563 | stat->valid = 1; 564 | 565 | ngx_memzero(&key_ccv, sizeof(ngx_http_compile_complex_value_t)); 566 | key_ccv.cf = cf; 567 | key_ccv.value = &value[1]; 568 | key_ccv.complex_value = &key_cv; 569 | 570 | if (ngx_http_compile_complex_value(&key_ccv) != NGX_OK) { 571 | return NGX_CONF_ERROR; 572 | } 573 | 574 | if (key_cv.lengths == NULL) { 575 | s = ngx_http_statsd_key_value(&value[1]); 576 | /*if (n < 0) { 577 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[2]); 578 | return NGX_CONF_ERROR; 579 | };*/ 580 | stat->key = (ngx_str_t) s; 581 | } else { 582 | stat->ckey = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); 583 | if (stat->ckey == NULL) { 584 | return NGX_CONF_ERROR; 585 | } 586 | *stat->ckey = key_cv; 587 | } 588 | 589 | ngx_memzero(&metric_ccv, sizeof(ngx_http_compile_complex_value_t)); 590 | metric_ccv.cf = cf; 591 | metric_ccv.value = &value[2]; 592 | metric_ccv.complex_value = &metric_cv; 593 | 594 | if (ngx_http_compile_complex_value(&metric_ccv) != NGX_OK) { 595 | return NGX_CONF_ERROR; 596 | } 597 | 598 | if (metric_cv.lengths == NULL) { 599 | n = ngx_http_statsd_metric_value(&value[2]); 600 | if (n < 0) { 601 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[2]); 602 | return NGX_CONF_ERROR; 603 | }; 604 | stat->metric = (ngx_uint_t) n; 605 | } else { 606 | stat->cmetric = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); 607 | if (stat->cmetric == NULL) { 608 | return NGX_CONF_ERROR; 609 | } 610 | *stat->cmetric = metric_cv; 611 | } 612 | 613 | if (cf->args->nelts > 3) { 614 | ngx_memzero(&valid_ccv, sizeof(ngx_http_compile_complex_value_t)); 615 | valid_ccv.cf = cf; 616 | valid_ccv.value = &value[3]; 617 | valid_ccv.complex_value = &valid_cv; 618 | 619 | if (ngx_http_compile_complex_value(&valid_ccv) != NGX_OK) { 620 | return NGX_CONF_ERROR; 621 | } 622 | 623 | if (valid_cv.lengths == NULL) { 624 | b = ngx_http_statsd_valid_value(&value[3]); 625 | if (b < 0) { 626 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[3]); 627 | return NGX_CONF_ERROR; 628 | }; 629 | stat->valid = (ngx_flag_t) b; 630 | } else { 631 | stat->cvalid = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); 632 | if (stat->cvalid == NULL) { 633 | return NGX_CONF_ERROR; 634 | } 635 | *stat->cvalid = valid_cv; 636 | } 637 | } 638 | 639 | return NGX_CONF_OK; 640 | } 641 | 642 | static char * 643 | ngx_http_statsd_add_count(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 644 | { 645 | return ngx_http_statsd_add_stat(cf, cmd, conf, STATSD_TYPE_COUNTER); 646 | } 647 | 648 | static char * 649 | ngx_http_statsd_add_timing(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 650 | { 651 | return ngx_http_statsd_add_stat(cf, cmd, conf, STATSD_TYPE_TIMING); 652 | } 653 | 654 | static ngx_int_t 655 | ngx_http_statsd_init(ngx_conf_t *cf) 656 | { 657 | ngx_int_t rc; 658 | ngx_uint_t i; 659 | ngx_http_core_main_conf_t *cmcf; 660 | ngx_http_statsd_main_conf_t *umcf; 661 | ngx_http_handler_pt *h; 662 | ngx_udp_endpoint_t *e; 663 | 664 | umcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_statsd_module); 665 | 666 | if(umcf->endpoints != NULL) { 667 | e = umcf->endpoints->elts; 668 | for(i = 0;i < umcf->endpoints->nelts;i++) { 669 | rc = ngx_statsd_init_endpoint(cf, e + i); 670 | 671 | if(rc != NGX_OK) { 672 | return NGX_ERROR; 673 | } 674 | } 675 | 676 | cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); 677 | 678 | h = ngx_array_push(&cmcf->phases[NGX_HTTP_LOG_PHASE].handlers); 679 | if (h == NULL) { 680 | return NGX_ERROR; 681 | } 682 | 683 | *h = ngx_http_statsd_handler; 684 | } 685 | 686 | return NGX_OK; 687 | } 688 | 689 | uintptr_t 690 | ngx_escape_statsd_key(u_char *dst, u_char *src, size_t size) 691 | { 692 | ngx_uint_t n; 693 | uint32_t *escape; 694 | 695 | /* " ", "#", """, "%", "'", %00-%1F, %7F-%FF */ 696 | 697 | static uint32_t statsd_key[] = { 698 | 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 699 | 700 | /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ 701 | 0xfc00bfff, /* 1111 1100 0000 0000 1011 1111 1111 1111 */ 702 | 703 | /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ 704 | 0x78000001, /* 0111 1000 0000 0000 0000 0000 0000 0001 */ 705 | 706 | /* ~}| {zyx wvut srqp onml kjih gfed cba` */ 707 | 0xf8000001, /* 1111 1000 0000 0000 0000 0000 0000 0001 */ 708 | 709 | 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 710 | 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 711 | 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 712 | 0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 713 | }; 714 | 715 | static uint32_t *map[] = 716 | { statsd_key }; 717 | 718 | 719 | escape = map[0]; 720 | 721 | if (dst == NULL) { 722 | 723 | /* find the number of the characters to be escaped */ 724 | 725 | n = 0; 726 | 727 | while (size) { 728 | if (escape[*src >> 5] & (1 << (*src & 0x1f))) { 729 | n++; 730 | } 731 | src++; 732 | size--; 733 | } 734 | 735 | return (uintptr_t) n; 736 | } 737 | 738 | while (size) { 739 | if (escape[*src >> 5] & (1 << (*src & 0x1f))) { 740 | *dst++ = '_'; 741 | src++; 742 | 743 | } else { 744 | *dst++ = *src++; 745 | } 746 | size--; 747 | } 748 | 749 | return (uintptr_t) dst; 750 | } 751 | --------------------------------------------------------------------------------