Skip to content

feat(http): expose the proxy cache a request went through - #321

Open
u5surf wants to merge 2 commits into
nginx:mainfrom
u5surf:feat/request-cache-accessors
Open

feat(http): expose the proxy cache a request went through#321
u5surf wants to merge 2 commits into
nginx:mainfrom
u5surf:feat/request-cache-accessors

Conversation

@u5surf

@u5surf u5surf commented Aug 31, 2026

Copy link
Copy Markdown

Proposed changes

Two accessors for the proxy cache a request went through, and three for the numbers behind it.

request.cache_status()            // Option<CacheStatus>  -  $upstream_cache_status
request.cache_zone_name()         // Option<&NgxStr>      -  the keys_zone name
request.cache_zone_max_size()     // Option<u64>, bytes
request.cache_zone_used_size()    // Option<u64>, bytes
request.cache_zone_block_size()   // Option<usize>

CacheStatus is an enum over the eight NGX_HTTP_CACHE_* values, with from_raw returning None for the zero that nginx leaves on a request that consulted no cache — so a caller cannot mistake "no lookup happened" for a status.

Everything is behind #[cfg(ngx_feature = "http_cache")], since none of the fields exist otherwise. Built and tested both ways.

Why the sizes are worth an accessor

This is the part I would not have got right by reading the struct.

nginx does not keep max_size and sh->size in bytes. ngx_http_file_cache_init divides the configured size by the filesystem block size so the two can be compared directly:

cache->bsize = ngx_fs_bsize(cache->path->name.data);
cache->max_size /= cache->bsize;

and the cache manager accumulates sh->size in the same unit. Reading either field off the struct answers in blocks — on a 4k filesystem, a figure roughly four thousand times too small, with nothing about the field to suggest it. A metric built that way looks plausible and is wrong.

cache_zone_max_size() and cache_zone_used_size() multiply by bsize and answer in bytes, which is what the configuration said. cache_zone_block_size() is there for anyone who wants the raw unit.

The test states the trap directly: a 1 MiB zone holding 256 KiB reads as 256 and 64 off the struct, and as 1048576 and 262144 through the accessors.

Where this came from

Writing a traffic-status module. These are the four things it reads to report nginx_vts_cache_requests_total and nginx_vts_cache_size_bytes, and none of them had an accessor, so that part of the module is still C. Its notes on what keeps it there are here.

Testing

cargo test, cargo clippy --all-targets, cargo fmt --check and cargo doc clean, with --features vendored and again with NGX_CONFIGURE_ARGS=--without-http-cache so the gated code is compiled out.

Seven unit tests over the cache accessors, covering each null in the r->cache -> file_cache -> shm_zone chain, the zero sentinel, and the block-to-byte conversion.

Checklist

  • I have written my commit messages in the Conventional Commits format.
  • I have read the CONTRIBUTING doc
  • I have added tests (when possible) that prove my fix is effective or that my feature works
  • I have checked that all unit tests pass after adding my changes
  • I have updated necessary documentation
  • I have rebased my branch onto main
  • I will ensure my PR is targeting the main branch and pulling from my branch from my own fork

u5surf added 2 commits May 25, 2026 15:06
Add a typed `CacheStatus` mirror of nginx's `NGX_HTTP_CACHE_*`
constants and two safe `Request` accessors:

  - `Request::cache_status() -> Option<CacheStatus>` reads
    `r->upstream->cache_status` (the same value
    `$upstream_cache_status` exposes) and converts the raw
    `ngx_uint_t` into a typed variant; the "no cache lookup" sentinel
    (`0`) and any unknown value surface as `None`.
  - `Request::cache_zone_name() -> Option<&NgxStr>` walks
    `r->cache->file_cache->shm_zone->shm.name` to return the
    `proxy_cache_path` keys-zone name — handy as the `zone=` label
    for cache metrics.

Both are gated by `#[cfg(ngx_feature = "http_cache")]` so builds
without `--without-http_cache` still link.

Unit tests use the `MaybeUninit::zeroed` pattern from nginx#272 to
construct the request / upstream / cache chain on the stack and
verify the null / zero-sentinel / populated paths.
cache_zone_name() gives a metric its zone= label; the numbers to put
against it are still out of reach. A module reporting on the cache wants
how large it may grow and how much of that it is using.

Adds cache_zone_max_size() and cache_zone_used_size(), both in bytes, and
cache_zone_block_size() for callers that want the raw unit.

Bytes rather than what the struct holds, because what it holds is
surprising. ngx_http_file_cache_init divides the configured max_size by
the filesystem block size so it can be compared against sh->size, which
the cache manager also accumulates in blocks. Reading either field
directly answers in blocks, which on a 4k filesystem is a figure some
thousands of times too small, and nothing about the field says so. The
test covers exactly that: a 1 MiB zone holding 256 KiB reads as 256 and
64 off the struct.

Signed-off-by: Y.Horie <u5.horie@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant