Add worked examples to docs pages

Add concrete, verified examples to the General idea, Filtering,
Parameters, Metrics, Dashboards, and Settings docs sections, covering
common tasks like declaring a value-role duration param, building a
requests dashboard with line/bar/pie panels, and filter-bar syntax
gotchas (quoted vs. unquoted values, metric-only [param] comparisons).
Verified each example against the actual filter/PromQL parsers and
engine behavior in src/filter_parser.rs, src/promql.rs, and
src/engine.rs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
This commit is contained in:
Hugh Kaznowski
2026-09-05 18:38:49 +01:00
co-authored by Claude Sonnet 5
parent 80515e0f84
commit 1db3adde90
+95
View File
@@ -62,6 +62,14 @@ const SECTIONS: DocSection[] = [
copy of the active one, and its "×" button deletes the active profile. Switching profiles hot-swaps the copy of the active one, and its "×" button deletes the active profile. Switching profiles hot-swaps the
running engine's declared params/metrics immediately, with no restart. running engine's declared params/metrics immediately, with no restart.
</p> </p>
<p className="font-semibold text-text">Worked example</p>
<p>
Piping a webserver's access log — <Code>{'tail -f access.log | ottyl'}</Code> — into a profile named{' '}
<Code>webserver</Code>: create the profile from the settings menu, declare params/metrics against it (see
the <em>Parameters</em>, <em>Metrics</em>, and <em>Dashboards</em> sections below), then next time you run
the same command, switch to <Code>webserver</Code> and everything you declared is already there —
no re-declaring params or rebuilding dashboards.
</p>
</div> </div>
), ),
}, },
@@ -109,6 +117,26 @@ const SECTIONS: DocSection[] = [
<p> <p>
The filter string is evaluated on the server. A parse error shows as inline text under the filter bar. The filter string is evaluated on the server. A parse error shows as inline text under the filter bar.
</p> </p>
<p className="font-semibold text-text">Worked examples</p>
<ul className="list-disc pl-5">
<li>
Only lines mentioning "timeout" in the last 15 minutes:{' '}
<Code>{'regex("timeout") and timestamp>-15m'}</Code>
</li>
<li>
Requests slower than 200ms, once a <Code>requests</Code> metric has a <Code>value</Code>-role{' '}
<Code>duration</Code> param declared: <Code>{'requests[duration]>200'}</Code>
</li>
<li>
5xx responses on one path, given a <Code>requests</Code> metric with a <Code>path</Code> label param —
note that <Code>[param]</Code> comparisons only work against a <em>declared metric's</em> labels, not a
bare param declared on its own, and values are unquoted (no surrounding <Code>"..."</Code>):{' '}
<Code>{'requests[path]==/checkout and regex("5[0-9]{2}")'}</Code>
</li>
<li>
Everything before noon on a specific date: <Code>{'timestamp<2024-06-15T12:00:00Z'}</Code>
</li>
</ul>
</div> </div>
), ),
}, },
@@ -141,6 +169,15 @@ const SECTIONS: DocSection[] = [
<Code>requests[duration]&gt;100</Code>), declare it as a param on a metric instead — see the Metrics <Code>requests[duration]&gt;100</Code>), declare it as a param on a metric instead — see the Metrics
section below. section below.
</p> </p>
<p className="font-semibold text-text">Worked example</p>
<p>
Given lines like <Code>{'request path=/checkout method=GET duration=182'}</Code>, declaring a param named{' '}
<Code>duration</Code> with regex <Code>{String.raw`duration=(?P<duration>\d+)`}</Code>, capture group{' '}
<Code>duration</Code>, and type <Code>number</Code> makes <Code>duration</Code> show up as a column in the
log table for every line that matched, and comparable in the filter bar once it's also declared as part of
a metric (see <em>Filtering</em> above — bare params aren't queryable by name on their own, only metric
labels/values are).
</p>
</div> </div>
), ),
}, },
@@ -235,6 +272,26 @@ const SECTIONS: DocSection[] = [
latest reading. latest reading.
</li> </li>
</ul> </ul>
<p className="font-semibold text-text">Worked example</p>
<p>
To count requests per path and track their latency from lines like{' '}
<Code>{'request path=/checkout method=GET duration=182'}</Code>, declare a metric named{' '}
<Code>requests</Code> with message-match regex{' '}
<Code>{String.raw`request path=(?P<path>\S+) method=(?P<method>\S+) duration=(?P<duration>\d+)`}</Code>{' '}
and two params: <Code>path</Code> (capture group <Code>path</Code>, kind <Code>string</Code>, role{' '}
<Code>label</Code>) and <Code>duration</Code> (capture group <Code>duration</Code>, kind{' '}
<Code>number</Code>, role <Code>value</Code> — at most one param can be <Code>value</Code>). Selecting{' '}
<Code>requests</Code> in the panel then shows one row per distinct <Code>path</Code> with its occurrence
count and a trend sparkline. The same declaration also unlocks{' '}
<Code>{'histogram(requests[50])'}</Code> (duration distribution in 50ms-wide buckets) and{' '}
<Code>gauge(requests)</Code> (each path's most recent duration) as dashboard panel queries.
</p>
<p>
A param of kind <Code>duration</Code> marked <Code>value</Code> is a special case: rather than parsing the
capture group as a number, its value is derived automatically as the elapsed time since the previous
matching event with the same labels — useful for measuring the gap between repeated events (e.g. time
between retries) without the log line itself reporting a duration.
</p>
</div> </div>
), ),
}, },
@@ -314,6 +371,37 @@ const SECTIONS: DocSection[] = [
(default <Code>https://ntfy.sh</Code>). Configure the sink and send a test notification from the Settings (default <Code>https://ntfy.sh</Code>). Configure the sink and send a test notification from the Settings
panel — the Alerts panel only defines rule conditions. panel — the Alerts panel only defines rule conditions.
</p> </p>
<p className="font-semibold text-text">Worked example: build a "requests" dashboard</p>
<p>
Assuming a <Code>requests</Code> metric declared as in the <em>Metrics</em> example above (a <Code>path</Code>{' '}
label and a <Code>duration</Code> value param), a useful dashboard is three panels:
</p>
<ul className="list-disc pl-5">
<li>
<span className="font-semibold text-text">Line</span>, query <Code>requests</Code> — one line per path,
showing every request occurrence over time.
</li>
<li>
<span className="font-semibold text-text">Bar</span>, query <Code>gauge(requests)</Code> — each path's
most recent request duration side by side, a quick "who's slow right now" snapshot.
</li>
<li>
<span className="font-semibold text-text">Pie</span>, query{' '}
<Code>{'requests{path="/checkout"}'}</Code>{' '}
— label matchers filter a query down to one series; here, just the checkout path's share of its own
occurrences over the range (mostly useful once more matchers are added to compare paths against each
other).
</li>
</ul>
<p>
Other query shapes worth knowing: <Code>{'sum(requests)'}</Code> collapses every path into one combined
count-over-time series, and <Code>{'rate(requests[5m])'}</Code> gives each path's occurrences-per-second
over a trailing 5-minute window — both work with any of the three chart types, since chart type is just how
the returned series get drawn, not part of the query itself. Add a panel, type a query, and it previews
live; a malformed query (e.g. mismatched braces, or <Code>histogram()</Code> missing its{' '}
<Code>[width]</Code>) shows its error inline in the panel instead of breaking the dashboard — a syntactically
valid query for a metric that doesn't exist just renders "No data in range."
</p>
</div> </div>
), ),
}, },
@@ -327,6 +415,13 @@ const SECTIONS: DocSection[] = [
max number of logs kept in memory (blank means unlimited), and imported/linked CSS. Changes apply max number of logs kept in memory (blank means unlimited), and imported/linked CSS. Changes apply
immediately and persist to the settings file on disk. There is no save button. immediately and persist to the settings file on disk. There is no save button.
</p> </p>
<p className="font-semibold text-text">Worked example</p>
<p>
Ingesting a very chatty process and only care about the last hour of activity: set{' '}
<em>max logs in memory</em> to something like <Code>50000</Code> so older lines are dropped as new ones
arrive rather than growing memory use unbounded for the life of the process. Leave it blank to keep
everything ingested — the default, and fine for shorter-lived commands.
</p>
</div> </div>
), ),
}, },