# Metrics One store of metrics for the whole Zones process: what Zones measures of itself, and what any zone's code measures, read as Prometheus text. Off unless the shell turns it on. ```js // shell/next.config.mjs export default zoneConfig({ mount: "/", metrics: true }); ``` `createZones({ metrics })` overrides the shell's declaration. ## Reading it `GET /metrics` (`/_next-zones/metrics` unless the shell's endpoints declare another base). `metrics: true` alone opens it: `endpoints` need not be declared, and `endpoints: { admin: true }` is not needed. It answers an admin only: a request with `Authorization: Bearer `, or from the same machine when no token is configured. The body is Prometheus' text format (`text/plain; version=0.0.4`): point a Prometheus scraper, an OpenTelemetry collector or any tool that reads that format at it. Nothing is pushed anywhere. ## What Zones measures | Metric | Type | Labels | | |---|---|---|---| | `nextzones_requests_total` | counter | `zone`, `version`, `code` (`2xx`…`5xx`) | Every request; `zone` is `shell` for the shell's pages and `static` for `/_next/…` files | | `nextzones_request_duration_seconds` | histogram | `zone` | Time to the end of each response | | `nextzones_install_seconds` | histogram | `zone`, `outcome` | Installs, from staging to serving | | `nextzones_install_blocked_seconds` | histogram | `zone` | The longest the event loop was held during an install | | `nextzones_pull_seconds` | histogram | `zone`, `via`, `outcome` | Pulls of a zone image from a source | | `nextzones_pruned_images_total`, `nextzones_pruned_bytes_total` | counter | | What pruning removed | | `nextzones_zone_active` | gauge | `zone`, `version` | 1 for the version that serves; 0 once a swap replaced it | | `nextzones_registry_modules` | gauge | | Server modules shared across builds | | `nextzones_registry_shared_total` | counter | | Times a build used a module another build had loaded | | `nextzones_reclaims_total` | counter | | Last-resort collections after a version was collected | | `process_resident_memory_bytes`, `nodejs_heap_used_bytes`, `nodejs_heap_total_bytes`, `nodejs_external_memory_bytes` | gauge | | The process | | `nodejs_eventloop_delay_seconds` | gauge | `quantile` (`0.5`, `0.99`, `1`) | Event loop delay since the last read | | `process_uptime_seconds` | gauge | | | ## Measuring in a zone ```ts import { counter, gauge, histogram, time } from "@runsnip/next-zones/metrics"; const exportsStarted = counter("blog_exports_total", { help: "Exports started" }); const queue = gauge("blog_queue_length", { help: "Jobs waiting" }); const size = histogram("blog_export_bytes", { help: "Export sizes", buckets: [1e4, 1e5, 1e6, 1e7] }); exportsStarted.inc({ format: "pdf" }); queue.set(12); size.observe(file.byteLength); const html = await time("blog_render_seconds", () => render(post), { kind: "post" }); // labelled outcome "ok" or "error" ``` - **One store for every zone.** The shell and each zone are separate builds, and each bundles this module; they all write to the one store Zones opened. Two zones may write one metric (with labels of their own). - **Off, they do nothing.** With metrics off, or in the browser, or when the zone runs alone with `next start`, every function returns without a trace, so a zone's code can measure unconditionally. - **Prometheus' rules:** a name is letters, digits, `_` and `:`; a counter only goes up; values in base units (seconds, bytes). A name keeps the type it was first used with: using it as another type throws. - **The functions:** - `help` is optional everywhere (Prometheus' `# HELP` line); - `counter(name, { help })` → `.inc(labels?)`, `.inc(n, labels?)`; - `gauge(name, { help })` → `.set(value, labels?)`, `.inc(…)`, `.dec(…)` (as a counter's); - `histogram(name, { help, buckets? })` → `.observe(value, labels?)`. The default buckets are seconds: a histogram of anything else (bytes, items) passes its own; - `time(name, fn, labels?, { help?, buckets? }?)` runs `fn` (sync or async), returns what it returns, and observes its seconds into the histogram `name`, labelled `outcome` `"ok"` or `"error"` (an error is thrown on); - `enabled()` says whether the store is open; `render()` is the Prometheus text that `/metrics` serves; - `collect(fn)` runs `fn` before each read, to set gauges from the current state, and returns a function that removes it. - **Buckets.** A histogram, and `time()`, use buckets in seconds unless given others (`time(name, fn, labels, { buckets })`): 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60.