diff --git a/src/common/ngx_wa_metrics.c b/src/common/ngx_wa_metrics.c index fcb59fdee..e57da54f2 100644 --- a/src/common/ngx_wa_metrics.c +++ b/src/common/ngx_wa_metrics.c @@ -3,34 +3,60 @@ #endif #include "ddebug.h" +#include #include #include #include +typedef enum { + NGX_WA_HISTOGRAM_LOG2, + NGX_WA_HISTOGRAM_CUSTOM, +} ngx_wa_histogram_type_e; + + typedef struct { - ngx_uint_t value; - ngx_msec_t last_update; + ngx_uint_t value; + ngx_msec_t last_update; } ngx_wa_metrics_gauge_t; +typedef struct { + uint32_t upper_bound; /* could be uint16_t if it's ok to limit it at 65535 */ + uint32_t count; +} ngx_wa_histogram_bin_t; + + +typedef struct { + ngx_wa_histogram_type_e type; + uint8_t n_bins; + ngx_wa_histogram_bin_t bins[]; +} ngx_wa_histogram_t; + + typedef union { - ngx_uint_t counter; - ngx_wa_metrics_gauge_t gauge; + ngx_uint_t counter; + ngx_wa_metrics_gauge_t gauge; + ngx_wa_histogram_t *histogram; } ngx_wa_metric_val_t; typedef struct { - ngx_wa_metric_type_e type; - ngx_wa_metric_val_t slots[]; + ngx_wa_metric_type_e type; + ngx_wa_metric_val_t slots[]; } ngx_wa_metric_t; +static ngx_wa_histogram_bin_t *histogram_bin(ngx_wa_metrics_t *metrics, + ngx_wa_histogram_t *h, ngx_int_t n, ngx_wa_histogram_t **out); + + static ngx_str_t * -ngx_wa_metrics_type_name(ngx_wa_metric_type_e type) +metric_type_name(ngx_wa_metric_type_e type) { static ngx_str_t counter = ngx_string("counter"); static ngx_str_t gauge = ngx_string("gauge"); + static ngx_str_t histogram = ngx_string("histogram"); static ngx_str_t unknown = ngx_string("unknown"); switch (type) { @@ -40,6 +66,9 @@ ngx_wa_metrics_type_name(ngx_wa_metric_type_e type) case NGX_WA_METRIC_GAUGE: return &gauge; + case NGX_WA_METRIC_HISTOGRAM: + return &histogram; + default: return &unknown; } @@ -47,7 +76,253 @@ ngx_wa_metrics_type_name(ngx_wa_metric_type_e type) static ngx_int_t -ngx_wa_metric_realloc(ngx_wa_metrics_t *metrics, ngx_rbtree_node_t *node, +histogram_grow(ngx_wa_metrics_t *metrics, ngx_wa_histogram_t *h, + ngx_wa_histogram_t **out) +{ + size_t old_size, size; + ngx_int_t rc = NGX_OK; + ngx_uint_t n; + ngx_wa_histogram_t *new_h = NULL; + + if (h->n_bins == metrics->config.max_histogram_bins) { + return NGX_ERROR; /* histogram full */ + } + + n = ngx_min(5, metrics->config.max_histogram_bins - h->n_bins); + old_size = sizeof(ngx_wa_histogram_t) + + sizeof(ngx_wa_histogram_bin_t) * h->n_bins; + size = old_size + sizeof(ngx_wa_histogram_bin_t) * n; + + if (metrics->shm->eviction == NGX_WASM_SHM_EVICTION_NONE) { + ngx_wasm_shm_lock(metrics->shm); + } + + new_h = ngx_slab_calloc_locked(metrics->shm->shpool, size); + if (new_h == NULL) { + rc = NGX_ERROR; + goto error; + } + + ngx_memcpy(new_h, h, old_size); + ngx_slab_free_locked(metrics->shm->shpool, h); + + new_h->n_bins += n; + *out = new_h; + +error: + + if (metrics->shm->eviction == NGX_WASM_SHM_EVICTION_NONE) { + ngx_wasm_shm_unlock(metrics->shm); + } + + return rc; +} + + +static uint32_t +bin_log2_upper_bound(ngx_int_t n) +{ + uint32_t upper_bound = 2; + + if (n < 2) { + return 1; + } + + for (n = n - 1; n >>= 1; upper_bound <<= 1) { /* void */ } + + return upper_bound; +} + + +static ngx_wa_histogram_bin_t * +histogram_bin(ngx_wa_metrics_t *metrics, ngx_wa_histogram_t *h, ngx_int_t n, + ngx_wa_histogram_t **out) +{ + size_t i, j = 0; + uint32_t ub = bin_log2_upper_bound(n); + ngx_wa_histogram_bin_t *b; + + for (i = 0; i < h->n_bins; i++) { + b = &h->bins[i]; + j = (j == 0 && ub < b->upper_bound) ? i : j; + + if (b->upper_bound == ub) { + return b; + + } else if (b->upper_bound == 0) { + break; + } + } + + if (i == h->n_bins) { + if (out && histogram_grow(metrics, h, out) == NGX_OK) { + h = *out; + + } else { + return &h->bins[j]; /* can't grow, return next closest bin */ + } + } + + /* shift bins to create space for the new one */ + ngx_memcpy(&h->bins[j + 1], &h->bins[j], + sizeof(ngx_wa_histogram_bin_t) * (i - j)); + + h->bins[j].upper_bound = ub; + h->bins[j].count = 0; + + return &h->bins[j]; +} + + +static ngx_int_t +histogram_create_locked(ngx_wa_metrics_t *metrics, ngx_wa_metric_t *m) +{ + size_t i; + uint16_t n_bins = 5; + ngx_wa_histogram_t **h; + + for (i = 0; i < metrics->workers; i++) { + h = &m->slots[i].histogram; + *h = ngx_slab_calloc_locked(metrics->shm->shpool, + sizeof(ngx_wa_histogram_t) + + sizeof(ngx_wa_histogram_bin_t) + * n_bins); + if (*h == NULL) { + goto error; + } + + (*h)->type = NGX_WA_HISTOGRAM_LOG2; + (*h)->n_bins = n_bins; + (*h)->bins[0].upper_bound = NGX_MAX_UINT32_VALUE; + } + + return NGX_OK; + +error: + + for (/* void */ ; i > 0; i--) { + ngx_slab_free(metrics->shm->shpool, m->slots[i - 1].histogram); + } + + return NGX_ERROR; +} + + +static ngx_uint_t +counter_get(ngx_wa_metric_t *m, ngx_uint_t slots) +{ + ngx_uint_t i, val = 0; + + for (i = 0; i < slots; i++) { + val += m->slots[i].counter; + } + + return val; +} + + +static ngx_uint_t +gauge_get(ngx_wa_metric_t *m, ngx_uint_t slots) +{ + ngx_msec_t l; + ngx_uint_t i, val = 0; + + val = m->slots[0].gauge.value; + l = m->slots[0].gauge.last_update; + + for (i = 1; i < slots; i++) { + if (m->slots[i].gauge.last_update > l) { + val = m->slots[i].gauge.value; + l = m->slots[i].gauge.last_update; + } + } + + return val; +} + + +static void +histogram_get(ngx_wa_metrics_t *metrics, ngx_wa_metric_t *m, ngx_uint_t slots, + ngx_wa_histogram_t *out) +{ + size_t i, j = 0; + ngx_wa_histogram_t *h; + ngx_wa_histogram_bin_t *b, *out_b; + + for (i = 0; i < slots; i++) { + h = m->slots[i].histogram; + + for (j = 0; j < h->n_bins; j++) { + b = &h->bins[j]; + if (b->upper_bound == 0) { + break; + } + + out_b = histogram_bin(metrics, out, b->upper_bound, NULL); + out_b->count += b->count; + } + } +} + +#if (NGX_DEBUG) +static void +histogram_log(ngx_wa_metrics_t *metrics, ngx_wa_metric_t *m, uint32_t mid) +{ + size_t i, size = sizeof(ngx_wa_histogram_t) + + sizeof(ngx_wa_histogram_bin_t) + * metrics->config.max_histogram_bins; + ngx_wa_histogram_t *h; + ngx_wa_histogram_bin_t *b; + u_char *p, buf[size], s_buf[128]; + + ngx_memzero(buf, size); + + p = s_buf; + h = (ngx_wa_histogram_t *) buf; + h->n_bins = metrics->config.max_histogram_bins; + h->bins[0].upper_bound = NGX_MAX_UINT32_VALUE; + + histogram_get(metrics, m, metrics->workers, h); + + for (i = 0; i < h->n_bins; i++) { + b = &h->bins[i]; + if (b->upper_bound == 0) { + break; + } + + p = ngx_sprintf(p, " %uD: %uD;", b->upper_bound, b->count); + } + + ngx_log_debug3(NGX_LOG_DEBUG_WASM, metrics->shm->log, 0, + "histogram \"%uD\": %*s", mid, p - s_buf - 1, s_buf + 1); +} +#endif + + +static ngx_int_t +histogram_reallocate(ngx_wa_metrics_t *metrics, ngx_wa_metric_t *old_m, + uint32_t mid) +{ + uint32_t cas, slots = metrics->old_metrics->workers; + ngx_int_t rc; + ngx_str_t *val; + ngx_wa_metric_t *m; + + rc = ngx_wasm_shm_kv_get_locked(metrics->shm, NULL, &mid, &val, &cas); + if (rc != NGX_OK) { + return rc; + } + + m = (ngx_wa_metric_t *) val->data; + + histogram_get(metrics, old_m, slots, m->slots[0].histogram); + + return NGX_OK; +} + + +static ngx_int_t +metrics_reallocate(ngx_wa_metrics_t *metrics, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) { uint32_t mid; @@ -70,23 +345,21 @@ ngx_wa_metric_realloc(ngx_wa_metrics_t *metrics, ngx_rbtree_node_t *node, return NGX_ERROR; } - if (ngx_wa_metrics_get(metrics->old_metrics, mid, &val) != NGX_OK) { - ngx_wasm_log_error(NGX_LOG_ERR, metrics->shm->log, 0, - "failed retrieving metric \"%V\" from old metrics", - &n->key.str); - - return NGX_ERROR; - } - switch (m->type) { case NGX_WA_METRIC_COUNTER: + val = counter_get(m, metrics->old_metrics->workers); rc = ngx_wa_metrics_increment(metrics, mid, val); break; case NGX_WA_METRIC_GAUGE: + val = gauge_get(m, metrics->old_metrics->workers); rc = ngx_wa_metrics_record(metrics, mid, val); break; + case NGX_WA_METRIC_HISTOGRAM: + rc = histogram_reallocate(metrics, m, mid); + break; + default: ngx_wa_assert(0); return NGX_ERROR; @@ -99,13 +372,13 @@ ngx_wa_metric_realloc(ngx_wa_metrics_t *metrics, ngx_rbtree_node_t *node, } if (node->left - && ngx_wa_metric_realloc(metrics, node->left, sentinel) != NGX_OK) + && metrics_reallocate(metrics, node->left, sentinel) != NGX_OK) { return NGX_ERROR; } if (node->right - && ngx_wa_metric_realloc(metrics, node->right, sentinel) != NGX_OK) + && metrics_reallocate(metrics, node->right, sentinel) != NGX_OK) { return NGX_ERROR; } @@ -132,6 +405,7 @@ ngx_wa_metrics_create(ngx_cycle_t *cycle) metrics->old_metrics = ngx_wasmx_metrics(); metrics->config.slab_size = NGX_CONF_UNSET_SIZE; metrics->config.max_metric_name_length = NGX_CONF_UNSET_SIZE; + metrics->config.max_histogram_bins = NGX_CONF_UNSET_SIZE; metrics->shm = ngx_pcalloc(cycle->pool, sizeof(ngx_wasm_shm_t)); if (metrics->shm == NULL) { @@ -169,6 +443,11 @@ ngx_wa_metrics_init_conf(ngx_wa_metrics_t *metrics, ngx_conf_t *cf) metrics->config.max_metric_name_length = 64; } + if (metrics->config.max_histogram_bins == NGX_CONF_UNSET_SIZE) { + metrics->config.max_histogram_bins = 20; + } + + /* if eviction is enabled, metrics->workers must be set to 1 */ metrics->workers = ccf->worker_processes; metrics->shm_zone = ngx_shared_memory_add(cf, &metrics->shm->name, metrics->config.slab_size, @@ -179,7 +458,7 @@ ngx_wa_metrics_init_conf(ngx_wa_metrics_t *metrics, ngx_conf_t *cf) metrics->shm_zone->data = metrics->shm; metrics->shm_zone->init = ngx_wasm_shm_init_zone; - metrics->shm_zone->noreuse = false; /* reuse shm zone by default */ + metrics->shm_zone->noreuse = false; if (old_metrics && (metrics->workers != old_metrics->workers @@ -212,12 +491,11 @@ ngx_wa_metrics_init(ngx_wa_metrics_t *metrics, ngx_cycle_t *cycle) } if (metrics->old_metrics && metrics->shm_zone->noreuse) { - /* reallocate metrics due to new number of workers or shm slab resize */ old_shm_kv = ngx_wasm_shm_get_kv(metrics->old_metrics->shm); root = old_shm_kv->rbtree.root; sentinel = old_shm_kv->rbtree.sentinel; - return ngx_wa_metric_realloc(metrics, root, sentinel); + return metrics_reallocate(metrics, root, sentinel); } return NGX_OK; @@ -231,14 +509,17 @@ ngx_wa_metrics_add(ngx_wa_metrics_t *metrics, ngx_str_t *name, uint32_t cas, mid; ngx_int_t rc, written; ngx_str_t *p, val; - ngx_wa_metric_t *metric; + ngx_wa_metric_t *m; ssize_t size = sizeof(ngx_wa_metric_t) + sizeof(ngx_wa_metric_val_t) * metrics->workers; u_char buf[size]; mid = ngx_crc32_long(name->data, name->len); - if (type != NGX_WA_METRIC_COUNTER && type != NGX_WA_METRIC_GAUGE) { + if (type != NGX_WA_METRIC_COUNTER + && type != NGX_WA_METRIC_GAUGE + && type != NGX_WA_METRIC_HISTOGRAM) + { dd("invalid metric type"); return NGX_ERROR; } @@ -255,9 +536,19 @@ ngx_wa_metrics_add(ngx_wa_metrics_t *metrics, ngx_str_t *name, goto done; } + dd("adding new metric"); + ngx_memzero(buf, size); - metric = (ngx_wa_metric_t *) buf; - metric->type = type; + m = (ngx_wa_metric_t *) buf; + m->type = type; + + if (type == NGX_WA_METRIC_HISTOGRAM) { + rc = histogram_create_locked(metrics, m); + if (rc != NGX_OK) { + dd("failed creating histogram"); + goto error; + } + } val.len = size; val.data = buf; @@ -280,7 +571,7 @@ ngx_wa_metrics_add(ngx_wa_metrics_t *metrics, ngx_str_t *name, if (rc == NGX_OK) { ngx_wasm_log_error(NGX_LOG_INFO, metrics->shm->log, 0, "defined %V \"%V\" with id %uD", - ngx_wa_metrics_type_name(type), name, mid); + metric_type_name(type), name, mid); } return rc; @@ -288,49 +579,27 @@ ngx_wa_metrics_add(ngx_wa_metrics_t *metrics, ngx_str_t *name, ngx_int_t -ngx_wa_metrics_get(ngx_wa_metrics_t *metrics, uint32_t metric_id, - ngx_uint_t *out) +ngx_wa_metrics_get(ngx_wa_metrics_t *metrics, uint32_t mid, ngx_uint_t *out) { uint32_t cas; ngx_int_t rc; ngx_str_t *n; - ngx_msec_t l; - ngx_uint_t i, val, slots; - ngx_wa_metric_t *metric; + ngx_wa_metric_t *m; - rc = ngx_wasm_shm_kv_get_locked(metrics->shm, NULL, &metric_id, &n, &cas); + rc = ngx_wasm_shm_kv_get_locked(metrics->shm, NULL, &mid, &n, &cas); if (rc != NGX_OK) { return rc; } - val = 0; - slots = metrics->workers; - -#if 0 - if (metrics->shm->eviction != NGX_WASM_EVICTION_NONE) { - slots = 1; - } -#endif - - metric = (ngx_wa_metric_t *) n->data; + m = (ngx_wa_metric_t *) n->data; - switch (metric->type) { + switch (m->type) { case NGX_WA_METRIC_COUNTER: - for (i = 0; i < slots; i++) { - val += metric->slots[i].counter; - } + *out = counter_get(m, metrics->workers); break; case NGX_WA_METRIC_GAUGE: - val = metric->slots[0].gauge.value; - l = metric->slots[0].gauge.last_update; - - for (i = 1; i < slots; i++) { - if (metric->slots[i].gauge.last_update > l) { - val = metric->slots[i].gauge.value; - l = metric->slots[i].gauge.last_update; - } - } + *out = gauge_get(m, metrics->workers); break; default: @@ -339,23 +608,20 @@ ngx_wa_metrics_get(ngx_wa_metrics_t *metrics, uint32_t metric_id, } ngx_log_debug2(NGX_LOG_DEBUG_WASM, metrics->shm->log, 0, - "wasm retrieving metric \"%z\" as %d", metric_id, val); - - *out = val; + "wasm retrieving metric \"%z\" as %d", mid, *out); return NGX_OK; } ngx_int_t -ngx_wa_metrics_increment(ngx_wa_metrics_t *metrics, uint32_t metric_id, - ngx_int_t n) +ngx_wa_metrics_increment(ngx_wa_metrics_t *metrics, uint32_t mid, ngx_int_t n) { uint32_t cas; ngx_int_t rc = NGX_OK; ngx_str_t *val; ngx_uint_t slot; - ngx_wa_metric_t *metric; + ngx_wa_metric_t *m; slot = (ngx_process == NGX_PROCESS_WORKER) ? ngx_worker : 0; @@ -366,28 +632,28 @@ ngx_wa_metrics_increment(ngx_wa_metrics_t *metrics, uint32_t metric_id, } #endif - rc = ngx_wasm_shm_kv_get_locked(metrics->shm, NULL, &metric_id, &val, &cas); + rc = ngx_wasm_shm_kv_get_locked(metrics->shm, NULL, &mid, &val, &cas); if (rc != NGX_OK) { ngx_wasm_log_error(NGX_LOG_ERR, metrics->shm->log, 0, - "metric \"%uD\" not found", metric_id); + "metric \"%uD\" not found", mid); goto error; } - metric = (ngx_wa_metric_t *) val->data; + m = (ngx_wa_metric_t *) val->data; - if (metric->type != NGX_WA_METRIC_COUNTER) { + if (m->type != NGX_WA_METRIC_COUNTER) { ngx_wasm_log_error(NGX_LOG_ERR, metrics->shm->log, 0, "attempt to call increment_metric on a %V; " "operation not supported", - ngx_wa_metrics_type_name(metric->type)); + metric_type_name(m->type)); rc = NGX_ERROR; goto error; } ngx_log_debug2(NGX_LOG_DEBUG_WASM, metrics->shm->log, 0, - "wasm updating metric \"%uD\" with %d", metric_id, n); + "wasm updating metric \"%uD\" with %d", mid, n); - metric->slots[slot].counter += n; + m->slots[slot].counter += n; error: @@ -402,14 +668,14 @@ ngx_wa_metrics_increment(ngx_wa_metrics_t *metrics, uint32_t metric_id, ngx_int_t -ngx_wa_metrics_record(ngx_wa_metrics_t *metrics, uint32_t metric_id, - ngx_int_t n) +ngx_wa_metrics_record(ngx_wa_metrics_t *metrics, uint32_t mid, ngx_int_t n) { - uint32_t cas; - ngx_int_t rc = NGX_OK; - ngx_str_t *val; - ngx_uint_t slot; - ngx_wa_metric_t *metric; + uint32_t cas; + ngx_int_t rc = NGX_OK; + ngx_str_t *val; + ngx_uint_t slot; + ngx_wa_metric_t *m; + ngx_wa_histogram_bin_t *bin; slot = (ngx_process == NGX_PROCESS_WORKER) ? ngx_worker : 0; @@ -420,29 +686,41 @@ ngx_wa_metrics_record(ngx_wa_metrics_t *metrics, uint32_t metric_id, } #endif - rc = ngx_wasm_shm_kv_get_locked(metrics->shm, NULL, &metric_id, &val, &cas); + rc = ngx_wasm_shm_kv_get_locked(metrics->shm, NULL, &mid, &val, &cas); if (rc != NGX_OK) { ngx_wasm_log_error(NGX_LOG_ERR, metrics->shm->log, 0, - "metric \"%uD\" not found", metric_id); + "metric \"%uD\" not found", mid); goto error; } - metric = (ngx_wa_metric_t *) val->data; + m = (ngx_wa_metric_t *) val->data; - if (metric->type == NGX_WA_METRIC_COUNTER) { + switch (m->type) { + case NGX_WA_METRIC_GAUGE: + m->slots[slot].gauge.value = n; + m->slots[slot].gauge.last_update = ngx_current_msec; + + break; + + case NGX_WA_METRIC_HISTOGRAM: + bin = histogram_bin(metrics, m->slots[slot].histogram, n, + &m->slots[slot].histogram); + bin->count += 1; +#if (NGX_DEBUG) + histogram_log(metrics, m, mid); +#endif + break; + + default: ngx_wasm_log_error(NGX_LOG_ERR, metrics->shm->log, 0, "attempt to call record_metric on a counter; " "operation not supported"); rc = NGX_ERROR; goto error; - } - - ngx_log_debug2(NGX_LOG_DEBUG_WASM, metrics->shm->log, 0, - "wasm recording metric \"%z\" with %d", metric_id, n); - metric->slots[slot].gauge.value = n; - metric->slots[slot].gauge.last_update = ngx_current_msec; + break; + } error: diff --git a/src/common/ngx_wa_metrics.h b/src/common/ngx_wa_metrics.h index ff6923fdf..65ca87c0d 100644 --- a/src/common/ngx_wa_metrics.h +++ b/src/common/ngx_wa_metrics.h @@ -13,12 +13,14 @@ typedef struct ngx_wa_metrics_s ngx_wa_metrics_t; typedef enum { NGX_WA_METRIC_COUNTER, NGX_WA_METRIC_GAUGE, + NGX_WA_METRIC_HISTOGRAM, } ngx_wa_metric_type_e; typedef struct { - size_t slab_size; - size_t max_metric_name_length; + size_t slab_size; + size_t max_metric_name_length; + size_t max_histogram_bins; } ngx_wa_metrics_conf_t; diff --git a/src/common/proxy_wasm/ngx_proxy_wasm_host.c b/src/common/proxy_wasm/ngx_proxy_wasm_host.c index a2c2ad23d..3faf92c09 100644 --- a/src/common/proxy_wasm/ngx_proxy_wasm_host.c +++ b/src/common/proxy_wasm/ngx_proxy_wasm_host.c @@ -1608,6 +1608,10 @@ ngx_proxy_wasm_hfuncs_define_metric(ngx_wavm_instance_t *instance, type = NGX_WA_METRIC_GAUGE; break; + case NGX_PROXY_WASM_METRIC_HISTOGRAM: + type = NGX_WA_METRIC_HISTOGRAM; + break; + default: return ngx_proxy_wasm_result_trap(pwexec, "unknown metric type", rets, diff --git a/t/03-proxy_wasm/hfuncs/metrics/400-record_histogram.t b/t/03-proxy_wasm/hfuncs/metrics/400-record_histogram.t new file mode 100644 index 000000000..6c4eddf19 --- /dev/null +++ b/t/03-proxy_wasm/hfuncs/metrics/400-record_histogram.t @@ -0,0 +1,193 @@ +# vim:set ft= ts=4 sts=4 sw=4 et fdm=marker: + +use strict; +use lib '.'; +use t::TestWasmX; + +skip_hup(); + +our $workers = 2; + +workers($workers); +if ($workers > 1) { + master_on(); +} + +plan_tests(6); +run_tests(); + +__DATA__ + +=== TEST 1: proxy_wasm metrics shm - record_metric, histogram - sanity +--- skip_no_debug +--- valgrind +--- load_nginx_modules: ngx_http_echo_module +--- wasm_modules: hostcalls +--- config + location /t { + proxy_wasm hostcalls 'on_configure=define_metrics \ + test=/t/metrics/record_histograms \ + metrics=h1 \ + value=1'; + proxy_wasm hostcalls 'on_configure=define_metrics \ + test=/t/metrics/record_histograms \ + metrics=h1 \ + value=2'; + proxy_wasm hostcalls 'on_configure=define_metrics \ + test=/t/metrics/record_histograms \ + metrics=h1 \ + value=3'; + + proxy_wasm hostcalls 'on_configure=define_metrics \ + test=/t/metrics/record_histograms \ + metrics=h1 \ + value=10'; + proxy_wasm hostcalls 'on_configure=define_metrics \ + test=/t/metrics/record_histograms \ + metrics=h1 \ + value=20'; + proxy_wasm hostcalls 'on_configure=define_metrics \ + test=/t/metrics/record_histograms \ + metrics=h1 \ + value=30'; + echo ok; + } +--- grep_error_log eval: qr/histogram "\d+":( \d+: \d+;)+/ +--- grep_error_log_out eval +qr/histogram "\d+": 1: 1; 2: 1; 4: 1; 16: 1; 32: 2; 4294967295: 0;/ +--- no_error_log +[error] +[crit] +[emerg] +[alert] + + + +=== TEST 2: proxy_wasm metrics shm - record_metric, histogram - on_configure +--- skip_no_debug +--- workers: 2 +--- valgrind +--- load_nginx_modules: ngx_http_echo_module +--- wasm_modules: hostcalls +--- config + location /t { + proxy_wasm hostcalls 'on_configure=define_and_record_histograms \ + test=/t/metrics/record_histograms \ + metrics=h1 \ + value=10'; + echo ok; + } +--- grep_error_log eval: qr/histogram "\d+":( \d+: \d+;)+/ +--- grep_error_log_out eval +qr/histogram "\d+": 16: 3; 4294967295: 0;/ +--- no_error_log +[error] +[crit] +[emerg] +[alert] + + + +=== TEST 3: proxy_wasm metrics - record_metric(), histogram - on_tick +--- skip_no_debug +--- wasm_modules: hostcalls +--- load_nginx_modules: ngx_http_echo_module +--- config eval +my $filters; + +foreach my $wid (0 .. $::workers - 1) { + $filters .= " + proxy_wasm hostcalls 'on_configure=define_metrics \ + on_tick=record_histograms \ + tick_period=100 \ + n_sync_calls=1 \ + on_worker=$wid \ + value=$wid \ + metrics=h2';"; +} +qq{ + location /t { + $filters + + echo ok; + } +} +--- grep_error_log eval: qr/histogram "\d+":( \d+: \d+;)+/ +--- grep_error_log_out eval +qr/histogram "\d+": 1: 2; 4294967295: 0;/ +--- no_error_log +[error] +[crit] +[emerg] +[alert] + + + +=== TEST 4: proxy_wasm metrics - record_metric(), histogram - on: request_headers, request_body, response_headers, response_body +--- skip_no_debug +--- load_nginx_modules: ngx_http_echo_module +--- wasm_modules: hostcalls +--- config eval +my $phases = CORE::join(',', qw( + request_headers + request_body + response_headers + response_body +)); + +qq{ + location /t { + proxy_wasm hostcalls 'on_configure=define_metrics \ + on=$phases \ + test=/t/metrics/record_histograms \ + value=100 \ + metrics=h1'; + echo ok; + } +} +--- request +POST /t +hello +--- grep_error_log eval: qr/histogram "\d+":( \d+: \d+;)+/ +--- grep_error_log_out eval +qr/histogram "\d+": 128: 4; 4294967295: 0;/ +--- no_error_log +[error] +[crit] +[emerg] +[alert] + + + +=== TEST 5: proxy_wasm metrics - record_metric(), histogram - on_http_call_response +--- skip_no_debug +--- wasm_modules: hostcalls +--- load_nginx_modules: ngx_http_echo_module +--- http_config eval +--- config eval +qq{ + listen unix:$ENV{TEST_NGINX_UNIX_SOCKET}; + + location /dispatched { + return 200 "Hello back"; + } + + location /t { + proxy_wasm hostcalls 'on_configure=define_metrics \ + test=/t/dispatch_http_call \ + host=unix:$ENV{TEST_NGINX_UNIX_SOCKET} \ + path=/dispatched \ + on_http_call_response=record_histograms \ + value=1000 \ + metrics=h2'; + echo ok; + } +} +--- grep_error_log eval: qr/histogram "\d+":( \d+: \d+;)+/ +--- grep_error_log_out eval +qr/histogram "\d+": 1024: 1; 4294967295: 0;/ +--- no_error_log +[error] +[crit] +[emerg] +[alert] diff --git a/t/07-metrics/002-histograms_sighup.t b/t/07-metrics/002-histograms_sighup.t new file mode 100644 index 000000000..ed6d77c22 --- /dev/null +++ b/t/07-metrics/002-histograms_sighup.t @@ -0,0 +1,186 @@ +# vim:set ft= ts=4 sts=4 sw=4 et fdm=marker: +use strict; +use lib '.'; +use t::TestWasmX; + +skip_no_hup(); + +our $workers = 2; +our $total = 0; + +workers($workers); +if ($workers > 1) { + master_on(); +} + +no_shuffle(); +plan_tests(6); +run_tests(); + +__DATA__ + +=== TEST 1: SIGHUP metrics - define and record histograms +--- valgrind +--- load_nginx_modules: ngx_http_echo_module +--- wasm_modules: hostcalls +--- config eval +qq{ + location /t { + proxy_wasm hostcalls 'on_configure=define_and_record_histograms \ + metrics=h2'; + echo ok; + } +} +--- grep_error_log eval: qr/histogram "\d+":( \d+: \d+;)+/ +--- grep_error_log_out eval +$::total += $::workers; +qr/histogram "\d+": 1: $::total; 4294967295: 0;/ +--- no_error_log +[error] +[crit] +[emerg] +[alert] + + + +=== TEST 2: SIGHUP metrics - shm preserved, no realloc +--- valgrind +--- load_nginx_modules: ngx_http_echo_module +--- wasm_modules: hostcalls +--- config eval +qq{ + location /t { + proxy_wasm hostcalls 'on_configure=define_and_record_histograms \ + metrics=h2'; + echo ok; + } +} +--- grep_error_log eval: qr/histogram "\d+":( \d+: \d+;)+/ +--- grep_error_log_out eval +$::total += $::workers; +qr/histogram "\d+": 1: $::total; 4294967295: 0;/ +--- no_error_log +[error] +[crit] +[emerg] +[alert] + + + +=== TEST 3: SIGHUP metrics - increased worker_processes - shm preserved, realloc +--- workers: 4 +--- valgrind +--- load_nginx_modules: ngx_http_echo_module +--- wasm_modules: hostcalls +--- config eval +qq{ + location /t { + proxy_wasm hostcalls 'on_configure=define_and_record_histograms \ + metrics=h2'; + echo ok; + } +} +--- grep_error_log eval: qr/histogram "\d+":( \d+: \d+;)+/ +--- grep_error_log_out eval +$::total += 4; +qr/histogram "\d+": 1: $::total; 4294967295: 0;/ +--- no_error_log +[error] +[crit] +[emerg] +[alert] + + + +=== TEST 4: SIGHUP metrics - decreased worker_processes - shm preserved, realloc +--- workers: 2 +--- valgrind +--- load_nginx_modules: ngx_http_echo_module +--- wasm_modules: hostcalls +--- config eval +qq{ + location /t { + proxy_wasm hostcalls 'on_configure=define_and_record_histograms \ + metrics=h2'; + echo ok; + } +} +--- grep_error_log eval: qr/histogram "\d+":( \d+: \d+;)+/ +--- grep_error_log_out eval +$::total += 2; +qr/histogram "\d+": 1: $::total; 4294967295: 0;/ +--- no_error_log +[error] +[crit] +[emerg] +[alert] + + + +=== TEST 5: SIGHUP metrics - decreased slab_size - shm preserved, realloc +--- valgrind +--- load_nginx_modules: ngx_http_echo_module +--- main_config eval +qq{ + wasm { + module hostcalls $ENV{TEST_NGINX_CRATES_DIR}/hostcalls.wasm; + + metrics { + slab_size 120k; + } + } +} +--- config eval +qq{ + location /t { + proxy_wasm hostcalls 'on_configure=define_metrics \ + on=request_headers \ + test=/t/metrics/record_histograms \ + metrics=h2'; + echo ok; + } +} +--- grep_error_log eval: qr/histogram "\d+":( \d+: \d+;)+/ +--- grep_error_log_out eval +$::total += 1; +qr/histogram "\d+": 1: $::total; 4294967295: 0;/ +--- no_error_log +[error] +[crit] +[emerg] +[alert] + + + +=== TEST 6: SIGHUP metrics - increased slab_size - shm preserved, realloc +--- valgrind +--- load_nginx_modules: ngx_http_echo_module +--- main_config eval +qq{ + wasm { + module hostcalls $ENV{TEST_NGINX_CRATES_DIR}/hostcalls.wasm; + + metrics { + slab_size 16k; + } + } +} +--- config eval +qq{ + location /t { + proxy_wasm hostcalls 'on_configure=define_metrics \ + on=response_headers \ + test=/t/metrics/record_histograms \ + metrics=h2'; + echo ok; + } +} +--- grep_error_log eval: qr/histogram "\d+":( \d+: \d+;)+/ +--- grep_error_log_out eval +$::total += 1; +qr/histogram "\d+": 1: $::total; 4294967295: 0;/ +--- no_error_log +[error] +[crit] +[emerg] +[alert] diff --git a/t/lib/proxy-wasm-tests/hostcalls/src/filter.rs b/t/lib/proxy-wasm-tests/hostcalls/src/filter.rs index 380b3876e..7eda5d6fc 100644 --- a/t/lib/proxy-wasm-tests/hostcalls/src/filter.rs +++ b/t/lib/proxy-wasm-tests/hostcalls/src/filter.rs @@ -105,6 +105,7 @@ impl Context for TestHttp { "define_metrics" => test_define_metrics(self, TestPhase::HTTPCallResponse), "increment_counters" => test_increment_counters(self, TestPhase::HTTPCallResponse), "toggle_gauges" => test_toggle_gauges(self, TestPhase::HTTPCallResponse), + "record_histograms" => test_record_histograms(self, TestPhase::HTTPCallResponse), _ => {} } diff --git a/t/lib/proxy-wasm-tests/hostcalls/src/lib.rs b/t/lib/proxy-wasm-tests/hostcalls/src/lib.rs index 147b0ab22..d226fe2d0 100644 --- a/t/lib/proxy-wasm-tests/hostcalls/src/lib.rs +++ b/t/lib/proxy-wasm-tests/hostcalls/src/lib.rs @@ -76,6 +76,10 @@ impl RootContext for TestRoot { test_define_metrics(self, TestPhase::Configure); test_toggle_gauges(self, TestPhase::Configure); } + "define_and_record_histograms" => { + test_define_metrics(self, TestPhase::Configure); + test_record_histograms(self, TestPhase::Configure); + } _ => (), } @@ -113,6 +117,10 @@ impl RootContext for TestRoot { test_set_gauges(self, TestPhase::Tick); self.n_sync_calls += 1; } + "record_histograms" => { + test_record_histograms(self, TestPhase::Tick); + self.n_sync_calls += 1; + } "log_metrics" => test_log_metrics(self, TestPhase::Tick), "set_property" => test_set_property(self), "dispatch" => { diff --git a/t/lib/proxy-wasm-tests/hostcalls/src/tests/mod.rs b/t/lib/proxy-wasm-tests/hostcalls/src/tests/mod.rs index a2151d571..0c27cb9b4 100644 --- a/t/lib/proxy-wasm-tests/hostcalls/src/tests/mod.rs +++ b/t/lib/proxy-wasm-tests/hostcalls/src/tests/mod.rs @@ -456,6 +456,7 @@ pub(crate) fn test_define_metrics(ctx: &mut (dyn TestContext + 'static), phase: let metric_type = match metric_char { 'c' => MetricType::Counter, 'g' => MetricType::Gauge, + 'h' => MetricType::Histogram, _ => panic!("unexpected metric type"), }; let n = metric[1..].parse::().expect("bad metrics value"); @@ -535,6 +536,21 @@ pub(crate) fn test_set_gauges(ctx: &(dyn TestContext + 'static), phase: TestPhas test_log_metrics(ctx, phase); } +pub(crate) fn test_record_histograms(ctx: &(dyn TestContext + 'static), phase: TestPhase) { + if !should_run_on_current_worker(ctx) { + return; + } + + let value = ctx + .get_config("value") + .map_or(1, |x| x.parse::().expect("bad value")); + + for (n, id) in ctx.get_metrics_mapping() { + record_metric(*id, value).unwrap(); + info!("record {} on {} at {:?}", value, n, phase); + } +} + pub(crate) fn test_get_metrics(ctx: &TestHttp) { for (n, id) in ctx.get_metrics_mapping() { let name = n.replace("_", "-"); diff --git a/t/lib/proxy-wasm-tests/hostcalls/src/types/test_http.rs b/t/lib/proxy-wasm-tests/hostcalls/src/types/test_http.rs index 00bf96f4c..b579f6b6b 100644 --- a/t/lib/proxy-wasm-tests/hostcalls/src/types/test_http.rs +++ b/t/lib/proxy-wasm-tests/hostcalls/src/types/test_http.rs @@ -121,6 +121,7 @@ impl TestHttp { "/t/metrics/define" => test_define_metrics(self, cur_phase), "/t/metrics/increment_counters" => test_increment_counters(self, cur_phase), "/t/metrics/toggle_gauges" => test_toggle_gauges(self, cur_phase), + "/t/metrics/record_histograms" => test_record_histograms(self, cur_phase), "/t/metrics/get" => test_get_metrics(self), "/t/metrics/increment_invalid_counter" => increment_metric(0, 1).unwrap(), "/t/metrics/set_invalid_gauge" => record_metric(0, 1).unwrap(),