> Section: [8. Observing it](https://jaque.sh/docs/ui/dashboard.md)
> Next: observability/metrics
> Index: https://jaque.sh/llms.txt


A sink (see [Sinks and perfdata](https://jaque.sh/docs/observability/sinks-and-perfdata.md))
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.

```cue
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

```cue
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

```cue
query: {selector: "env=prod, team=redes", metric: "load", agg: "avg"}
```

A [label selector](https://jaque.sh/docs/config/labels-and-views.md), 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

```cue
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

```cue
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](https://jaque.sh/docs/observability/sinks-and-perfdata.md)
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.
