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:
2026-04-18 10:58:51 +02:00
parent cdcbb07c9a
commit fdef2a552b
8 changed files with 746 additions and 232 deletions

View File

@@ -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");