Skip to content

Request timing analytics on the site Overview, with a link to the captured queries#797

Merged
geodro merged 10 commits into
mainfrom
feat/request-timing-analytics
Jul 8, 2026
Merged

Request timing analytics on the site Overview, with a link to the captured queries#797
geodro merged 10 commits into
mainfrom
feat/request-timing-analytics

Conversation

@geodro

@geodro geodro commented Jul 7, 2026

Copy link
Copy Markdown
Member

This adds a request-timing analytics view to the site Overview, driven by the always-on nginx access feed so it needs no debugger to use, and links a slow route to the queries behind it.

Every request nginx serves is written to a small SQLite store in the data directory, buffered off the access feed and flushed in batches, and pruned to a seven-day window. The driver is pure Go so the CGO-free build is unaffected, and the existing in-memory windows keep driving the doctor and slow-route notifications unchanged. lerd-ui reads the store back over a WAL connection to build the view for a chosen range, fifteen minutes up to seven days.

The Overview section shows the typical and p95 response times, the request count and error rate, a response-time distribution, a throughput chart, the slowest routes, and a table of every route with its p50 and p95, plus a recent-requests tab with each call's time, method, path, status, and duration. The slowest-routes list ranks each route by its recent p95, so a route that has been fixed drops down or off as newer faster requests arrive even while its old slow samples still sit in the window. Requests nginx serves without the app, static assets by extension and anything with a zero request time such as manifest.json, are left out so a page's asset requests don't drown its app routes. The first request after a site has sat idle past the idle-suspend timeout is a cold start whose wake cost would skew the numbers, so it is kept out of every timing figure while still counting toward the totals and marked in the recent list.

When debug capture is on, each route carries an Inspect queries button that jumps to the Debug tab's Queries lens filtered to that route, the one place that renders captured queries, rather than duplicating them in a modal. The Debug lenses now share a single search filter within a site's Debug view, and the search matches the request path so filtering by a route works.

Latency throughout is nginx's per-request time in milliseconds. With the fuller view on the Overview, the older compact request-timing panel is removed from the Debug tab.

Closes #796

geodro added 3 commits July 7, 2026 23:48
…alytics view

The watcher already reads the nginx access feed to flag slow routes; it now also writes each request to a SQLite store in the data dir, buffered between save ticks and flushed in one batch, with rows past a seven day retention pruned hourly. The store is pure Go so the CGO-free build stays intact, and the existing in-memory windows keep driving the doctor and slow-route notifications unchanged.

lerd-ui reads the same store over a WAL connection and serves a per-site analytics view for a chosen window, the response-time distribution, per-route p50 and p95, a status-class breakdown, per-minute throughput, and the most recent requests. This is the data behind an upcoming request-timing panel.
The Overview gains a Request timing section driven by the durable store: a range picker, KPIs for the typical and p95 response times, request count and error rate, a response-time distribution, a throughput chart, the slowest routes with click to profile, and a routes and recent-requests panel.

The first request after a site has sat idle past the idle-suspend timeout is a cold start whose wake cost would skew the numbers, so it is recorded with a cold flag and kept out of every timing figure, the site and per-route percentiles and the distribution, while still counting toward the request total and showing in the recent list with a marker. Older stores gain the cold column through a lightweight migration.
…timing panel

The slowest-routes list ranks and reads each route by its recent p95, the p95 over the route's most recent samples, so a route that was slow but has since been fixed drops down or off as newer faster requests arrive, even while its old slow samples still sit in the window's overall p95. The recent-requests list shows each call's clock time rather than a relative age.

With the fuller analytics view now on the Overview, the compact request-timing panel is removed from the Debug tab, which is left with only the debug-bridge lenses, and the slow-route doctor hint and the sites documentation point at the Overview instead.
@geodro geodro requested a review from a team as a code owner July 7, 2026 21:34
geodro added 3 commits July 8, 2026 09:21
…put chart

Static assets, and requests nginx serves without the app, judged by a static-asset file extension or a zero request time (manifest.json, robots.txt, service workers), are kept out of the request-timing view both at ingest and for anything already stored, so a page's dozens of asset requests no longer drown out its app routes.

The per-route p50 was serialized away when it was exactly zero, which rendered as NaN in the routes table; it is always sent now. The throughput chart gains a labelled y axis and clock-time x labels, with points placed by their real time, so its numbers are readable.
When debug capture is on, each route in the timing view gains an Inspect queries button that opens the query evidence recorded against that exact route, matched on the same route key the timing groups by. A new endpoint gathers the captured query events for the route and returns each request's query count and total query time alongside its N+1 and slow-query findings, with the offending SQL and its caller, worst by query time first.

Evidence exists only for requests hit while capture was on, so a route not yet exercised with the debugger opens empty with a prompt to reload it. This reuses the same analysis the optimize report and the N+1 notifier already run, so the timing view and the debugger agree on which queries belong to which route.
…filter

The timing view's Inspect queries button no longer opens a modal that re-rendered the queries; it links to the Debug tab's Queries lens, the one place that renders captured queries, seeded with the route's path so the lens scopes to it. The per-route endpoint and the modal are removed, so there's a single source of truth for query rendering.

The Debug lenses, Queries, Dumps, and the kind lenses, now share one search filter within a site's Debug view, so a search set in one lens, or seeded by the Inspect link, carries over when you switch lenses. The search matches the request path as well as the SQL, file, worker, and branch, and the filter box gains a clear button distinct from the Clear that wipes captured data.
@geodro geodro changed the title Request timing analytics on the site Overview Request timing analytics on the site Overview, with a link to the captured queries Jul 8, 2026
geodro added 4 commits July 8, 2026 10:15
…ng windows

The durable store already drops static assets and the zero-time requests nginx serves directly; the in-memory aggregator that feeds the doctor's typical response time, the slow-route notifications, and the MCP route_timing action now drops them the same way, so those stay consistent with the analytics view and a page's asset requests don't drag the baseline down.
@geodro geodro merged commit 732197a into main Jul 8, 2026
3 checks passed
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.

Request timing analytics on the site Overview

1 participant