Import and check
This page answers how to get the five dashboards into a Grafana, what the datasource needs before
they can return anything, and what mikroscope dashboards check tells you once they are there. The
short version of the last part: check proves that every panel’s query returns rows through
Grafana’s own query API. It does not prove that a reader can read the result.
The InfluxDB 3 datasource
Section titled “The InfluxDB 3 datasource”The database comes first: influxdb3 create database mikroscope on the InfluxDB 3 node, with a
token that can read and write it — the same database and token the sink writes to, on
InfluxDB 3.
The datasource is type influxdb, version: SQL, dbName: mikroscope, with both secure fields
set:
httpHeaderValue1=Bearer <token>(the HTTP path)token=<token>(the FlightSQL path)
Without the second, panels fail with flightsql: Unauthenticated (Grafana 12.3.2, 2026-09-12).
Prometheus: two scrape jobs
Section titled “Prometheus: two scrape jobs”The Prometheus dashboard expects two scrape jobs. The first scrapes the collector
(mikroscope forward --prom :9124), which carries every family the agent has, recomputed from the
samples it received, plus the collector’s own derived and detection families. The second scrapes the
agent directly and keeps only the families only the sampler can produce: its tick timing
histograms, the trigger and capture counters, and slipped ticks.
- job_name: "mikroscope" scrape_interval: 5s static_configs: [{ targets: ["<collector host>:9124"] }]- job_name: "mikroscope-agent" scrape_interval: 5s static_configs: [{ targets: ["172.30.10.2:9123"] }] metric_relabel_configs: - source_labels: [__name__] regex: "mikroscope_(tick_.*|trigger_.*|capture.*|captures_held|slipped_total)" action: keepScraping the agent without the keep list would double every counter the collector also exposes.
172.30.10.2:9123 is the agent’s address on the default install; how a Prometheus host reaches it
is on Reaching the agent.
The PostgreSQL datasource
Section titled “The PostgreSQL datasource”The SQL sink writes a script, not rows: forward --sql out.sql and then psql -f out.sql, or
--sql - | psql. So the database has the schema and the data only after that script has been
applied — a datasource pointed at an empty database answers every panel with “relation does not
exist”.
The datasource is Grafana’s grafana-postgresql-datasource, with the database and the user the
script was loaded as. sslmode is yours to choose; postgresVersion only decides which syntax the
plugin may emit, and every query in this dashboard is plain SQL.
The panels are the InfluxDB ones, rewritten: the bucket macro, the percentiles, the casts and the
column names the SQL sink had to change because user, from and to are reserved words. Ten
queries are not rewritten and say so — mikroscope_buddy and the RouterOS interface counters are
wide in InfluxDB and long in the SQL schema, and a pivot is a different question.
The Graphite datasource
Section titled “The Graphite datasource”Graphite has no labels: every dimension is a path node, so a query IS a path — and the first two
nodes are yours. --graphite-prefix (default mikroscope) and --host-tag are therefore
dashboard variables, read from Graphite’s own metric tree, and the dashboard asks for them at
the top rather than being hard-coded to whoever generated it.
The datasource is type graphite; nothing else is needed. From the CLI, check cannot read a
browser’s variable picker, so it takes them:
mikroscope dashboards check --store graphite --datasource-uid <uid> \ --var prefix=mikroscope --var host=rb5009The Elasticsearch datasource
Section titled “The Elasticsearch datasource”Type elasticsearch, with the index the --elastic-index you forwarded with produces and
@timestamp as the time field.
This dashboard is the smallest of the five, and the reason is in the documents rather than in the
queries: the sink writes a sample’s per-core and per-device readings as arrays — cpu is an
array of four objects — and a dynamically mapped array is a multi-valued field with no
correspondence between its members. avg(cpu.busy_ratio) is the mean over the cores, which is a
real number; “core 2’s busy ratio” is not expressible at all without a nested mapping the sink does
not declare. So the Elasticsearch panels are the scalar aggregates, and the per-core ones are
absent rather than wrong.
Importing by hand
Section titled “Importing by hand”Grafana → Dashboards → New → Import, upload dashboards/ or
mikroscope-prometheus., and pick the datasource when Grafana asks for DS_MIKROSCOPE.
A file uploaded this way carries the compiled defaults: the five panels the reference device
cannot produce sit in the not-available row, and every other panel ships with its query, whether
your store holds its measurement or not. On InfluxDB a panel whose table or column is missing then
shows InfluxDB 3’s planning error, as a red badge, when its section is opened. import from the CLI
avoids that.
Importing from the CLI
Section titled “Importing from the CLI”export GRAFANA_URL=http://grafana:3000 GRAFANA_TOKEN=…mikroscope dashboards import --store influxdb --datasource-uid <uid>mikroscope dashboards check --store influxdb --datasource-uid <uid> --window 15m| Flag | Default | Used by | What it does |
|---|---|---|---|
--store |
influxdb |
import, check | influxdb or prometheus; also the datasource plugin id sent to Grafana |
--grafana |
$GRAFANA_URL |
import, check | Grafana’s base URL |
--datasource-uid |
none, required | import, check | the datasource DS_MIKROSCOPE is bound to |
--no-probe |
off | import, check | skip asking the datasource what it holds; use the compiled defaults |
--window |
15m |
check | length of the query window |
--end |
now | check | the window’s right edge, RFC 3339 |
--out |
dashboards |
gen | the directory gen writes the four files into |
Scroll sideways to see every column
The token is read only from GRAFANA_TOKEN; there is no flag for it. It is a Grafana service
account token with permission to write dashboards. The datasource UID is the last path segment of
the datasource’s settings URL in Grafana, /. Without a Grafana
URL, a token and a datasource UID, both commands stop with import/check need --grafana, GRAFANA_TOKEN and --datasource-uid.
import posts the dashboard to Grafana’s /api/dashboards/import with the datasource input
resolved to your UID, overwrite on, into the General folder (folderId 0). The dashboard’s uid
is fixed, so importing again replaces the same dashboard at the same URL. It prints that URL.
The probe
Section titled “The probe”Before generating, import and check ask the datasource which of mikroscope’s measurements it
holds, through Grafana’s /api/ds/query:
SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = 'iox'Columns and not only tables: InfluxDB 3 refuses a query naming a missing column at planning time exactly as it refuses a missing table, and a store written before a field existed has the table and not the field. A panel that reads a field added later declares it, and the probe checks it.
group by(__name__) ({__name__=~"mikroscope_.+"})An instant query that returns one series per metric name that exists, with no samples to transfer.
A histogram counts as present when its _bucket, _count or _sum series is.
It prints datasource holds N measurements; on InfluxDB, N counts tables plus table.column pairs,
so it is larger than the number of measurements. It then generates against the answer:
- A panel whose measurements and required fields are all present ships in its own section with its query — including a panel the reference device could not produce.
- A panel with anything missing moves into the collapsed “Not available on this device” row with
its query removed. It runs nothing, so it cannot paint a red
table … not foundbadge; its no-value text names what this store does not hold. - A probe that fails — an error from Grafana, or an answer with no
mikroscope_names in it — is a warning, not an error.importandcheckprintwarning: could not ask <store> which measurements it holds, with the reason, and carry on with the compiled defaults, so you are told which dashboard you got.
--no-probe skips the question and uses the compiled defaults, which is also what plain gen does,
since it has no datasource to ask.
What check verifies
Section titled “What check verifies”check generates the dashboard exactly as import would — probe included — and then, for every
panel, including every panel nested inside a collapsed row, sends each of its queries through
Grafana’s /api/ds/query against your datasource over the window, and counts the rows that come
back. The request carries the step Grafana would compute for that panel: the window divided by 900
data points, raised to the panel’s own minimum interval where it has one. Without that step, an
increase(x[$__interval]) target returns an empty frame, because a step below the scrape interval
leaves fewer than two points in the range.
It prints one line per panel and a verdict:
ok <panel title> rows=<n> frames=<n> none <panel title> rows=<n> frames=<n> <error, if any> FAIL <panel title> rows=<n> frames=<n> <error, if any>every panel returns data (<k> known-empty tolerated)- ok: the panel returned at least one row and no query reported an error.
- none: the panel is marked known-empty and did not qualify as ok. Two kinds of panel carry the mark: those whose emptiness is the healthy state (for example the detection and trigger panels, the two port-event panels, the gaps table, the opt-in conntrack poll, the sub-sample burst panel and the worst kernel-log severity timeline), and those in the not-available row. A known-empty panel is tolerated whether it returned no rows or an error.
- FAIL: anything else — no rows, or an error on any of the panel’s queries even if another returned rows.
With one or more failures check exits non-zero with N panel(s) return no data (K known-empty tolerated). A dashboard is not done until every panel that is not known-empty returns rows.
What check does not verify
Section titled “What check does not verify”What else is outside its reach, from the code:
- The dashboard stored in Grafana.
checkregenerates the dashboard locally and runs those queries. It does not read back whatimportstored, so a dashboard edited in Grafana’s UI is not what it checks. - Whether a number is right. One row is a pass. Wrong arithmetic that returns rows passes.
- Annotations. Only panels are walked; the detections and triggers annotation queries are not run.
- Alert rules. The provisioning files
genwrites are not loaded or evaluated. - What the browser does to a query. Some variables are substituted in the browser, not by the
server
checktalks to. On 2026-09-14 the InfluxDB datasource escaped$__interval_msin five panels, in the browser, into SQL InfluxDB 3 could not parse; the rendered dashboard showed it. - The real panel width.
checkpretends every panel is 900 data points wide, the value the browser sent for this dashboard’s graphs on 2026-09-12. A narrower panel gets a wider bin.
Checking against a finished capture
Section titled “Checking against a finished capture”--window is the length of the query window and --end moves its right edge, so the panels can be
checked against a capture that has already finished rather than against an idle now:
mikroscope dashboards check --store influxdb --datasource-uid <uid> \ --window 1h --end 2026-09-13T08:30:00ZA panel answers differently over a window with data than over one without, and a check is only as good as the window it is pointed at.
What has been verified
Section titled “What has been verified”2026-09-16, on the owner’s Grafana 13.2.1, against the reference deployment: the agent on the
reference RB5009 (RouterOS 7.24.2, privileged, the default triggers);
forward --prom :9124 --influx … --interfaces bridge,ether1,PPPoE_ for
30 minutes into an isolated InfluxDB 3 Core; a Prometheus 3.14 scraping the collector every 5 s,
plus the agent directly for the families the collector cannot recompute.
| Store | Window | Panels | Failing | Known-empty tolerated |
|---|---|---|---|---|
| InfluxDB 3 | 30 minutes | 171 | 0 | 10 (the two port-event panels, the opt-in conntrack poll, the two trigger panels, PSI, the four idle block devices) |
| Prometheus | 30 minutes | 133 | 0 | 9 (the two port-event panels, the two conntrack API panels, PSI, the four block devices) |
Scroll sideways to see every column
The two port-event panels’ SQL was validated the same day against a synthetic table in the same
InfluxDB 3, because the live store holds no kind column until the first port record classified by
kind is written to it.
The headless row-by-row walk of both dashboards in Chromium at 1600x1000 — 0 error badges, 0 “No data” — is from 2026-09-15 and covers 168 InfluxDB panels and 130 Prometheus ones. It has not been repeated, so no render pass is claimed for the three panels it did not cover: the two port-event panels and “What each interface is: type, role, bridge and label”.
2026-09-12, on the owner’s Grafana 12.3.2. The InfluxDB dashboard against an isolated InfluxDB 3
Core fed by mikroscope forward from an RB5009: every panel returned rows, 158–316 per panel over
10 minutes. The Prometheus dashboard against a Prometheus 3.14 scraping the collector every 5 s:
every panel returned rows, 228–2 052 over 5 minutes.