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. Where graphs appear

Two places. The panels view (#/panels) renders every declared ui.panels entry as an SVG 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.