Harden scrape rendering and add AddressSanitizer test suite
Move all heap allocation out of the slab-mutex critical section in render_prom/render_json: snapshot cardinality under a brief lock, allocate aggs/snaps/string tables outside the lock, then re-acquire only to deep-copy strings and walk the LRU into the pre-allocated buffers. A worker crash during output buffer allocation can no longer leave the shared-memory zone locked, and a corrupt cardinality count is caught by a 10k sanity cap rather than causing a runaway ngx_pcalloc. Add build-asan and tests/02-asan/: a full sanitizer-instrumented nginx + module built via apt-source, and a 2-node containerlab Robot suite that drives reload storms, concurrent scrape-during-reload, and intern-table growth, failing if AddressSanitizer or UBSan reports anything on stderr. The two Robot suites now check for their required build artifacts up front so `make robot-test` no longer rebuilds them on every invocation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2212,6 +2212,33 @@ ngx_http_ipng_stats_append(ngx_chain_t ***last, ngx_chain_t *cl)
|
||||
}
|
||||
|
||||
|
||||
/* Per-node snapshot used by the Prometheus and JSON renderers. The
|
||||
* renderers drain the shared-zone LRU into an array of these under
|
||||
* the slab mutex, and then release the mutex before doing any output
|
||||
* buffer allocation. That keeps ngx_pcalloc / malloc off the locked
|
||||
* path — a worker crash during rendering can never leave the slab
|
||||
* mutex held (NFR-4.3), and slab-lock contention no longer blocks on
|
||||
* glibc's allocator. */
|
||||
typedef struct {
|
||||
ngx_uint_t source_id;
|
||||
ngx_uint_t vip_id;
|
||||
ngx_uint_t class;
|
||||
uint64_t requests;
|
||||
uint64_t bytes_in;
|
||||
uint64_t bytes_out;
|
||||
uint64_t duration_sum_ms;
|
||||
uint64_t upstream_sum_ms;
|
||||
} ngx_http_ipng_stats_snap_t;
|
||||
|
||||
|
||||
/* Sanity cap on shared-zone cardinality, above which the renderer
|
||||
* refuses to snapshot rather than try to allocate a potentially
|
||||
* runaway buffer. This is a belt-and-suspenders guard against a
|
||||
* corrupt `sh->sources.nelts` or `sh->vips.nelts` — see design.md
|
||||
* on reload hardening. */
|
||||
#define NGX_HTTP_IPNG_STATS_MAX_INTERN 10000
|
||||
|
||||
|
||||
/* Find or create an aggregation entry for (source_id, vip_id). Linear
|
||||
* scan — the table is small in practice (< 100 entries). */
|
||||
static ngx_http_ipng_stats_agg_t *
|
||||
@@ -2232,91 +2259,45 @@ ngx_http_ipng_stats_agg_get(ngx_http_ipng_stats_agg_t *aggs, ngx_uint_t *naggs,
|
||||
}
|
||||
|
||||
|
||||
/* Walk matching nodes, emit per-node counters via `emit_counters`,
|
||||
* accumulate histograms into `aggs`. Caller holds slab mutex.
|
||||
* `ctx` is opaque — passed through to the emit callback. Returns number
|
||||
* of distinct (source, vip) pairs observed, or -1 on error. */
|
||||
typedef ngx_int_t (*ngx_http_ipng_stats_counters_pt)(ngx_http_request_t *r,
|
||||
ngx_http_ipng_stats_shctx_t *sh, ngx_http_ipng_stats_node_t *n,
|
||||
ngx_chain_t ***last, ngx_uint_t *emitted);
|
||||
|
||||
/* Deep-copy the sources and vips interning tables into r->pool so the
|
||||
* renderers can dereference strings without holding the slab mutex.
|
||||
* Caller holds the slab mutex. Writes the number of valid entries
|
||||
* (possibly smaller than the allocated length) via *n_src_out / *n_vip_out. */
|
||||
static ngx_int_t
|
||||
ngx_http_ipng_stats_walk_aggregate(ngx_http_request_t *r,
|
||||
ngx_http_ipng_stats_main_conf_t *imcf,
|
||||
ngx_str_t *filter_src, ngx_str_t *filter_vip,
|
||||
ngx_http_ipng_stats_counters_pt emit_counters,
|
||||
ngx_chain_t ***last, ngx_uint_t *emitted,
|
||||
ngx_http_ipng_stats_agg_t *aggs, ngx_uint_t naggs_alloc,
|
||||
ngx_uint_t *naggs_out)
|
||||
ngx_http_ipng_stats_snapshot_strings(ngx_http_request_t *r,
|
||||
ngx_http_ipng_stats_shctx_t *sh,
|
||||
ngx_str_t *src_tbl, ngx_uint_t n_src_alloc, ngx_uint_t *n_src_out,
|
||||
ngx_str_t *vip_tbl, ngx_uint_t n_vip_alloc, ngx_uint_t *n_vip_out)
|
||||
{
|
||||
ngx_http_ipng_stats_shctx_t *sh = imcf->shm_zone->data;
|
||||
ngx_queue_t *q;
|
||||
ngx_http_ipng_stats_node_t *n;
|
||||
ngx_str_t *src_entry, *vip_entry;
|
||||
ngx_atomic_uint_t *lanes, *blanes;
|
||||
ngx_http_ipng_stats_agg_t *a;
|
||||
ngx_uint_t i;
|
||||
ngx_uint_t nb = imcf->nbuckets;
|
||||
ngx_uint_t nbb = imcf->nbytebuckets;
|
||||
ngx_uint_t naggs = 0;
|
||||
ngx_int_t rc;
|
||||
ngx_uint_t i, n;
|
||||
u_char *d;
|
||||
|
||||
for (q = ngx_queue_head(&sh->lru);
|
||||
q != ngx_queue_sentinel(&sh->lru);
|
||||
q = ngx_queue_next(q))
|
||||
{
|
||||
n = ngx_queue_data(q, ngx_http_ipng_stats_node_t, lru);
|
||||
if (n->source_id >= sh->sources.nelts
|
||||
|| n->vip_id >= sh->vips.nelts)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
src_entry = &sh->sources.entries[n->source_id];
|
||||
vip_entry = &sh->vips.entries[n->vip_id];
|
||||
|
||||
if (filter_src->len > 0
|
||||
&& (src_entry->len != filter_src->len
|
||||
|| ngx_memcmp(src_entry->data, filter_src->data,
|
||||
filter_src->len) != 0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (filter_vip->len > 0
|
||||
&& (vip_entry->len != filter_vip->len
|
||||
|| ngx_memcmp(vip_entry->data, filter_vip->data,
|
||||
filter_vip->len) != 0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
rc = emit_counters(r, sh, n, last, emitted);
|
||||
if (rc != NGX_OK) return NGX_ERROR;
|
||||
|
||||
a = ngx_http_ipng_stats_agg_get(aggs, &naggs, naggs_alloc,
|
||||
n->source_id, n->vip_id);
|
||||
if (a == NULL) continue;
|
||||
|
||||
a->duration_sum_ms += n->duration_sum_ms;
|
||||
a->upstream_sum_ms += n->upstream_sum_ms;
|
||||
a->bytes_in_sum += n->bytes_in;
|
||||
a->bytes_out_sum += n->bytes_out;
|
||||
a->req_total += n->requests;
|
||||
|
||||
lanes = (ngx_atomic_uint_t *) (n + 1);
|
||||
blanes = lanes + 2 * (nb + 1);
|
||||
|
||||
for (i = 0; i <= nb; i++) {
|
||||
a->dhist[i] += lanes[i];
|
||||
a->uhist[i] += lanes[nb + 1 + i];
|
||||
a->up_total += lanes[nb + 1 + i];
|
||||
}
|
||||
for (i = 0; i <= nbb; i++) {
|
||||
a->bin_hist[i] += blanes[i];
|
||||
a->bout_hist[i] += blanes[nbb + 1 + i];
|
||||
n = sh->sources.nelts < n_src_alloc ? sh->sources.nelts : n_src_alloc;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (sh->sources.entries[i].len > 0) {
|
||||
d = ngx_pnalloc(r->pool, sh->sources.entries[i].len);
|
||||
if (d == NULL) return NGX_ERROR;
|
||||
ngx_memcpy(d, sh->sources.entries[i].data,
|
||||
sh->sources.entries[i].len);
|
||||
src_tbl[i].data = d;
|
||||
src_tbl[i].len = sh->sources.entries[i].len;
|
||||
}
|
||||
}
|
||||
*n_src_out = n;
|
||||
|
||||
n = sh->vips.nelts < n_vip_alloc ? sh->vips.nelts : n_vip_alloc;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (sh->vips.entries[i].len > 0) {
|
||||
d = ngx_pnalloc(r->pool, sh->vips.entries[i].len);
|
||||
if (d == NULL) return NGX_ERROR;
|
||||
ngx_memcpy(d, sh->vips.entries[i].data,
|
||||
sh->vips.entries[i].len);
|
||||
vip_tbl[i].data = d;
|
||||
vip_tbl[i].len = sh->vips.entries[i].len;
|
||||
}
|
||||
}
|
||||
*n_vip_out = n;
|
||||
|
||||
*naggs_out = naggs;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
@@ -2352,32 +2333,105 @@ ngx_http_ipng_stats_agg_alloc(ngx_http_request_t *r,
|
||||
}
|
||||
|
||||
|
||||
/* -- Prometheus ---------------------------------------------------- */
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_ipng_stats_prom_counters(ngx_http_request_t *r,
|
||||
ngx_http_ipng_stats_shctx_t *sh, ngx_http_ipng_stats_node_t *n,
|
||||
ngx_chain_t ***last, ngx_uint_t *emitted)
|
||||
/* Walk the LRU under the slab mutex, draining matching nodes into the
|
||||
* caller-supplied `snaps` array and aggregating histogram lanes into
|
||||
* `aggs`. All output buffers are pre-allocated by the caller — this
|
||||
* function does not touch r->pool, so a crash here cannot leave the
|
||||
* slab mutex held via a glibc abort.
|
||||
*
|
||||
* `src_tbl` / `vip_tbl` hold the deep-copied interning strings and are
|
||||
* used only for filter comparisons; the renderer downstream indexes
|
||||
* them again. Nodes whose ids fall outside the snapshotted range are
|
||||
* skipped (a belt-and-suspenders guard against a stale id after a
|
||||
* concurrent reload). */
|
||||
static void
|
||||
ngx_http_ipng_stats_snapshot_nodes(ngx_http_ipng_stats_shctx_t *sh,
|
||||
ngx_http_ipng_stats_main_conf_t *imcf,
|
||||
ngx_str_t *filter_src, ngx_str_t *filter_vip,
|
||||
ngx_str_t *src_tbl, ngx_uint_t n_src,
|
||||
ngx_str_t *vip_tbl, ngx_uint_t n_vip,
|
||||
ngx_http_ipng_stats_snap_t *snaps, ngx_uint_t nsnaps_alloc,
|
||||
ngx_uint_t *nsnaps_out,
|
||||
ngx_http_ipng_stats_agg_t *aggs, ngx_uint_t naggs_alloc,
|
||||
ngx_uint_t *naggs_out)
|
||||
{
|
||||
ngx_str_t *src = &sh->sources.entries[n->source_id];
|
||||
ngx_str_t *vip = &sh->vips.entries[n->vip_id];
|
||||
const char *cls = ngx_http_ipng_stats_class_label(n->class);
|
||||
ngx_chain_t *cl = ngx_http_ipng_stats_chain_buf(r, 1024);
|
||||
if (cl == NULL) return NGX_ERROR;
|
||||
cl->buf->last = ngx_sprintf(cl->buf->last,
|
||||
"nginx_ipng_requests_total{source_tag=\"%V\",vip=\"%V\",code=\"%s\"} %uA\n"
|
||||
"nginx_ipng_bytes_in_total{source_tag=\"%V\",vip=\"%V\",code=\"%s\"} %uA\n"
|
||||
"nginx_ipng_bytes_out_total{source_tag=\"%V\",vip=\"%V\",code=\"%s\"} %uA\n"
|
||||
"nginx_ipng_latency_total{source_tag=\"%V\",vip=\"%V\",code=\"%s\"} %.3f\n",
|
||||
src, vip, cls, n->requests,
|
||||
src, vip, cls, n->bytes_in,
|
||||
src, vip, cls, n->bytes_out,
|
||||
src, vip, cls, (double) n->duration_sum_ms / 1000.0);
|
||||
(*emitted)++;
|
||||
return ngx_http_ipng_stats_append(last, cl);
|
||||
ngx_queue_t *q;
|
||||
ngx_http_ipng_stats_node_t *n;
|
||||
ngx_str_t *src_entry, *vip_entry;
|
||||
ngx_atomic_uint_t *lanes, *blanes;
|
||||
ngx_http_ipng_stats_agg_t *a;
|
||||
ngx_uint_t i;
|
||||
ngx_uint_t nb = imcf->nbuckets;
|
||||
ngx_uint_t nbb = imcf->nbytebuckets;
|
||||
ngx_uint_t nsnaps = 0, naggs = 0;
|
||||
|
||||
for (q = ngx_queue_head(&sh->lru);
|
||||
q != ngx_queue_sentinel(&sh->lru);
|
||||
q = ngx_queue_next(q))
|
||||
{
|
||||
n = ngx_queue_data(q, ngx_http_ipng_stats_node_t, lru);
|
||||
if (n->source_id >= n_src || n->vip_id >= n_vip) continue;
|
||||
|
||||
src_entry = &src_tbl[n->source_id];
|
||||
vip_entry = &vip_tbl[n->vip_id];
|
||||
|
||||
if (filter_src->len > 0
|
||||
&& (src_entry->len != filter_src->len
|
||||
|| ngx_memcmp(src_entry->data, filter_src->data,
|
||||
filter_src->len) != 0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (filter_vip->len > 0
|
||||
&& (vip_entry->len != filter_vip->len
|
||||
|| ngx_memcmp(vip_entry->data, filter_vip->data,
|
||||
filter_vip->len) != 0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nsnaps < nsnaps_alloc) {
|
||||
snaps[nsnaps].source_id = n->source_id;
|
||||
snaps[nsnaps].vip_id = n->vip_id;
|
||||
snaps[nsnaps].class = n->class;
|
||||
snaps[nsnaps].requests = n->requests;
|
||||
snaps[nsnaps].bytes_in = n->bytes_in;
|
||||
snaps[nsnaps].bytes_out = n->bytes_out;
|
||||
snaps[nsnaps].duration_sum_ms = n->duration_sum_ms;
|
||||
snaps[nsnaps].upstream_sum_ms = n->upstream_sum_ms;
|
||||
nsnaps++;
|
||||
}
|
||||
|
||||
a = ngx_http_ipng_stats_agg_get(aggs, &naggs, naggs_alloc,
|
||||
n->source_id, n->vip_id);
|
||||
if (a == NULL) continue;
|
||||
a->duration_sum_ms += n->duration_sum_ms;
|
||||
a->upstream_sum_ms += n->upstream_sum_ms;
|
||||
a->bytes_in_sum += n->bytes_in;
|
||||
a->bytes_out_sum += n->bytes_out;
|
||||
a->req_total += n->requests;
|
||||
|
||||
lanes = (ngx_atomic_uint_t *) (n + 1);
|
||||
blanes = lanes + 2 * (nb + 1);
|
||||
for (i = 0; i <= nb; i++) {
|
||||
a->dhist[i] += lanes[i];
|
||||
a->uhist[i] += lanes[nb + 1 + i];
|
||||
a->up_total += lanes[nb + 1 + i];
|
||||
}
|
||||
for (i = 0; i <= nbb; i++) {
|
||||
a->bin_hist[i] += blanes[i];
|
||||
a->bout_hist[i] += blanes[nbb + 1 + i];
|
||||
}
|
||||
}
|
||||
|
||||
*nsnaps_out = nsnaps;
|
||||
*naggs_out = naggs;
|
||||
}
|
||||
|
||||
|
||||
/* -- Prometheus ---------------------------------------------------- */
|
||||
|
||||
|
||||
static u_char *
|
||||
ngx_http_ipng_stats_render_hist(u_char *p, const char *metric,
|
||||
ngx_str_t *src, ngx_str_t *vip, ngx_uint_t *bounds, ngx_uint_t nb,
|
||||
@@ -2422,12 +2476,16 @@ ngx_http_ipng_stats_render_prom(ngx_http_request_t *r,
|
||||
ngx_chain_t **last = &out;
|
||||
ngx_str_t ctype = ngx_string("text/plain; version=0.0.4");
|
||||
ngx_http_ipng_stats_agg_t *aggs;
|
||||
ngx_http_ipng_stats_snap_t *snaps;
|
||||
ngx_str_t *src_tbl = NULL, *vip_tbl = NULL;
|
||||
ngx_uint_t n_src_alloc, n_vip_alloc;
|
||||
ngx_uint_t n_src = 0, n_vip = 0;
|
||||
ngx_uint_t naggs = 0, naggs_alloc;
|
||||
ngx_uint_t emitted = 0, i;
|
||||
ngx_uint_t nsnaps = 0, nsnaps_alloc;
|
||||
ngx_uint_t i;
|
||||
ngx_uint_t nb = imcf->nbuckets;
|
||||
ngx_uint_t nbb = imcf->nbytebuckets;
|
||||
size_t hist_sz;
|
||||
ngx_int_t rc;
|
||||
|
||||
slab = (ngx_slab_pool_t *) imcf->shm_zone->shm.addr;
|
||||
|
||||
@@ -2449,28 +2507,91 @@ ngx_http_ipng_stats_render_prom(ngx_http_request_t *r,
|
||||
"# TYPE nginx_ipng_upstream_response_seconds histogram\n"
|
||||
"# HELP nginx_ipng_bytes_in Request size histogram in bytes.\n"
|
||||
"# TYPE nginx_ipng_bytes_in histogram\n"
|
||||
"# HELP nginx_ipng_bytes_out Response size histogram in bytes.\n"
|
||||
"# HELP nginx_ipng_bytes_out Request size histogram in bytes.\n"
|
||||
"# TYPE nginx_ipng_bytes_out histogram\n",
|
||||
NGX_HTTP_IPNG_STATS_VERSION, NGX_HTTP_IPNG_STATS_SCHEMA_VERSION);
|
||||
if (ngx_http_ipng_stats_append(&last, cl) != NGX_OK) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
/* Read cardinality with a brief lock. We release before allocating
|
||||
* the snapshot buffers so glibc's allocator is never entered with
|
||||
* the slab mutex held — a worker crash during allocation cannot
|
||||
* leave the shared-memory zone locked. */
|
||||
ngx_shmtx_lock(&slab->mutex);
|
||||
n_src_alloc = sh->sources.nelts;
|
||||
n_vip_alloc = sh->vips.nelts;
|
||||
ngx_shmtx_unlock(&slab->mutex);
|
||||
|
||||
if (n_src_alloc > NGX_HTTP_IPNG_STATS_MAX_INTERN
|
||||
|| n_vip_alloc > NGX_HTTP_IPNG_STATS_MAX_INTERN)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
|
||||
"ipng_stats: render: refusing to render, cardinality "
|
||||
"out of range (sources=%ui, vips=%ui)",
|
||||
n_src_alloc, n_vip_alloc);
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
naggs_alloc = n_src_alloc * n_vip_alloc;
|
||||
nsnaps_alloc = naggs_alloc * NGX_HTTP_IPNG_STATS_NCLASSES;
|
||||
if (naggs_alloc == 0) naggs_alloc = 1;
|
||||
if (nsnaps_alloc == 0) nsnaps_alloc = 1;
|
||||
|
||||
if (ngx_http_ipng_stats_agg_alloc(r, imcf, naggs_alloc,
|
||||
&aggs, &naggs_alloc) != NGX_OK)
|
||||
{
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
snaps = ngx_pcalloc(r->pool, nsnaps_alloc * sizeof(*snaps));
|
||||
if (n_src_alloc > 0) {
|
||||
src_tbl = ngx_pcalloc(r->pool, n_src_alloc * sizeof(ngx_str_t));
|
||||
if (src_tbl == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
if (n_vip_alloc > 0) {
|
||||
vip_tbl = ngx_pcalloc(r->pool, n_vip_alloc * sizeof(ngx_str_t));
|
||||
if (vip_tbl == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
if (snaps == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
|
||||
ngx_shmtx_lock(&slab->mutex);
|
||||
|
||||
if (ngx_http_ipng_stats_agg_alloc(r, imcf,
|
||||
sh->sources.nelts * sh->vips.nelts, &aggs, &naggs_alloc) != NGX_OK)
|
||||
if (ngx_http_ipng_stats_snapshot_strings(r, sh,
|
||||
src_tbl, n_src_alloc, &n_src,
|
||||
vip_tbl, n_vip_alloc, &n_vip) != NGX_OK)
|
||||
{
|
||||
ngx_shmtx_unlock(&slab->mutex);
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
rc = ngx_http_ipng_stats_walk_aggregate(r, imcf, filter_source, filter_vip,
|
||||
ngx_http_ipng_stats_prom_counters, &last, &emitted,
|
||||
aggs, naggs_alloc, &naggs);
|
||||
if (rc != NGX_OK) {
|
||||
ngx_shmtx_unlock(&slab->mutex);
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
ngx_http_ipng_stats_snapshot_nodes(sh, imcf,
|
||||
filter_source, filter_vip,
|
||||
src_tbl, n_src, vip_tbl, n_vip,
|
||||
snaps, nsnaps_alloc, &nsnaps,
|
||||
aggs, naggs_alloc, &naggs);
|
||||
|
||||
ngx_shmtx_unlock(&slab->mutex);
|
||||
|
||||
/* Per-node counters. */
|
||||
for (i = 0; i < nsnaps; i++) {
|
||||
ngx_http_ipng_stats_snap_t *s = &snaps[i];
|
||||
ngx_str_t *src = &src_tbl[s->source_id];
|
||||
ngx_str_t *vip = &vip_tbl[s->vip_id];
|
||||
const char *cls = ngx_http_ipng_stats_class_label(s->class);
|
||||
cl = ngx_http_ipng_stats_chain_buf(r, 1024);
|
||||
if (cl == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
cl->buf->last = ngx_sprintf(cl->buf->last,
|
||||
"nginx_ipng_requests_total{source_tag=\"%V\",vip=\"%V\",code=\"%s\"} %uL\n"
|
||||
"nginx_ipng_bytes_in_total{source_tag=\"%V\",vip=\"%V\",code=\"%s\"} %uL\n"
|
||||
"nginx_ipng_bytes_out_total{source_tag=\"%V\",vip=\"%V\",code=\"%s\"} %uL\n"
|
||||
"nginx_ipng_latency_total{source_tag=\"%V\",vip=\"%V\",code=\"%s\"} %.3f\n",
|
||||
src, vip, cls, s->requests,
|
||||
src, vip, cls, s->bytes_in,
|
||||
src, vip, cls, s->bytes_out,
|
||||
src, vip, cls, (double) s->duration_sum_ms / 1000.0);
|
||||
if (ngx_http_ipng_stats_append(&last, cl) != NGX_OK) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/* One chain link per (source, vip) for the four aggregated histograms.
|
||||
@@ -2479,15 +2600,12 @@ ngx_http_ipng_stats_render_prom(ngx_http_request_t *r,
|
||||
|
||||
for (i = 0; i < naggs; i++) {
|
||||
ngx_http_ipng_stats_agg_t *a = &aggs[i];
|
||||
ngx_str_t *src = &sh->sources.entries[a->source_id];
|
||||
ngx_str_t *vip = &sh->vips.entries[a->vip_id];
|
||||
ngx_str_t *src = &src_tbl[a->source_id];
|
||||
ngx_str_t *vip = &vip_tbl[a->vip_id];
|
||||
u_char *p;
|
||||
|
||||
cl = ngx_http_ipng_stats_chain_buf(r, hist_sz);
|
||||
if (cl == NULL) {
|
||||
ngx_shmtx_unlock(&slab->mutex);
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
if (cl == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
p = cl->buf->last;
|
||||
p = ngx_http_ipng_stats_render_hist(p,
|
||||
"nginx_ipng_request_duration_seconds", src, vip,
|
||||
@@ -2507,33 +2625,16 @@ ngx_http_ipng_stats_render_prom(ngx_http_request_t *r,
|
||||
(double) a->bytes_out_sum, 0);
|
||||
cl->buf->last = p;
|
||||
if (ngx_http_ipng_stats_append(&last, cl) != NGX_OK) {
|
||||
ngx_shmtx_unlock(&slab->mutex);
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_shmtx_unlock(&slab->mutex);
|
||||
return ngx_http_ipng_stats_send(r, &ctype, out);
|
||||
}
|
||||
|
||||
|
||||
/* -- JSON ---------------------------------------------------------- */
|
||||
|
||||
/* Per-(source, vip, class) counter group. We render one JSON object per
|
||||
* aggregated (source, vip) record, with the class breakdown stored in
|
||||
* an interim table while the walk is in progress. */
|
||||
typedef struct {
|
||||
ngx_uint_t source_id;
|
||||
ngx_uint_t vip_id;
|
||||
ngx_uint_t class;
|
||||
uint64_t requests;
|
||||
uint64_t bytes_in;
|
||||
uint64_t bytes_out;
|
||||
uint64_t duration_sum_ms;
|
||||
uint64_t upstream_sum_ms;
|
||||
} ngx_http_ipng_stats_jnode_t;
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_ipng_stats_render_json(ngx_http_request_t *r,
|
||||
ngx_http_ipng_stats_main_conf_t *imcf,
|
||||
@@ -2545,16 +2646,15 @@ ngx_http_ipng_stats_render_json(ngx_http_request_t *r,
|
||||
ngx_chain_t **last = &out;
|
||||
ngx_str_t ctype = ngx_string("application/json");
|
||||
ngx_http_ipng_stats_agg_t *aggs;
|
||||
ngx_http_ipng_stats_jnode_t *jnodes;
|
||||
ngx_uint_t njnodes = 0, njnodes_alloc;
|
||||
ngx_http_ipng_stats_snap_t *snaps;
|
||||
ngx_str_t *src_tbl = NULL, *vip_tbl = NULL;
|
||||
ngx_uint_t n_src_alloc, n_vip_alloc;
|
||||
ngx_uint_t n_src = 0, n_vip = 0;
|
||||
ngx_uint_t nsnaps = 0, nsnaps_alloc;
|
||||
ngx_uint_t naggs = 0, naggs_alloc;
|
||||
ngx_uint_t i, j, emitted = 0;
|
||||
ngx_uint_t nb = imcf->nbuckets;
|
||||
ngx_uint_t nbb = imcf->nbytebuckets;
|
||||
ngx_queue_t *q;
|
||||
ngx_http_ipng_stats_node_t *nd;
|
||||
ngx_str_t *src_entry, *vip_entry;
|
||||
ngx_atomic_uint_t *lanes, *blanes;
|
||||
ngx_http_ipng_stats_agg_t *a;
|
||||
size_t rec_sz;
|
||||
|
||||
@@ -2570,86 +2670,59 @@ ngx_http_ipng_stats_render_json(ngx_http_request_t *r,
|
||||
}
|
||||
|
||||
ngx_shmtx_lock(&slab->mutex);
|
||||
n_src_alloc = sh->sources.nelts;
|
||||
n_vip_alloc = sh->vips.nelts;
|
||||
ngx_shmtx_unlock(&slab->mutex);
|
||||
|
||||
if (n_src_alloc > NGX_HTTP_IPNG_STATS_MAX_INTERN
|
||||
|| n_vip_alloc > NGX_HTTP_IPNG_STATS_MAX_INTERN)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
|
||||
"ipng_stats: render: refusing to render, cardinality "
|
||||
"out of range (sources=%ui, vips=%ui)",
|
||||
n_src_alloc, n_vip_alloc);
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
naggs_alloc = n_src_alloc * n_vip_alloc;
|
||||
nsnaps_alloc = naggs_alloc * NGX_HTTP_IPNG_STATS_NCLASSES;
|
||||
if (naggs_alloc == 0) naggs_alloc = 1;
|
||||
if (nsnaps_alloc == 0) nsnaps_alloc = 1;
|
||||
|
||||
naggs_alloc = sh->sources.nelts * sh->vips.nelts;
|
||||
if (ngx_http_ipng_stats_agg_alloc(r, imcf, naggs_alloc,
|
||||
&aggs, &naggs_alloc) != NGX_OK)
|
||||
{
|
||||
ngx_shmtx_unlock(&slab->mutex);
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
/* Upper bound on jnodes = naggs_alloc * NCLASSES. */
|
||||
njnodes_alloc = naggs_alloc * NGX_HTTP_IPNG_STATS_NCLASSES;
|
||||
if (njnodes_alloc == 0) njnodes_alloc = 1;
|
||||
jnodes = ngx_pcalloc(r->pool, njnodes_alloc * sizeof(*jnodes));
|
||||
if (jnodes == NULL) {
|
||||
ngx_shmtx_unlock(&slab->mutex);
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
snaps = ngx_pcalloc(r->pool, nsnaps_alloc * sizeof(*snaps));
|
||||
if (n_src_alloc > 0) {
|
||||
src_tbl = ngx_pcalloc(r->pool, n_src_alloc * sizeof(ngx_str_t));
|
||||
if (src_tbl == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
if (n_vip_alloc > 0) {
|
||||
vip_tbl = ngx_pcalloc(r->pool, n_vip_alloc * sizeof(ngx_str_t));
|
||||
if (vip_tbl == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
if (snaps == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
|
||||
for (q = ngx_queue_head(&sh->lru);
|
||||
q != ngx_queue_sentinel(&sh->lru);
|
||||
q = ngx_queue_next(q))
|
||||
ngx_shmtx_lock(&slab->mutex);
|
||||
|
||||
if (ngx_http_ipng_stats_snapshot_strings(r, sh,
|
||||
src_tbl, n_src_alloc, &n_src,
|
||||
vip_tbl, n_vip_alloc, &n_vip) != NGX_OK)
|
||||
{
|
||||
nd = ngx_queue_data(q, ngx_http_ipng_stats_node_t, lru);
|
||||
if (nd->source_id >= sh->sources.nelts
|
||||
|| nd->vip_id >= sh->vips.nelts)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
src_entry = &sh->sources.entries[nd->source_id];
|
||||
vip_entry = &sh->vips.entries[nd->vip_id];
|
||||
|
||||
if (filter_source->len > 0
|
||||
&& (src_entry->len != filter_source->len
|
||||
|| ngx_memcmp(src_entry->data, filter_source->data,
|
||||
filter_source->len) != 0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (filter_vip->len > 0
|
||||
&& (vip_entry->len != filter_vip->len
|
||||
|| ngx_memcmp(vip_entry->data, filter_vip->data,
|
||||
filter_vip->len) != 0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (njnodes < njnodes_alloc) {
|
||||
jnodes[njnodes].source_id = nd->source_id;
|
||||
jnodes[njnodes].vip_id = nd->vip_id;
|
||||
jnodes[njnodes].class = nd->class;
|
||||
jnodes[njnodes].requests = nd->requests;
|
||||
jnodes[njnodes].bytes_in = nd->bytes_in;
|
||||
jnodes[njnodes].bytes_out = nd->bytes_out;
|
||||
jnodes[njnodes].duration_sum_ms = nd->duration_sum_ms;
|
||||
jnodes[njnodes].upstream_sum_ms = nd->upstream_sum_ms;
|
||||
njnodes++;
|
||||
}
|
||||
|
||||
a = ngx_http_ipng_stats_agg_get(aggs, &naggs, naggs_alloc,
|
||||
nd->source_id, nd->vip_id);
|
||||
if (a == NULL) continue;
|
||||
a->duration_sum_ms += nd->duration_sum_ms;
|
||||
a->upstream_sum_ms += nd->upstream_sum_ms;
|
||||
a->bytes_in_sum += nd->bytes_in;
|
||||
a->bytes_out_sum += nd->bytes_out;
|
||||
a->req_total += nd->requests;
|
||||
|
||||
lanes = (ngx_atomic_uint_t *) (nd + 1);
|
||||
blanes = lanes + 2 * (nb + 1);
|
||||
for (i = 0; i <= nb; i++) {
|
||||
a->dhist[i] += lanes[i];
|
||||
a->uhist[i] += lanes[nb + 1 + i];
|
||||
a->up_total += lanes[nb + 1 + i];
|
||||
}
|
||||
for (i = 0; i <= nbb; i++) {
|
||||
a->bin_hist[i] += blanes[i];
|
||||
a->bout_hist[i] += blanes[nbb + 1 + i];
|
||||
}
|
||||
ngx_shmtx_unlock(&slab->mutex);
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
ngx_http_ipng_stats_snapshot_nodes(sh, imcf,
|
||||
filter_source, filter_vip,
|
||||
src_tbl, n_src, vip_tbl, n_vip,
|
||||
snaps, nsnaps_alloc, &nsnaps,
|
||||
aggs, naggs_alloc, &naggs);
|
||||
|
||||
ngx_shmtx_unlock(&slab->mutex);
|
||||
|
||||
/* One JSON record per aggregated (source, vip). Size upper-bound
|
||||
* accounts for: fixed overhead, up to NCLASSES class entries, 4
|
||||
* histograms. */
|
||||
@@ -2660,33 +2733,30 @@ ngx_http_ipng_stats_render_json(ngx_http_request_t *r,
|
||||
|
||||
for (i = 0; i < naggs; i++) {
|
||||
a = &aggs[i];
|
||||
ngx_str_t *src = &sh->sources.entries[a->source_id];
|
||||
ngx_str_t *vip = &sh->vips.entries[a->vip_id];
|
||||
ngx_str_t *src = &src_tbl[a->source_id];
|
||||
ngx_str_t *vip = &vip_tbl[a->vip_id];
|
||||
u_char *p;
|
||||
ngx_uint_t first_class = 1;
|
||||
|
||||
cl = ngx_http_ipng_stats_chain_buf(r, rec_sz);
|
||||
if (cl == NULL) {
|
||||
ngx_shmtx_unlock(&slab->mutex);
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
if (cl == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
p = cl->buf->last;
|
||||
p = ngx_sprintf(p,
|
||||
"%s{\"source_tag\":\"%V\",\"vip\":\"%V\",\"classes\":{",
|
||||
(emitted == 0) ? "" : ",", src, vip);
|
||||
|
||||
for (j = 0; j < njnodes; j++) {
|
||||
if (jnodes[j].source_id != a->source_id
|
||||
|| jnodes[j].vip_id != a->vip_id) continue;
|
||||
for (j = 0; j < nsnaps; j++) {
|
||||
if (snaps[j].source_id != a->source_id
|
||||
|| snaps[j].vip_id != a->vip_id) continue;
|
||||
p = ngx_sprintf(p,
|
||||
"%s\"%s\":{\"requests\":%uL,\"bytes_in\":%uL,"
|
||||
"\"bytes_out\":%uL,\"latency_ms\":%uL,"
|
||||
"\"upstream_latency_ms\":%uL}",
|
||||
first_class ? "" : ",",
|
||||
ngx_http_ipng_stats_class_label(jnodes[j].class),
|
||||
jnodes[j].requests, jnodes[j].bytes_in,
|
||||
jnodes[j].bytes_out, jnodes[j].duration_sum_ms,
|
||||
jnodes[j].upstream_sum_ms);
|
||||
ngx_http_ipng_stats_class_label(snaps[j].class),
|
||||
snaps[j].requests, snaps[j].bytes_in,
|
||||
snaps[j].bytes_out, snaps[j].duration_sum_ms,
|
||||
snaps[j].upstream_sum_ms);
|
||||
first_class = 0;
|
||||
}
|
||||
p = ngx_sprintf(p, "},\"request_duration_ms\":{\"sum\":%uL,"
|
||||
@@ -2741,14 +2811,11 @@ ngx_http_ipng_stats_render_json(ngx_http_request_t *r,
|
||||
|
||||
cl->buf->last = p;
|
||||
if (ngx_http_ipng_stats_append(&last, cl) != NGX_OK) {
|
||||
ngx_shmtx_unlock(&slab->mutex);
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
emitted++;
|
||||
}
|
||||
|
||||
ngx_shmtx_unlock(&slab->mutex);
|
||||
|
||||
cl = ngx_http_ipng_stats_chain_buf(r, 8);
|
||||
if (cl == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
cl->buf->last = ngx_sprintf(cl->buf->last, "]}\n");
|
||||
|
||||
Reference in New Issue
Block a user