Merge ticket/008-docs-worked-examples

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:58 +01:00
co-authored by Claude Sonnet 5
2 changed files with 95 additions and 8 deletions
-8
View File
@@ -1,8 +0,0 @@
# Add worked examples to docs pages
The docs pages should have examples of how to achieve something. Particularly the dashboards and panels section. But also metrics and params and the others.
## Acceptance criteria
- Dashboards and panels docs include concrete, worked examples showing how to accomplish common tasks.
- Metrics and params docs (and other doc sections) get similar example treatment.
- Examples are accurate against current functionality (verified against the actual behavior, not just written speculatively).
+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
running engine's declared params/metrics immediately, with no restart.
</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>
),
},
@@ -109,6 +117,26 @@ const SECTIONS: DocSection[] = [
<p>
The filter string is evaluated on the server. A parse error shows as inline text under the filter bar.
</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>
),
},
@@ -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
section below.
</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>
),
},
@@ -235,6 +272,26 @@ const SECTIONS: DocSection[] = [
latest reading.
</li>
</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>
),
},
@@ -314,6 +371,37 @@ const SECTIONS: DocSection[] = [
(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.
</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>
),
},
@@ -327,6 +415,13 @@ const SECTIONS: DocSection[] = [
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.
</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>
),
},