A sink (see Sinks and perfdata)
writes perfdata somewhere. A datasource reads it back. The two are
declared separately because writing and reading are separate concerns: a
deployment can run sinks with no datasources declared, in which case the
dashboard serves no graphs at all, or declare a datasource pointed at a
backend nothing in sinks: writes to.
jaque never hands the browser a datasource's URL or credentials. The
dashboard asks jaque's query service for a panel or a metric by name, and
jaque runs the query against the configured backend itself. Whatever a
datasource's url carries stays server-side.
1. The datasources: block
Each datasource is a named entry under datasources:, discriminated by
type the same way sinks: is.
datasources: {
ch: {
type: "clickhouse"
url: "clickhouse://ch:9000/jaque"
table: "perfdata"
}
prom: {
type: "prometheus"
url: "http://prometheus:9090"
}
}
| Type | Fields | Notes |
|---|---|---|
clickhouse |
url, table |
table defaults to perfdata; queried for both the semantic and raw SQL panel forms below |
prometheus |
url |
queried through /api/v1/query_range; only the raw PromQL panel form runs against it |
A panel or the object detail view can only ask a clickhouse datasource
for a semantic or raw SQL query, and only a prometheus datasource for a
PromQL query. Naming the wrong type is a config error caught when jaque
loads the config, not when the panel is first viewed.
2. The ui: block
ui: {
datasource: "ch"
panels: {
load_avg: {
ds: "ch"
query: {selector: "env=prod", metric: "load", agg: "avg"}
}
}
}
ui.datasource names the datasource the object detail view's automatic
per-object graphs use. It is optional: leaving it unset falls back to the
single declared datasource, or, with more than one declared, the
lexicographically first by name. Either way that datasource must be
clickhouse -- the object detail graphs use the semantic query form,
which needs an object ID to filter by, and PromQL has no such axis
(ADR-018).
ui.panels declares the named panels the panels view renders. Each panel
has:
| Field | Default | Meaning |
|---|---|---|
ds |
(required) | the datasource name this panel queries |
query |
(required) | one of the three forms below |
range |
6h |
how far back the panel plots by default |
type |
line |
line or area |
3. The three query forms
A panel's query is exactly one of semantic, raw SQL, or raw PromQL.
Semantic -- no query language
query: {selector: "env=prod, team=redes", metric: "load", agg: "avg"}
A label selector, a metric name (the
perfdata point's label, e.g. load or rta), and an aggregation --
avg, max or min. jaque resolves the selector to a set of object
IDs and groups by object, one series per matching object. Requires a
clickhouse datasource. This is the form to reach for by default: it
needs no SQL and no PromQL, and it is the only form the object detail
view's automatic graphs use.
Raw SQL
query: {sql: "SELECT service AS series, bucket, avg(value) AS value FROM (SELECT service, toStartOfInterval(ts, INTERVAL $__interval SECOND) AS bucket, value FROM perfdata WHERE ts >= toDateTime($__from) AND ts <= toDateTime($__to)) GROUP BY series, bucket ORDER BY series, bucket"}
Runs verbatim against the panel's ClickHouse datasource. See section 4 for the column contract and macros.
Raw PromQL
query: {promql: "rate(jaque_perfdata_bytes{host=\"gw\"}[5m])"}
Runs verbatim against the panel's Prometheus datasource through
query_range. Requires a prometheus datasource. jaque does not parse
or evaluate PromQL itself -- it forwards the query text and reshapes
whatever Prometheus returns into the same series format every panel uses.
4. The raw SQL column contract and macros
A raw SQL query must return exactly three columns, in this order:
| Column | Type | Meaning |
|---|---|---|
series |
String |
the series name this row belongs to |
bucket |
DateTime or DateTime64 |
the sample's timestamp |
value |
Float64 |
the sample's value |
A query returning different columns, a different order, or different types is rejected when it runs, with an error naming what was expected.
Three macros are substituted into the query text before it runs:
| Macro | Value |
|---|---|
$__from |
the range's start, unix seconds |
$__to |
the range's end, unix seconds |
$__interval |
the query's step, in whole seconds |
The step is the queried range divided into 200 buckets, rounded up to a whole number of seconds, with a floor of 10 seconds -- a 6h panel (the default range) steps at 108s; nothing ever queries at a step finer than 10s regardless of how short the range is.
5. Counter rate
A metric point's kind -- gauge or counter -- comes from how it was
recorded (see Sinks and perfdata
section 3); a semantic query knows this per series and handles each
according to its kind. A gauge series is aggregated by the requested
agg and plotted as-is. A counter series is always aggregated by max
within each bucket regardless of the requested agg, then converted to a
per-second rate in jaque before it reaches the dashboard: each plotted
point is (value[i] - value[i-1]) / step. The first point of a counter
series has no predecessor and is dropped, and any point whose delta would
be negative -- the counter reset -- is dropped too, rather than plotted
as a spike or a trough.
Raw SQL and raw PromQL queries get no such handling: a raw query returns
whatever it computes, so a raw SQL query plotting a counter needs its own
rate expression, and a raw PromQL query reaches for rate() or
increase() the way any PromQL query would.
6. Units
Every point a semantic query returns is normalized to a scale-free unit
before it is stored (see Sinks and perfdata
section 3) -- a plugin reporting 6ms is stored as 0.006 seconds, and
that unit travels with the series to the dashboard. The axis and the
legend both format each series' values by that unit rather than showing
the raw stored number: a duration reads as 6ms or 82d, a byte count
as 1.4MiB, a fraction as 65.9%. The unit comes from the perfdata the
sink normalized, never from the panel's own declaration -- a panel does
not say what its values mean, the recorded points already know.
Raw SQL and raw PromQL panels carry no unit: neither query form's three-column contract has anywhere to put one, so their values render as plain numbers. And a series whose samples disagree on the unit -- the same metric recorded two different ways -- is treated as having none too, rather than guessing: a single scale is the whole point of a unit, and a wrong guess would be worse than a plain number.
7. Where graphs appear
Two places. The panels view (#/panels) renders every declared
ui.panels entry as a chart, line or area per its type, over
its default range. The object detail view
(#/object/<host>, #/object/<host>/<service>) renders one graph per
metric the object has reported perfdata for, using the semantic form
against ui.datasource -- no panel needs to be declared for these, they
follow from the perfdata jaque has already recorded for that object.
Neither place needs the datasources:/ui: blocks to exist for the rest
of the dashboard to work; a deployment with no datasource declared simply
shows neither.
Every graph is interactive. Hovering over the plot reads every series' value at that instant. Dragging horizontally across the plot zooms into that window, and double-clicking restores the full range. Clicking a legend entry hides or shows that series.
A time picker sits above the panels grid: one button showing the
active window ("Last 1 hour", or the typed start and end). Clicking
it opens a dropdown. One column lists quick ranges -- Last 5 minutes
up to Last 1 year -- and clicking one applies it at once; default
returns every panel to its own configured range. The other column
holds the absolute range: two text fields, start and end, a calendar
that fills them by picking two days (time included), and an apply
button. Each field takes either an absolute date-time
(2026-08-20 14:00) or an expression relative to now (now-7d),
including snapped forms like now-7d/d that round to a whole day
boundary -- so a link built from a snapped expression means the same
whole days again tomorrow. Setting both fields overrides every
panel's window at once and re-queries them all. The link carries
whatever was typed, not the resolved time. Escape or clicking
outside closes the dropdown without applying.
Each panel card also has an expand button. Clicking it grows that one panel to the full width of the view and its chart to a taller height, in place of the grid. The expanded panel is part of the URL, so it's also part of any link you send -- opening a shared link lands directly on that panel, already expanded. The same button, now labeled to collapse, returns to the grid, and pressing Escape does the same.