From 41d2629770e8c1a211d30b00ac6f539e04da64d4 Mon Sep 17 00:00:00 2001 From: Boris Tyshkevich Date: Wed, 22 Jul 2026 18:46:21 +0200 Subject: [PATCH] Consolidate examples into flagship dashboards --- CHANGELOG.md | 8 + README.md | 41 +- docs/CLICKHOUSE-OPERATIONS-DEMO.md | 27 + docs/ONTIME-CHART-DEMO.md | 80 +- docs/SHOP-ANALYTICS-DEMO.md | 23 + docs/SYSTEM-EXPLORER-DEMO.md | 90 - docs/local-app.html | 2 +- ...hanced.json => clickhouse-operations.json} | 2215 ++++++++--------- examples/kpi-panel.json | 85 - examples/mjs/README.md | 14 +- examples/mjs/build-iceberg-dashboards.mjs | 2 +- examples/mjs/build-ontime-charts.mjs | 225 +- examples/mjs/build-system-explorer-charts.mjs | 257 -- examples/mjs/example-bundle.mjs | 47 +- examples/mjs/normalize-examples.mjs | 43 +- examples/ontime-charts.json | 280 ++- examples/query-log-explorer.json | 305 --- examples/shop-charts.json | 324 ++- examples/shop-demo.sql | 6 +- examples/system-explorer-charts.json | 404 --- examples/text-log-panel.json | 87 - tests/unit/spec-examples.test.js | 64 +- 22 files changed, 1758 insertions(+), 2871 deletions(-) create mode 100644 docs/CLICKHOUSE-OPERATIONS-DEMO.md create mode 100644 docs/SHOP-ANALYTICS-DEMO.md delete mode 100644 docs/SYSTEM-EXPLORER-DEMO.md rename examples/{grafana-clickhouse-ops-enhanced.json => clickhouse-operations.json} (86%) delete mode 100644 examples/kpi-panel.json delete mode 100644 examples/mjs/build-system-explorer-charts.mjs delete mode 100644 examples/query-log-explorer.json delete mode 100644 examples/system-explorer-charts.json delete mode 100644 examples/text-log-panel.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 21afee7c..0ab5a0ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ auto-generated per-PR notes; this file is the curated, human-readable history. ## [Unreleased] +### Changed +- Consolidated the non-Iceberg examples into three responsive flagship + dashboards: On-time flights, Shop analytics, and ClickHouse Operations. + Each uses an authored `grafana-grid@1` layout with a complete `flow@1` + fallback, shared filters, and focused visible tiles; useful secondary + analyses remain untiled Library entries. Removed the superseded KPI, logs, + query-log, System Explorer, and earlier Grafana-port fixtures and generator. + ### Added - **Cross-tab workspace consistency: refresh before write and stale-tab invalidation** (#343). Workbench and editable-Dashboard tabs editing the same diff --git a/README.md b/README.md index 4669066f..9f797c02 100644 --- a/README.md +++ b/README.md @@ -23,15 +23,20 @@ held at **100% test coverage**. See the [**feature tour, deployment guide and screenshots**](https://docs.altinity.com/altinity-sql-browser/) on the project site. Try it live on the Antalya demo cluster: **https://antalya.demo.altinity.cloud/sql**. -The [**ontime chart demo**](docs/ONTIME-CHART-DEMO.md) is a ready-made library of 10 -queries (load [`examples/ontime-charts.json`](examples/ontime-charts.json) via -**File ▾ → Import queries**) that walks through every chart type and feature against the public -`ontime` flight dataset. The [**system explorer demo**](docs/SYSTEM-EXPLORER-DEMO.md) -is a 14-query library (load [`examples/system-explorer-charts.json`](examples/system-explorer-charts.json) -via **File ▾ → Import queries**) that introspects ClickHouse's own `system` database — -running queries, merges/replication health, and historical query/part/error -activity — with a shared From/To filter driving every time-ranged Dashboard tile -at once. +Three flagship bundles cover the main workflows without a pile of one-feature +fixtures: + +- [**On-time flights**](docs/ONTIME-CHART-DEMO.md) — seven analytical tiles, + a KPI band, a shared 2023 date range, and carrier/airport multiselects over + the public `ontime` dataset. +- [**Shop analytics**](docs/SHOP-ANALYTICS-DEMO.md) — seven business tiles over + the schema, sample data, materialized views, and dictionary created by + [`examples/shop-demo.sql`](examples/shop-demo.sql). +- [**ClickHouse Operations**](docs/CLICKHOUSE-OPERATIONS-DEMO.md) — sixteen + operator-first tiles for live health, resources, background work, and + investigation, with the remaining operational queries kept in the Library. + +Load the corresponding portable bundle from `examples/` with **File ▾ → Open…**. The [**Iceberg catalog explorer**](docs/ICEBERG-CATALOG-EXPLORER-DEMO.md) is a distributable installer + two dashboards for Iceberg data-lake catalogs: [`examples/iceberg-install.json`](examples/iceberg-install.json) generates the @@ -92,9 +97,9 @@ ordinary `.kpi-panel` grid, while a **favorited, explicitly-KPI-typed** Dashboar query instead joins a full-width **KPI band** — a flat, wrapping card stream with no per-favorite name, description, or statistics footer, spanning every flow Dashboard layout (Report/2/3 columns). Consecutive explicit KPI -favorites merge into one shared band. The complete -[`kpi-panel.json`](examples/kpi-panel.json) Library example can be opened from -**File ▾ → Import queries** to see both. +favorites merge into one shared band. The On-time, Shop, and Operations +flagship dashboards all include a production +KPI band alongside charts, tables, and logs. When constructing a named tuple from expressions, either enable alias-derived member names for the query: @@ -492,14 +497,10 @@ nested option values, limits each helper to 1,000 options, and falls back to the ordinary parameter field when a source, consumer type, or provider conflicts. Filter sources run and reconcile saved values before any Panel query starts. -The complete [`query-log-explorer.json`](examples/query-log-explorer.json) -Library example (load via **File ▾ → Import queries**) demonstrates every filter -variant against `system.query_log` on any cluster: three Filter sources, one -per option shape (`Array(Tuple(value, label))`, `Map(String, String)`, plain -`Array(T)`), alongside plain auto-detected numeric/text fields — a KPI panel, -four analytical Panels adapted from the Altinity KB's ["Handy queries for -system.query_log"](https://kb.altinity.com/altinity-kb-useful-queries/query_log/), -a Logs panel, and a Text panel explaining the demo. +The flagship bundles demonstrate the same contracts in context: readable +`Array(Tuple(value, label))` airport options in On-time, targeted country and +category filters in Shop, and inferred `Array(T)` multiselects for operational +query-log dimensions in ClickHouse Operations. ```sql SELECT diff --git a/docs/CLICKHOUSE-OPERATIONS-DEMO.md b/docs/CLICKHOUSE-OPERATIONS-DEMO.md new file mode 100644 index 00000000..360807cd --- /dev/null +++ b/docs/CLICKHOUSE-OPERATIONS-DEMO.md @@ -0,0 +1,27 @@ +# ClickHouse Operations dashboard + +[`examples/clickhouse-operations.json`](../examples/clickhouse-operations.json) +is an operator-first dashboard imported and curated from the ClickHouse +Operations bundle. Open it with **File ▾ → Open…** against a cluster where the +signed-in user can inspect the relevant `system` tables. + +Its sixteen visible tiles follow an investigation path: + +- overview: live KPIs, running queries, query rate, active connections; +- resources: CPU, memory, I/O utilization, network traffic; +- background work: merges, mutations, replication checks; +- investigation: errors, largest tables, query-hash metric, query details, and + recent server logs. + +The active time range defaults to `-1h` through `now`. `user`, `query_kind`, +`exception_code`, and `query_hash` are inferred searchable multiselects; their +consumers consistently use `Array(T)` parameters with `has(...)`. `metric` and +`is_initial_query` remain single-select controls, while time and log search are +scalar. The other downloaded operational queries remain untiled in the Library. + +Typical grants include `SELECT` on `system.metric_log`, +`system.asynchronous_metric_log`, `system.query_log`, `system.part_log`, +`system.text_log`, `system.parts`, `system.merges`, `system.mutations`, and +replication-related system tables. Exact availability depends on server +version and deployment policy; missing grants fail individual tiles without +hiding the rest of the dashboard. diff --git a/docs/ONTIME-CHART-DEMO.md b/docs/ONTIME-CHART-DEMO.md index da06463e..fe44cb59 100644 --- a/docs/ONTIME-CHART-DEMO.md +++ b/docs/ONTIME-CHART-DEMO.md @@ -1,65 +1,27 @@ -# Chart demo — the `ontime` flight dataset +# On-time flights dashboard -A ready-made **Library** of 10 analytical queries that show off every chart type and -feature in the Altinity® SQL Browser, running against the public US flight-history -dataset (`ontime`, ~230M rows, 1987–2025) on the Antalya demo cluster. +[`examples/ontime-charts.json`](../examples/ontime-charts.json) is the flagship +analytical dashboard for the public `ontime` flight dataset. Open it on the +[Antalya demo](https://antalya.demo.altinity.cloud/sql) with **File ▾ → Open…**. -- **Live demo:** **https://antalya.demo.altinity.cloud/sql** -- **The library file:** [`examples/ontime-charts.json`](../examples/ontime-charts.json) - ([raw download](https://raw.githubusercontent.com/Altinity/altinity-sql-browser/main/examples/ontime-charts.json)) -- **Reproduce it:** [`examples/mjs/build-ontime-charts.mjs`](../examples/mjs/build-ontime-charts.mjs) - regenerates the JSON (it derives each chart's schema key live with - `clickhouse-client --connection antalya`). +The authored grid contains seven visible tiles: Flight KPIs, Daily flights, +On-time rate, Busiest origins, Monthly carrier volume, Delay causes by carrier, +and Cancellation reasons. Additional chart analyses remain available as +untiled Library entries. -## Load it (≈30 seconds) +The active time range defaults to `2023-01-01` through `2023-12-31`. Carrier +and Origin airport start inactive and become searchable multiselects because +every targeted query declares `Array(String)` and uses `has(...)`. Airport +codes are the bound values; readable airport names are the labels. -1. Open **https://antalya.demo.altinity.cloud/sql** and sign in (**Continue with Google**, - or use the credentials box). -2. Download [`ontime-charts.json`](https://raw.githubusercontent.com/Altinity/altinity-sql-browser/main/examples/ontime-charts.json) - (right-click → Save link as…). -3. In the header, click **File ▾ → Open…** and pick the file. The library is renamed - **ontime-charts** and fills with 10 saved queries (confirm the replace if you already - had queries saved). -4. Click any query in the **Queries** panel — it runs and opens straight into its chart. - Switch **Table / JSON / Chart** at the top of the results, or change the **Type / X / Y / - Series** dropdowns to re-encode any chart live. +The dashboard reads `ontime.fact_ontime` and joins +`ontime.dim_airports` for origin names. To refresh panel schema keys against +the configured connection, run: -## What each query demonstrates +```bash +node examples/mjs/build-ontime-charts.mjs +``` -| # | Query | Chart | Feature | -|---|-------|-------|---------| -| 1 | Busiest origin airports — 2023 | Bar (horizontal) | categorical axis; joined to `dim_airports` for readable names; hover any bar (long or short) for its exact value | -| 2 | Flights by month — 2023 | Column | numeric `month` auto-detected as an ordinal axis; K/M-humanised value ticks | -| 3 | Daily flights — 2023 | Line | `Date` axis auto-detected as a time series (~365 points) | -| 4 | Daily on-time rate — 2023 | Area | filled time series (a percentage measure) | -| 5 | Cancellation reasons — 2023 | Pie | share of a small category set, with a legend | -| 6 | Monthly flights by carrier — 2023 | Grouped bars | a **Series** column (carrier) splits each month into per-carrier bars | -| 7 | Average delay breakdown by carrier — 2023 | Multi-measure columns | four measures plotted together (“All measures”) | -| 8 | Daily flights since 2022 | Line | result exceeds the chart cap → a **“first 500 of 1.5K rows”** note (the table keeps them all) | -| 9 | Flights by day of week — 2023 | Column | ordinal `dayofweek` axis | -| 10 | Worst average departure delay by airport — 2023 | Bar (horizontal) | a non-count measure (avg minutes), joined for names | - -Each saved query stores its panel configuration in the complete query Spec, so it reopens exactly as designed. (Charts -plot the first 500 rows; the full result is always available in the Table view.) - -## Direct links - -Every query is also reachable as a single shareable link — open one and the SQL **and** its -chart configuration are pre-loaded; press **Run**, then the **Chart** tab. (Inside the app, -the **Share** button copies the same kind of link for whatever you're looking at.) - -- **Bar** — [Busiest origin airports — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUXG4gICAgYS5EaXNwbGF5QWlycG9ydE5hbWUgQVMgYWlycG9ydCxcbiAgICBjb3VudCgpIEFTIGZsaWdodHNcbkZST00gb250aW1lLmZhY3Rfb250aW1lIEFTIGZcbklOTkVSIEpPSU4gb250aW1lLmRpbV9haXJwb3J0cyBBUyBhXG4gICAgT04gYS5BaXJwb3J0Q29kZSA9IGYuT3JpZ2luQ29kZSBBTkQgYS5Jc0xhdGVzdCA9IDFcbldIRVJFIGYuWWVhciA9IDIwMjNcbkdST1VQIEJZIGFpcnBvcnRcbk9SREVSIEJZIGZsaWdodHMgREVTQ1xuTElNSVQgMTUiLCJjaGFydCI6eyJjZmciOnsidHlwZSI6ImhiYXIiLCJ4IjowLCJ5IjpbMV0sInNlcmllcyI6bnVsbH0sImtleSI6ImFpcnBvcnQ6U3RyaW5nfGZsaWdodHM6VUludDY0In19) -- **Column** — [Flights by month — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUIE1vbnRoIEFTIG1vbnRoLCBjb3VudCgpIEFTIGZsaWdodHNcbkZST00gb250aW1lLmZhY3Rfb250aW1lXG5XSEVSRSBZZWFyID0gMjAyM1xuR1JPVVAgQlkgbW9udGhcbk9SREVSIEJZIG1vbnRoIiwiY2hhcnQiOnsiY2ZnIjp7InR5cGUiOiJiYXIiLCJ4IjowLCJ5IjpbMV0sInNlcmllcyI6bnVsbH0sImtleSI6Im1vbnRoOlVJbnQ4fGZsaWdodHM6VUludDY0In19) -- **Line** — [Daily flights — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUIEZsaWdodERhdGUgQVMgZGF0ZSwgY291bnQoKSBBUyBmbGlnaHRzXG5GUk9NIG9udGltZS5mYWN0X29udGltZVxuV0hFUkUgWWVhciA9IDIwMjNcbkdST1VQIEJZIGRhdGVcbk9SREVSIEJZIGRhdGUiLCJjaGFydCI6eyJjZmciOnsidHlwZSI6ImxpbmUiLCJ4IjowLCJ5IjpbMV0sInNlcmllcyI6bnVsbH0sImtleSI6ImRhdGU6RGF0ZXxmbGlnaHRzOlVJbnQ2NCJ9fQ==) -- **Area** — [Daily on-time rate — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUXG4gICAgRmxpZ2h0RGF0ZSBBUyBkYXRlLFxuICAgIHJvdW5kKDEwMCAqIGNvdW50SWYoQXJyRGVsMTUgPSAwKSAvIGNvdW50KCksIDEpIEFTIG9uX3RpbWVfcGN0XG5GUk9NIG9udGltZS5mYWN0X29udGltZVxuV0hFUkUgWWVhciA9IDIwMjNcbkdST1VQIEJZIGRhdGVcbk9SREVSIEJZIGRhdGUiLCJjaGFydCI6eyJjZmciOnsidHlwZSI6ImFyZWEiLCJ4IjowLCJ5IjpbMV0sInNlcmllcyI6bnVsbH0sImtleSI6ImRhdGU6RGF0ZXxvbl90aW1lX3BjdDpGbG9hdDY0In19) -- **Pie** — [Cancellation reasons — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUXG4gICAgbXVsdGlJZihDYW5jZWxsYXRpb25Db2RlID0gJ0EnLCAnQ2FycmllcicsXG4gICAgICAgICAgICBDYW5jZWxsYXRpb25Db2RlID0gJ0InLCAnV2VhdGhlcicsXG4gICAgICAgICAgICBDYW5jZWxsYXRpb25Db2RlID0gJ0MnLCAnTmF0aW9uYWwgQWlyIFN5c3RlbScsXG4gICAgICAgICAgICBDYW5jZWxsYXRpb25Db2RlID0gJ0QnLCAnU2VjdXJpdHknLCAnT3RoZXInKSBBUyByZWFzb24sXG4gICAgY291bnQoKSBBUyBjYW5jZWxsYXRpb25zXG5GUk9NIG9udGltZS5mYWN0X29udGltZVxuV0hFUkUgWWVhciA9IDIwMjMgQU5EIENhbmNlbGxlZCA9IDFcbkdST1VQIEJZIHJlYXNvblxuT1JERVIgQlkgY2FuY2VsbGF0aW9ucyBERVNDIiwiY2hhcnQiOnsiY2ZnIjp7InR5cGUiOiJwaWUiLCJ4IjowLCJ5IjpbMV0sInNlcmllcyI6bnVsbH0sImtleSI6InJlYXNvbjpTdHJpbmd8Y2FuY2VsbGF0aW9uczpVSW50NjQifX0=) -- **Grouped columns** — [Monthly flights by carrier — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUXG4gICAgTW9udGggQVMgbW9udGgsXG4gICAgQ2FycmllciBBUyBjYXJyaWVyLFxuICAgIGNvdW50KCkgQVMgZmxpZ2h0c1xuRlJPTSBvbnRpbWUuZmFjdF9vbnRpbWVcbldIRVJFIFllYXIgPSAyMDIzIEFORCBDYXJyaWVyIElOICgnV04nLCAnQUEnLCAnREwnLCAnVUEnKVxuR1JPVVAgQlkgbW9udGgsIGNhcnJpZXJcbk9SREVSIEJZIG1vbnRoLCBjYXJyaWVyIiwiY2hhcnQiOnsiY2ZnIjp7InR5cGUiOiJiYXIiLCJ4IjowLCJ5IjpbMl0sInNlcmllcyI6MX0sImtleSI6Im1vbnRoOlVJbnQ4fGNhcnJpZXI6TG93Q2FyZGluYWxpdHkoU3RyaW5nKXxmbGlnaHRzOlVJbnQ2NCJ9fQ==) -- **Multi-measure columns** — [Average delay breakdown by carrier — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUXG4gICAgQ2FycmllciBBUyBjYXJyaWVyLFxuICAgIHJvdW5kKGF2ZyhDYXJyaWVyRGVsYXkpLCAxKSBBUyBjYXJyaWVyX2RlbGF5LFxuICAgIHJvdW5kKGF2ZyhXZWF0aGVyRGVsYXkpLCAxKSBBUyB3ZWF0aGVyX2RlbGF5LFxuICAgIHJvdW5kKGF2ZyhOQVNEZWxheSksIDEpIEFTIG5hc19kZWxheSxcbiAgICByb3VuZChhdmcoTGF0ZUFpcmNyYWZ0RGVsYXkpLCAxKSBBUyBsYXRlX2FpcmNyYWZ0X2RlbGF5XG5GUk9NIG9udGltZS5mYWN0X29udGltZVxuV0hFUkUgWWVhciA9IDIwMjMgQU5EIEFyckRlbDE1ID0gMVxuR1JPVVAgQlkgY2FycmllclxuT1JERVIgQlkgY2Fycmllcl9kZWxheSBERVNDXG5MSU1JVCAxMiIsImNoYXJ0Ijp7ImNmZyI6eyJ0eXBlIjoiYmFyIiwieCI6MCwieSI6WzEsMiwzLDRdLCJzZXJpZXMiOm51bGx9LCJrZXkiOiJjYXJyaWVyOkxvd0NhcmRpbmFsaXR5KFN0cmluZyl8Y2Fycmllcl9kZWxheTpOdWxsYWJsZShGbG9hdDY0KXx3ZWF0aGVyX2RlbGF5Ok51bGxhYmxlKEZsb2F0NjQpfG5hc19kZWxheTpOdWxsYWJsZShGbG9hdDY0KXxsYXRlX2FpcmNyYWZ0X2RlbGF5Ok51bGxhYmxlKEZsb2F0NjQpIn19) -- **Line (capped)** — [Daily flights since 2022](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUIEZsaWdodERhdGUgQVMgZGF0ZSwgY291bnQoKSBBUyBmbGlnaHRzXG5GUk9NIG9udGltZS5mYWN0X29udGltZVxuV0hFUkUgRmxpZ2h0RGF0ZSA+PSAnMjAyMi0wMS0wMSdcbkdST1VQIEJZIGRhdGVcbk9SREVSIEJZIGRhdGUiLCJjaGFydCI6eyJjZmciOnsidHlwZSI6ImxpbmUiLCJ4IjowLCJ5IjpbMV0sInNlcmllcyI6bnVsbH0sImtleSI6ImRhdGU6RGF0ZXxmbGlnaHRzOlVJbnQ2NCJ9fQ==) -- **Column** — [Flights by day of week — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUIERheU9mV2VlayBBUyBkYXlvZndlZWssIGNvdW50KCkgQVMgZmxpZ2h0c1xuRlJPTSBvbnRpbWUuZmFjdF9vbnRpbWVcbldIRVJFIFllYXIgPSAyMDIzXG5HUk9VUCBCWSBkYXlvZndlZWtcbk9SREVSIEJZIGRheW9md2VlayIsImNoYXJ0Ijp7ImNmZyI6eyJ0eXBlIjoiYmFyIiwieCI6MCwieSI6WzFdLCJzZXJpZXMiOm51bGx9LCJrZXkiOiJkYXlvZndlZWs6VUludDh8ZmxpZ2h0czpVSW50NjQifX0=) -- **Bar** — [Worst average departure delay by airport — 2023](https://antalya.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUXG4gICAgYS5EaXNwbGF5QWlycG9ydE5hbWUgQVMgYWlycG9ydCxcbiAgICByb3VuZChhdmcoZi5EZXBEZWxheU1pbnV0ZXMpLCAxKSBBUyBhdmdfZGVwX2RlbGF5XG5GUk9NIG9udGltZS5mYWN0X29udGltZSBBUyBmXG5JTk5FUiBKT0lOIG9udGltZS5kaW1fYWlycG9ydHMgQVMgYVxuICAgIE9OIGEuQWlycG9ydENvZGUgPSBmLk9yaWdpbkNvZGUgQU5EIGEuSXNMYXRlc3QgPSAxXG5XSEVSRSBmLlllYXIgPSAyMDIzXG5HUk9VUCBCWSBhaXJwb3J0XG5IQVZJTkcgY291bnQoKSA+PSAxMDAwMFxuT1JERVIgQlkgYXZnX2RlcF9kZWxheSBERVNDXG5MSU1JVCAxNSIsImNoYXJ0Ijp7ImNmZyI6eyJ0eXBlIjoiaGJhciIsIngiOjAsInkiOlsxXSwic2VyaWVzIjpudWxsfSwia2V5IjoiYWlycG9ydDpTdHJpbmd8YXZnX2RlcF9kZWxheTpOdWxsYWJsZShGbG9hdDY0KSJ9fQ==) - -## Tables used - -- `ontime.fact_ontime` — one row per US domestic flight (dates, carrier, origin/dest, delays, cancellations, …). -- `ontime.dim_airports` — airport reference data; joined on `AirportCode = OriginCode AND IsLatest = 1` for human-readable airport names. +The generator preserves the authored tile order and sizes, filters and +defaults, KPI field configuration, semantic dashboard identity, and complete +`flow@1` fallback. diff --git a/docs/SHOP-ANALYTICS-DEMO.md b/docs/SHOP-ANALYTICS-DEMO.md new file mode 100644 index 00000000..f63235f8 --- /dev/null +++ b/docs/SHOP-ANALYTICS-DEMO.md @@ -0,0 +1,23 @@ +# Shop analytics dashboard + +The Shop example has two deliberately separate artifacts: + +- [`examples/shop-demo.sql`](../examples/shop-demo.sql) creates the `shop` + schema, sample events/products, dictionary, materialized views, aggregate + tables, and lineage relationships. +- [`examples/shop-charts.json`](../examples/shop-charts.json) contains the + reusable queries and authored dashboard, without duplicating setup SQL. + +Load the SQL once with a suitably privileged ClickHouse client, then open the +bundle with **File ▾ → Open…**. The dashboard shows Sales KPIs, Daily revenue, +Revenue by country, Revenue by category, Top products, Daily active users, and +Traffic by hour. Country revenue trends remain as an untiled Library analysis. + +The active range defaults to `-90d` through `now`. Country and Category are +inactive searchable multiselects. Their target lists are explicit because the +aggregate tables intentionally do not all carry both dimensions. + +The setup user needs create/insert privileges for `shop.*`. Dashboard users +need `SELECT ON shop.*`; `SELECT ON system.dictionaries` is also required to +show dictionary lineage. The dictionary's ClickHouse source user must be able +to read `shop.products`. diff --git a/docs/SYSTEM-EXPLORER-DEMO.md b/docs/SYSTEM-EXPLORER-DEMO.md deleted file mode 100644 index c0bf852e..00000000 --- a/docs/SYSTEM-EXPLORER-DEMO.md +++ /dev/null @@ -1,90 +0,0 @@ -# System explorer demo — introspecting ClickHouse® itself - -A ready-made **Library** of 14 queries against ClickHouse's own `system` database — -running queries, merges/mutations/replication health, storage, and historical -query/part/error activity — running on the OSS `github.demo` cluster. Ideas and -query shapes are adapted from Mikhail Filimonov's -[ClickHouse ops Grafana dashboard](https://gist.github.com/filimonov/271e5b27c085356c67db3c1bf2204506) -(68 panels covering `metric_log`, `asynchronous_metric_log`, `query_log`, -`query_views_log`, `part_log`, and `error_log`) — not ported 1:1 (no Grafana -template macros, no per-cluster time-series for every background-pool metric), -just enough to show the shape of "explore your own cluster" as a Library + -Dashboard, not a full monitoring reimplementation. - -The six historical queries (#9–#14) share **one pair of query variables**, -`{from:String}`/`{to:String}` (parsed with `parseDateTimeBestEffort`), instead -of each hardcoding its own `now() - INTERVAL …` window. Same names everywhere -means the Dashboard's global filter bar (#149 D3) renders a single **From / -To** field pair that re-runs all six time-ranged tiles together when you type -a new range — one filter, six charts. - -- **Live demo:** **https://github.demo.altinity.cloud/sql** -- **The library file:** [`examples/system-explorer-charts.json`](../examples/system-explorer-charts.json) - ([raw download](https://raw.githubusercontent.com/Altinity/altinity-sql-browser/main/examples/system-explorer-charts.json)) -- **Reproduce it:** [`examples/mjs/build-system-explorer-charts.mjs`](../examples/mjs/build-system-explorer-charts.mjs) - regenerates the JSON (it derives each chart's schema key live via `DESCRIBE`- - equivalent `FORMAT JSON`, with throwaway `--param_from`/`--param_to` values - bound just so ClickHouse can resolve column types — the shipped SQL keeps - the placeholders unbound for the browser to fill in). - -## Load it (≈30 seconds) - -1. Open **https://github.demo.altinity.cloud/sql** and sign in (**Continue with - GitHub** via Auth0, or use the credentials box — see - [LOGIN-SCREEN.md](LOGIN-SCREEN.md) for what each login path grants). -2. Download [`system-explorer-charts.json`](https://raw.githubusercontent.com/Altinity/altinity-sql-browser/main/examples/system-explorer-charts.json) - (right-click → Save link as…). -3. In the header, click **File ▾ → Append…** and pick the file (Append merges - into whatever's already in your Library, reporting `Added N`; use **Open…** - instead if you'd rather replace the whole Library). Eight of the fourteen - queries import already **favorited**. -4. Click **File ▾ → "Open as dashboard"** (or the Dashboard link in the - sidebar). Two KPI-less tiles (#6–8, live snapshots) render immediately; - the six time-ranged tiles (#9–13, minus the table-only #14) show an "Enter - a value for: from, to" placeholder until you type a range into the - dashboard's **From / To** filter fields — then all six re-run together. - `parseDateTimeBestEffort` accepts most absolute formats; e.g. From - `2026-07-01 00:00:00`, To `2026-07-05 00:00:00` (there's no relative - `now`/`today` shorthand — type a real timestamp for "to" as well). - -## What each query demonstrates - -| # | Query | View | What it shows | -|---|-------|------|----------------| -| 1 | Currently running queries | Table | `system.processes` live snapshot — often empty; that's a real result | -| 2 | Merges in progress | Table | `system.merges` — background merge progress + size | -| 3 | Mutations in progress | Table | `system.mutations WHERE NOT is_done`, with failure reason | -| 4 | Replication status | Table | `system.replicas` — delay, queue depth, leadership | -| 5 | Stuck replication queue entries | Table | `system.replication_queue WHERE num_tries > 0` | -| 6 | Largest tables by disk usage | Bar (horizontal) | `system.parts` summed per table | -| 7 | Active parts by table | Bar (horizontal) | part *count* per table — an early "too many parts" signal | -| 8 | Cumulative error counters | Bar (horizontal) | `system.errors` — every error code hit since restart | -| 9 | Queries per minute | Line | `system.query_log` bucketed per minute over `{from}`/`{to}`; `DateTime` axis auto-detected as time | -| 10 | Slowest query patterns — avg duration | Bar (horizontal) | `query_log` over `{from}`/`{to}`, grouped by `normalized_query_hash`, a non-count measure | -| 11 | Query errors over time | Grouped bars | `query_log` failures over `{from}`/`{to}`, **Series** = error name | -| 12 | Part lifecycle events over time | Grouped bars | `part_log` over `{from}`/`{to}`, **Series** = `event_type` (an `Enum8` column) | -| 13 | Memory usage over time | Line | `system.metric_log`'s `CurrentMetric_MemoryTracking` over `{from}`/`{to}`, averaged per minute | -| 14 | Query cost breakdown — slowest patterns (detail) | Table | the deep-dive version of #10 over `{from}`/`{to}`: executions, rows/bytes read, p99 memory | - -Rows 1–5 and 14 need `SELECT` on the relevant `system.*` table; rows 6–13 also -read `system.query_log`/`system.part_log`/`system.metric_log`, which most -demo/read-only users won't have — sign in with an account that has broader -`system` grants (or run it against your own cluster as an admin) to see all -fourteen populate. - -## Direct links - -Every chartable query is also reachable as a single shareable link — open one -and the SQL **and** its chart configuration are pre-loaded. Rows 6–8 need only -**Run**, then the **Chart** tab; rows 9–13 also need `from`/`to` values typed -into the variable strip below the editor before Run is enabled (the link -itself can't carry a variable *value*, only the query). - -- **Bar** — [Largest tables by disk usage](https://github.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUIGNvbmNhdChkYXRhYmFzZSwgJy4nLCB0YWJsZSkgQVMgdGFibGUsIHN1bShieXRlc19vbl9kaXNrKSBBUyBkaXNrX2J5dGVzXG5GUk9NIHN5c3RlbS5wYXJ0c1xuV0hFUkUgYWN0aXZlXG5HUk9VUCBCWSBkYXRhYmFzZSwgdGFibGVcbk9SREVSIEJZIGRpc2tfYnl0ZXMgREVTQ1xuTElNSVQgMTUiLCJjaGFydCI6eyJjZmciOnsidHlwZSI6ImhiYXIiLCJ4IjowLCJ5IjpbMV0sInNlcmllcyI6bnVsbH0sImtleSI6InRhYmxlOlN0cmluZ3xkaXNrX2J5dGVzOlVJbnQ2NCJ9fQ==) -- **Bar** — [Active parts by table](https://github.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUIGNvbmNhdChkYXRhYmFzZSwgJy4nLCB0YWJsZSkgQVMgdGFibGUsIGNvdW50KCkgQVMgcGFydHNcbkZST00gc3lzdGVtLnBhcnRzXG5XSEVSRSBhY3RpdmVcbkdST1VQIEJZIGRhdGFiYXNlLCB0YWJsZVxuT1JERVIgQlkgcGFydHMgREVTQ1xuTElNSVQgMTUiLCJjaGFydCI6eyJjZmciOnsidHlwZSI6ImhiYXIiLCJ4IjowLCJ5IjpbMV0sInNlcmllcyI6bnVsbH0sImtleSI6InRhYmxlOlN0cmluZ3xwYXJ0czpVSW50NjQifX0=) -- **Bar** — [Cumulative error counters](https://github.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUIG5hbWUsIHZhbHVlIEFTIHRpbWVzXG5GUk9NIHN5c3RlbS5lcnJvcnNcbldIRVJFIHZhbHVlID4gMFxuT1JERVIgQlkgdmFsdWUgREVTQ1xuTElNSVQgMTUiLCJjaGFydCI6eyJjZmciOnsidHlwZSI6ImhiYXIiLCJ4IjowLCJ5IjpbMV0sInNlcmllcyI6bnVsbH0sImtleSI6Im5hbWU6U3RyaW5nfHRpbWVzOlVJbnQ2NCJ9fQ==) -- **Line** — [Queries per minute](https://github.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUIHRvU3RhcnRPZk1pbnV0ZShldmVudF90aW1lKSBBUyB0LCBjb3VudCgpIEFTIHF1ZXJpZXNcbkZST00gc3lzdGVtLnF1ZXJ5X2xvZ1xuV0hFUkUgZXZlbnRfdGltZSBCRVRXRUVOIHBhcnNlRGF0ZVRpbWVCZXN0RWZmb3J0KHtmcm9tOlN0cmluZ30pIEFORCBwYXJzZURhdGVUaW1lQmVzdEVmZm9ydCh7dG86U3RyaW5nfSkgQU5EIHR5cGUgPSAnUXVlcnlGaW5pc2gnXG5HUk9VUCBCWSB0XG5PUkRFUiBCWSB0IiwiY2hhcnQiOnsiY2ZnIjp7InR5cGUiOiJsaW5lIiwieCI6MCwieSI6WzFdLCJzZXJpZXMiOm51bGx9LCJrZXkiOiJ0OkRhdGVUaW1lfHF1ZXJpZXM6VUludDY0In19) -- **Bar** — [Slowest query patterns — avg duration](https://github.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUIGxlZnQoYW55KHF1ZXJ5KSwgNTApIEFTIHF1ZXJ5LCBhdmcocXVlcnlfZHVyYXRpb25fbXMpIEFTIGF2Z19kdXJhdGlvbl9tc1xuRlJPTSBzeXN0ZW0ucXVlcnlfbG9nXG5XSEVSRSBldmVudF90aW1lIEJFVFdFRU4gcGFyc2VEYXRlVGltZUJlc3RFZmZvcnQoe2Zyb206U3RyaW5nfSkgQU5EIHBhcnNlRGF0ZVRpbWVCZXN0RWZmb3J0KHt0bzpTdHJpbmd9KSBBTkQgdHlwZSA9ICdRdWVyeUZpbmlzaCdcbkdST1VQIEJZIG5vcm1hbGl6ZWRfcXVlcnlfaGFzaFxuT1JERVIgQlkgYXZnX2R1cmF0aW9uX21zIERFU0NcbkxJTUlUIDE1IiwiY2hhcnQiOnsiY2ZnIjp7InR5cGUiOiJoYmFyIiwieCI6MCwieSI6WzFdLCJzZXJpZXMiOm51bGx9LCJrZXkiOiJxdWVyeTpTdHJpbmd8YXZnX2R1cmF0aW9uX21zOkZsb2F0NjQifX0=) -- **Grouped bars** — [Query errors over time](https://github.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUIHRvU3RhcnRPZkhvdXIoZXZlbnRfdGltZSkgQVMgdCwgZXJyb3JDb2RlVG9OYW1lKGV4Y2VwdGlvbl9jb2RlKSBBUyBlcnJvciwgY291bnQoKSBBUyBuXG5GUk9NIHN5c3RlbS5xdWVyeV9sb2dcbldIRVJFIGV2ZW50X3RpbWUgQkVUV0VFTiBwYXJzZURhdGVUaW1lQmVzdEVmZm9ydCh7ZnJvbTpTdHJpbmd9KSBBTkQgcGFyc2VEYXRlVGltZUJlc3RFZmZvcnQoe3RvOlN0cmluZ30pIEFORCBleGNlcHRpb25fY29kZSAhPSAwXG5HUk9VUCBCWSB0LCBlcnJvclxuT1JERVIgQlkgdCIsImNoYXJ0Ijp7ImNmZyI6eyJ0eXBlIjoiYmFyIiwieCI6MCwieSI6WzJdLCJzZXJpZXMiOjF9LCJrZXkiOiJ0OkRhdGVUaW1lfGVycm9yOkxvd0NhcmRpbmFsaXR5KFN0cmluZyl8bjpVSW50NjQifX0=) -- **Grouped bars** — [Part lifecycle events over time](https://github.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUIHRvU3RhcnRPZkhvdXIoZXZlbnRfdGltZSkgQVMgdCwgZXZlbnRfdHlwZSwgY291bnQoKSBBUyBuXG5GUk9NIHN5c3RlbS5wYXJ0X2xvZ1xuV0hFUkUgZXZlbnRfdGltZSBCRVRXRUVOIHBhcnNlRGF0ZVRpbWVCZXN0RWZmb3J0KHtmcm9tOlN0cmluZ30pIEFORCBwYXJzZURhdGVUaW1lQmVzdEVmZm9ydCh7dG86U3RyaW5nfSlcbkdST1VQIEJZIHQsIGV2ZW50X3R5cGVcbk9SREVSIEJZIHQiLCJjaGFydCI6eyJjZmciOnsidHlwZSI6ImJhciIsIngiOjAsInkiOlsyXSwic2VyaWVzIjoxfSwia2V5IjoidDpEYXRlVGltZXxldmVudF90eXBlOkVudW04KCdOZXdQYXJ0JyA9IDEsICdNZXJnZVBhcnRzJyA9IDIsICdEb3dubG9hZFBhcnQnID0gMywgJ1JlbW92ZVBhcnQnID0gNCwgJ011dGF0ZVBhcnQnID0gNSwgJ01vdmVQYXJ0JyA9IDYsICdNZXJnZVBhcnRzU3RhcnQnID0gNywgJ011dGF0ZVBhcnRTdGFydCcgPSA4KXxuOlVJbnQ2NCJ9fQ==) -- **Line** — [Memory usage over time](https://github.demo.altinity.cloud/sql#eyJfX2FzYiI6MSwic3FsIjoiU0VMRUNUIHRvU3RhcnRPZk1pbnV0ZShldmVudF90aW1lKSBBUyB0LCBhdmcoQ3VycmVudE1ldHJpY19NZW1vcnlUcmFja2luZykgQVMgbWVtb3J5X2J5dGVzXG5GUk9NIHN5c3RlbS5tZXRyaWNfbG9nXG5XSEVSRSBldmVudF90aW1lIEJFVFdFRU4gcGFyc2VEYXRlVGltZUJlc3RFZmZvcnQoe2Zyb206U3RyaW5nfSkgQU5EIHBhcnNlRGF0ZVRpbWVCZXN0RWZmb3J0KHt0bzpTdHJpbmd9KVxuR1JPVVAgQlkgdFxuT1JERVIgQlkgdCIsImNoYXJ0Ijp7ImNmZyI6eyJ0eXBlIjoibGluZSIsIngiOjAsInkiOlsxXSwic2VyaWVzIjpudWxsfSwia2V5IjoidDpEYXRlVGltZXxtZW1vcnlfYnl0ZXM6RmxvYXQ2NCJ9fQ==) diff --git a/docs/local-app.html b/docs/local-app.html index d1d4459d..8f1a0df9 100644 --- a/docs/local-app.html +++ b/docs/local-app.html @@ -150,7 +150,7 @@

Bundled demo endpoints

Want a guided tour? Load the ontime chart demo — ten ready-made queries that walk through every chart type — or the - system explorer demo, + ClickHouse Operations demo, fourteen queries that introspect ClickHouse's own system database.

diff --git a/examples/grafana-clickhouse-ops-enhanced.json b/examples/clickhouse-operations.json similarity index 86% rename from examples/grafana-clickhouse-ops-enhanced.json rename to examples/clickhouse-operations.json index 95ffdce4..3f548a92 100644 --- a/examples/grafana-clickhouse-ops-enhanced.json +++ b/examples/clickhouse-operations.json @@ -2,58 +2,104 @@ "$schema": "https://altinity.com/schemas/altinity-sql-browser/portable-bundle-v1.schema.json", "format": "altinity-sql-browser/portable-bundle", "version": 1, - "exportedAt": "2026-07-16T00:00:00.000Z", + "exportedAt": "2026-07-22T00:00:00.000Z", "metadata": { - "name": "ClickHouse operations", - "description": "Operational ClickHouse dashboard adapted from the Grafana dashboard." + "name": "ClickHouse Operations", + "description": "Operator-first server overview, resources, background work, and investigation views." }, "queries": [ { - "id": "gco-filter", - "sql": "SELECT\n [toUInt8(1), toUInt8(0)] AS is_initial_query,\n (\n SELECT arraySort(groupUniqArray(query_kind))\n FROM merge(system, '^query_log')\n WHERE event_time >= now() - INTERVAL 7 DAY AND query_kind != ''\n ) AS query_kind,\n (\n SELECT arraySort(groupUniqArray(initial_user))\n FROM merge(system, '^query_log')\n WHERE event_time >= now() - INTERVAL 7 DAY AND initial_user != ''\n ) AS user,\n (\n SELECT groupArray((exception_code AS value, concat(toString(exception_code), ' · ', errorCodeToName(exception_code)) AS label))\n FROM\n (\n SELECT exception_code\n FROM merge(system, '^query_log')\n WHERE event_time >= now() - INTERVAL 7 DAY AND exception_code != 0\n GROUP BY exception_code\n ORDER BY count() DESC\n LIMIT 100\n )\n ) AS exception_code,\n (\n SELECT groupArray((normalized_query_hash AS value, concat(toString(normalized_query_hash), ' · ', substring(sample_query, 1, 80)) AS label))\n FROM\n (\n SELECT normalized_query_hash, anyHeavy(replaceAll(query, '\\n', ' ')) AS sample_query\n FROM merge(system, '^query_log')\n WHERE event_time >= now() - INTERVAL 7 DAY AND normalized_query_hash != 0\n GROUP BY normalized_query_hash\n ORDER BY count() DESC\n LIMIT 80\n )\n ) AS query_hash,\n ['count', 'avg_duration', 'max_duration', 'cpu_time', 'read_bytes', 'written_bytes', 'avg_written_rows', 'result_bytes', 'network_bytes', 'memory', 'max_memory', 'network_wait', 'io_time', 'io_wait', 'zk_txns', 'read_bps', 'write_bps', 'parts_inserted', 'parts_inserted_avg', 'marks_load_time', 'marks_miss_rate', 'selected_parts', 'selected_ranges', 'selected_marks', 'exceptions', 'open_files', 'external_processing_files', 'threads1', 'threads2', 'threads3'] AS metric\nSETTINGS enable_named_columns_in_function_tuple = 1", + "id": "gco-kpi-overview", + "sql": "WITH snap AS (\n SELECT\n (SELECT max(event_time) FROM merge(system, '^metric_log')) - 300 AS t_prev,\n argMax(CurrentMetric_Query, event_time) AS q_now,\n round(argMax(CurrentMetric_MemoryTracking, event_time) / 1048576) AS mem_now,\n round(argMaxIf(CurrentMetric_MemoryTracking, event_time, event_time <= t_prev) / 1048576) AS mem_prev,\n argMax(CurrentMetric_PartsActive, event_time) AS parts_now,\n argMaxIf(CurrentMetric_PartsActive, event_time, event_time <= t_prev) AS parts_prev,\n round(max(CurrentMetric_MemoryTracking) / 1048576) AS mem_peak\n FROM merge(system, '^metric_log')\n)\nSELECT\n q_now AS running_queries,\n CAST((mem_now, mem_now - mem_prev) AS Tuple(value Int64, delta Int64)) AS memory_used,\n CAST((parts_now, parts_now - parts_prev) AS Tuple(value Int64, delta Int64)) AS active_parts,\n mem_peak AS peak_memory\nFROM snap", "specVersion": 1, "spec": { - "name": "Grafana port filters", - "favorite": false, - "description": "Filter source for is_initial_query, query_kind, user, exception_code, query_hash, and metric. Dynamic query-log options use a fixed last-seven-days window. Controls are single-select; blank means Grafana All. Query hashes are capped at the 80 most frequent.", + "name": "Overview · Live KPIs", + "description": "Current running queries, tracked/peak memory (MiB), and active parts from the latest system.metric_log snapshot. Memory and parts carry a delta against five minutes earlier.", + "favorite": true, + "view": "panel", + "panel": { + "cfg": { + "type": "kpi" + }, + "fieldConfig": { + "defaults": { + "noValue": "—" + }, + "columns": { + "active_parts": { + "displayName": "Active parts", + "description": "Active data parts across all tables.", + "decimals": 0, + "delta": { + "decimals": 0, + "positiveIsGood": false + } + }, + "memory_used": { + "displayName": "Memory tracked", + "description": "Server-tracked memory at the latest sample.", + "unit": " MiB", + "decimals": 0, + "delta": { + "unit": " MiB", + "decimals": 0, + "positiveIsGood": false + } + }, + "peak_memory": { + "displayName": "Peak memory", + "description": "Maximum tracked memory over the window.", + "unit": " MiB", + "decimals": 0 + }, + "running_queries": { + "displayName": "Running queries", + "description": "Concurrent queries at the latest sample.", + "decimals": 0, + "color": "#4f8cff" + } + } + } + }, "dashboard": { - "role": "filter" + "role": "panel", + "sizeHints": { + "preferred": "compact", + "minimum": "compact", + "aspectRatio": 2 + } } } }, { - "id": "gco-029-running-queries", - "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n toDateTime(intDiv(toUInt32(event_time), bucket_s) * bucket_s) AS t,\n max(CurrentMetric_Query) AS value\nFROM merge(system, '^metric_log')\nWHERE event_time >= {from:DateTime}\n AND event_time <= {to:DateTime}\nGROUP BY t\nORDER BY t", + "id": "gco-donut-query-kind", + "sql": "SELECT query_kind, count() AS queries\nFROM merge(system, '^query_log')\nWHERE event_time >= {from:DateTime}\n AND event_time <= {to:DateTime}\n AND type != 'QueryStart'\n AND query_kind != ''\nGROUP BY query_kind\nORDER BY queries DESC", "specVersion": 1, "spec": { - "name": "Overview · Running Queries (max over interval)", - "favorite": true, - "description": "CurrentMetric_Query from system.metric_log. Single-node port; the original per-host series collapses to one line.", + "name": "Queries · Mix by kind (Donut)", + "description": "Share of query kinds (Select, Insert, …) from system.query_log over the dashboard window. Uses the Donut Style preset.", + "favorite": false, "view": "panel", "panel": { "cfg": { - "type": "line", + "type": "pie", + "style": { + "frame": "normal", + "legend": "show", + "shape": "donut" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { - "value": { - "displayName": "Running queries", + "queries": { + "displayName": "Queries", "unit": " queries", - "decimals": 0, - "description": "Maximum concurrent queries in each bucket." + "decimals": 0 } } } @@ -74,33 +120,80 @@ "specVersion": 1, "spec": { "name": "Other activities · Merges running", - "favorite": true, "description": "CurrentMetric_Merge from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": true, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, + "series": null + }, + "fieldConfig": { + "columns": { + "value": { + "displayName": "Merges", + "description": "Average active merge tasks in each bucket.", + "unit": " tasks", + "decimals": 1 + } + } + } + }, + "dashboard": { + "role": "panel", + "sizeHints": { + "preferred": "medium", + "minimum": "compact", + "aspectRatio": 1.5 + } + } + } + }, + { + "id": "gco-029-running-queries", + "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n toDateTime(intDiv(toUInt32(event_time), bucket_s) * bucket_s) AS t,\n max(CurrentMetric_Query) AS value\nFROM merge(system, '^metric_log')\nWHERE event_time >= {from:DateTime}\n AND event_time <= {to:DateTime}\nGROUP BY t\nORDER BY t", + "specVersion": 1, + "spec": { + "name": "Overview · Running Queries (max over interval)", + "description": "CurrentMetric_Query from system.metric_log. Single-node port; the original per-host series collapses to one line.", + "favorite": true, + "view": "panel", + "panel": { + "cfg": { + "type": "line", "style": { + "axes": "show", "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", "grid": "auto", - "axes": "show" - } + "legend": "auto", + "points": "auto", + "scale": "zero" + }, + "x": 0, + "y": [ + 1 + ], + "series": null }, "fieldConfig": { "columns": { "value": { - "displayName": "Merges", - "unit": " tasks", - "decimals": 1, - "description": "Average active merge tasks in each bucket." + "displayName": "Running queries", + "description": "Maximum concurrent queries in each bucket.", + "unit": " queries", + "decimals": 0 } } } @@ -121,33 +214,33 @@ "specVersion": 1, "spec": { "name": "Other activities · Mutations running", - "favorite": true, "description": "CurrentMetric_PartMutation from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": true, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Mutations", + "description": "Average active mutation tasks in each bucket.", "unit": " tasks", - "decimals": 1, - "description": "Average active mutation tasks in each bucket." + "decimals": 1 } } } @@ -168,33 +261,33 @@ "specVersion": 1, "spec": { "name": "Other activities · Moves running", - "favorite": true, "description": "CurrentMetric_Move from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Moves", + "description": "Average active move tasks in each bucket.", "unit": " tasks", - "decimals": 1, - "description": "Average active move tasks in each bucket." + "decimals": 1 } } } @@ -215,33 +308,33 @@ "specVersion": 1, "spec": { "name": "Other activities · DistributedSend running", - "favorite": true, "description": "CurrentMetric_DistributedSend from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Distributed sends", + "description": "Average active distributed-send tasks in each bucket.", "unit": " tasks", - "decimals": 1, - "description": "Average active distributed-send tasks in each bucket." + "decimals": 1 } } } @@ -262,33 +355,33 @@ "specVersion": 1, "spec": { "name": "Other activities · ReplicatedChecks running", - "favorite": true, "description": "CurrentMetric_ReplicatedChecks from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": true, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Replication checks", + "description": "Average active replication-check tasks in each bucket.", "unit": " tasks", - "decimals": 1, - "description": "Average active replication-check tasks in each bucket." + "decimals": 1 } } } @@ -309,33 +402,33 @@ "specVersion": 1, "spec": { "name": "Other activities · ReplicatedFetch running", - "favorite": true, "description": "CurrentMetric_ReplicatedFetch from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Replication fetches", + "description": "Average active replication-fetch tasks in each bucket.", "unit": " tasks", - "decimals": 1, - "description": "Average active replication-fetch tasks in each bucket." + "decimals": 1 } } } @@ -356,33 +449,33 @@ "specVersion": 1, "spec": { "name": "Other activities · ReplicatedSend running", - "favorite": true, "description": "CurrentMetric_ReplicatedSend from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Replication sends", + "description": "Average active replication-send tasks in each bucket.", "unit": " tasks", - "decimals": 1, - "description": "Average active replication-send tasks in each bucket." + "decimals": 1 } } } @@ -403,33 +496,33 @@ "specVersion": 1, "spec": { "name": "Other activities · KafkaBackgroundReads running", - "favorite": true, "description": "CurrentMetric_KafkaBackgroundReads from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Kafka background reads", + "description": "Average active Kafka background-read tasks in each bucket.", "unit": " tasks", - "decimals": 1, - "description": "Average active Kafka background-read tasks in each bucket." + "decimals": 1 } } } @@ -450,33 +543,33 @@ "specVersion": 1, "spec": { "name": "Other activities · RefreshingViews running", - "favorite": true, "description": "CurrentMetric_RefreshingViews from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Refreshing views", + "description": "Average active refreshable-view tasks in each bucket.", "unit": " tasks", - "decimals": 1, - "description": "Average active refreshable-view tasks in each bucket." + "decimals": 1 } } } @@ -497,33 +590,33 @@ "specVersion": 1, "spec": { "name": "Other activities · KafkaWrites", - "favorite": true, "description": "CurrentMetric_KafkaWrites from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Kafka writes", + "description": "Average active Kafka write tasks in each bucket.", "unit": " tasks", - "decimals": 1, - "description": "Average active Kafka write tasks in each bucket." + "decimals": 1 } } } @@ -544,33 +637,33 @@ "specVersion": 1, "spec": { "name": "Other activities · BackgroundSchedulePoolTask", - "favorite": true, "description": "CurrentMetric_BackgroundSchedulePoolTask from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Background schedule tasks", + "description": "Average active BackgroundSchedulePool tasks.", "unit": " tasks", - "decimals": 1, - "description": "Average active BackgroundSchedulePool tasks." + "decimals": 1 } } } @@ -591,33 +684,33 @@ "specVersion": 1, "spec": { "name": "Other activities · BackgroundCommonPoolTask", - "favorite": true, "description": "CurrentMetric_BackgroundCommonPoolTask from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Background common tasks", + "description": "Average active BackgroundCommonPool tasks.", "unit": " tasks", - "decimals": 1, - "description": "Average active BackgroundCommonPool tasks." + "decimals": 1 } } } @@ -638,33 +731,33 @@ "specVersion": 1, "spec": { "name": "Other activities · BackgroundMovePoolTask", - "favorite": true, "description": "CurrentMetric_BackgroundMovePoolTask from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Background move tasks", + "description": "Average active BackgroundMovePool tasks.", "unit": " tasks", - "decimals": 1, - "description": "Average active BackgroundMovePool tasks." + "decimals": 1 } } } @@ -685,33 +778,33 @@ "specVersion": 1, "spec": { "name": "Other activities · BackgroundFetchesPoolTask", - "favorite": true, "description": "CurrentMetric_BackgroundFetchesPoolTask from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Background fetch tasks", + "description": "Average active BackgroundFetchesPool tasks.", "unit": " tasks", - "decimals": 1, - "description": "Average active BackgroundFetchesPool tasks." + "decimals": 1 } } } @@ -727,38 +820,38 @@ } }, { - "id": "gco-066-background-merges-mutations", - "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n toDateTime(intDiv(toUInt32(event_time), bucket_s) * bucket_s) AS t,\n avg(CurrentMetric_BackgroundMergesAndMutationsPoolTask) AS value\nFROM merge(system, '^metric_log')\nWHERE event_time >= {from:DateTime}\n AND event_time <= {to:DateTime}\nGROUP BY t\nORDER BY t", + "id": "gco-074-background-distributed", + "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n toDateTime(intDiv(toUInt32(event_time), bucket_s) * bucket_s) AS t,\n avg(CurrentMetric_BackgroundDistributedSchedulePoolTask) AS value\nFROM merge(system, '^metric_log')\nWHERE event_time >= {from:DateTime}\n AND event_time <= {to:DateTime}\nGROUP BY t\nORDER BY t", "specVersion": 1, "spec": { - "name": "Other activities · BackgroundMergesAndMutationsPoolTask", - "favorite": true, - "description": "CurrentMetric_BackgroundMergesAndMutationsPoolTask from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "name": "Other activities · BackgroundDistributedSchedulePoolTask", + "description": "CurrentMetric_BackgroundDistributedSchedulePoolTask from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { - "displayName": "Background merge/mutation tasks", + "displayName": "Background distributed tasks", + "description": "Average active distributed schedule tasks.", "unit": " tasks", - "decimals": 1, - "description": "Average active background merge and mutation tasks." + "decimals": 1 } } } @@ -774,38 +867,38 @@ } }, { - "id": "gco-074-background-distributed", - "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n toDateTime(intDiv(toUInt32(event_time), bucket_s) * bucket_s) AS t,\n avg(CurrentMetric_BackgroundDistributedSchedulePoolTask) AS value\nFROM merge(system, '^metric_log')\nWHERE event_time >= {from:DateTime}\n AND event_time <= {to:DateTime}\nGROUP BY t\nORDER BY t", + "id": "gco-066-background-merges-mutations", + "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n toDateTime(intDiv(toUInt32(event_time), bucket_s) * bucket_s) AS t,\n avg(CurrentMetric_BackgroundMergesAndMutationsPoolTask) AS value\nFROM merge(system, '^metric_log')\nWHERE event_time >= {from:DateTime}\n AND event_time <= {to:DateTime}\nGROUP BY t\nORDER BY t", "specVersion": 1, "spec": { - "name": "Other activities · BackgroundDistributedSchedulePoolTask", - "favorite": true, - "description": "CurrentMetric_BackgroundDistributedSchedulePoolTask from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "name": "Other activities · BackgroundMergesAndMutationsPoolTask", + "description": "CurrentMetric_BackgroundMergesAndMutationsPoolTask from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { - "displayName": "Background distributed tasks", + "displayName": "Background merge/mutation tasks", + "description": "Average active background merge and mutation tasks.", "unit": " tasks", - "decimals": 1, - "description": "Average active distributed schedule tasks." + "decimals": 1 } } } @@ -826,33 +919,33 @@ "specVersion": 1, "spec": { "name": "Other activities · BackgroundMessageBrokerSchedulePoolTask", - "favorite": true, "description": "CurrentMetric_BackgroundMessageBrokerSchedulePoolTask from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Background broker tasks", + "description": "Average active message-broker schedule tasks.", "unit": " tasks", - "decimals": 1, - "description": "Average active message-broker schedule tasks." + "decimals": 1 } } } @@ -873,33 +966,33 @@ "specVersion": 1, "spec": { "name": "Other activities · BackgroundBufferFlushSchedulePoolTask", - "favorite": true, "description": "CurrentMetric_BackgroundBufferFlushSchedulePoolTask from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Background buffer flush tasks", + "description": "Average active buffer-flush schedule tasks.", "unit": " tasks", - "decimals": 1, - "description": "Average active buffer-flush schedule tasks." + "decimals": 1 } } } @@ -920,33 +1013,33 @@ "specVersion": 1, "spec": { "name": "Other activities · GlobalThreadActive", - "favorite": true, "description": "CurrentMetric_GlobalThreadActive from system.metric_log, averaged into an automatic roughly-300-point time bucket.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Active global threads", + "description": "Average active global threads.", "unit": " threads", - "decimals": 1, - "description": "Average active global threads." + "decimals": 1 } } } @@ -967,33 +1060,33 @@ "specVersion": 1, "spec": { "name": "Other activities · Accounted time (ClickHouse, average over interval)", - "favorite": true, "description": "ProfileEvent timing columns normalized to microseconds and expanded into a Series column. This preserves the source dashboard dynamic COLUMNS approach but uses SQL Browser line-series pivoting.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "area", - "x": 0, - "y": [ - 2 - ], - "series": 1, "style": { + "axes": "show", "curve": "linear", + "grid": "auto", + "legend": "auto", "points": "auto", - "stack": "stacked", "scale": "data", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "stack": "stacked" + }, + "x": 0, + "y": [ + 2 + ], + "series": 1 }, "fieldConfig": { "columns": { "value": { "displayName": "Accounted time", - "unit": " µs", - "description": "ClickHouse timing components normalized to microseconds." + "description": "ClickHouse timing components normalized to microseconds.", + "unit": " µs" } } } @@ -1014,34 +1107,34 @@ "specVersion": 1, "spec": { "name": "Other activities · Active tasks in local task-specific pools (p99.9 over interval)", - "favorite": true, "description": "Dynamic *ThreadsActive metrics expanded to one SQL Browser line series per task pool.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "area", - "x": 0, - "y": [ - 2 - ], - "series": 1, "style": { + "axes": "show", "curve": "linear", + "grid": "auto", + "legend": "auto", "points": "auto", - "stack": "stacked", "scale": "data", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "stack": "stacked" + }, + "x": 0, + "y": [ + 2 + ], + "series": 1 }, "fieldConfig": { "columns": { "value": { "displayName": "Active pool tasks", + "description": "p99.9 active tasks split by task-specific pool.", "unit": " tasks", - "decimals": 0, - "description": "p99.9 active tasks split by task-specific pool." + "decimals": 0 } } } @@ -1062,33 +1155,33 @@ "specVersion": 1, "spec": { "name": "Other activities · ZooKeeper transactions (average per interval)", - "favorite": true, "description": "ProfileEvent_ZooKeeperTransactions from system.metric_log.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", - "x": 0, - "y": [ - 1 - ], - "series": null, "style": { + "axes": "show", "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", "grid": "auto", - "axes": "show" - } + "legend": "auto", + "points": "auto", + "scale": "zero" + }, + "x": 0, + "y": [ + 1 + ], + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "ZooKeeper transactions", + "description": "Average ZooKeeper transactions in each bucket.", "unit": " txns", - "decimals": 1, - "description": "Average ZooKeeper transactions in each bucket." + "decimals": 1 } } } @@ -1109,33 +1202,33 @@ "specVersion": 1, "spec": { "name": "Other activities · ZooKeeper latency (average per interval)", - "favorite": true, "description": "ZooKeeper wait microseconds divided by transactions for each automatic time bucket.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "hide", + "legend": "hide", + "points": "hide", + "scale": "data" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "hide", - "scale": "data", - "legend": "hide", - "grid": "hide", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "ZooKeeper latency", + "description": "Average ZooKeeper wait per transaction.", "unit": " µs", - "decimals": 1, - "description": "Average ZooKeeper wait per transaction." + "decimals": 1 } } } @@ -1156,33 +1249,33 @@ "specVersion": 1, "spec": { "name": "Other activities · ZooKeeper in-flight requests (p95 per interval)", - "favorite": true, "description": "CurrentMetric_ZooKeeperRequest p95 from system.metric_log.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "ZooKeeper in-flight requests", + "description": "p95 in-flight ZooKeeper requests.", "unit": " requests", - "decimals": 1, - "description": "p95 in-flight ZooKeeper requests." + "decimals": 1 } } } @@ -1203,33 +1296,33 @@ "specVersion": 1, "spec": { "name": "Other activities · ZooKeeper traffic (average per interval)", - "favorite": true, "description": "Received bytes are positive and sent bytes negative, matching the Grafana directional plot.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "area", - "x": 0, - "y": [ - 2 - ], - "series": 1, "style": { + "axes": "show", "curve": "linear", + "grid": "auto", + "legend": "auto", "points": "auto", - "stack": "overlay", "scale": "data", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "stack": "overlay" + }, + "x": 0, + "y": [ + 2 + ], + "series": 1 }, "fieldConfig": { "columns": { "value": { "displayName": "ZooKeeper traffic", - "unit": " B/s", - "description": "Received traffic is positive and sent traffic is negative." + "description": "Received traffic is positive and sent traffic is negative.", + "unit": " B/s" } } } @@ -1250,33 +1343,33 @@ "specVersion": 1, "spec": { "name": "System metrics · CPUs loaded (ClickHouse-scoped, average per interval)", - "favorite": true, "description": "ProfileEvent_OSCPUVirtualTimeMicroseconds converted to CPU-seconds per sample.", + "favorite": true, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "hide", + "legend": "hide", + "points": "hide", + "scale": "data" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "hide", - "scale": "data", - "legend": "hide", - "grid": "hide", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "CPU time", + "description": "ClickHouse-scoped CPU-seconds per sample.", "unit": " CPU s", - "decimals": 2, - "description": "ClickHouse-scoped CPU-seconds per sample." + "decimals": 2 } } } @@ -1297,33 +1390,33 @@ "specVersion": 1, "spec": { "name": "System metrics · Memory usage (ClickHouse-scoped, max per interval)", - "favorite": true, "description": "Maximum CurrentMetric_MemoryTracking in bytes.", + "favorite": true, "view": "panel", "panel": { "cfg": { "type": "area", - "x": 0, - "y": [ - 1 - ], - "series": null, "style": { + "axes": "show", "curve": "linear", + "grid": "hide", + "legend": "hide", "points": "hide", - "stack": "overlay", "scale": "data", - "legend": "hide", - "grid": "hide", - "axes": "show" - } + "stack": "overlay" + }, + "x": 0, + "y": [ + 1 + ], + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Memory usage", - "unit": " B", - "description": "Maximum ClickHouse memory tracking in each bucket." + "description": "Maximum ClickHouse memory tracking in each bucket.", + "unit": " B" } } } @@ -1344,34 +1437,34 @@ "specVersion": 1, "spec": { "name": "System metrics · CPU usage (system-wide, average per interval)", - "favorite": true, "description": "1 - OSIdleTimeNormalized from system.asynchronous_metric_log.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "area", - "x": 0, - "y": [ - 1 - ], - "series": null, "style": { + "axes": "show", "curve": "linear", + "grid": "auto", + "legend": "auto", "points": "auto", - "stack": "overlay", "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "stack": "overlay" + }, + "x": 0, + "y": [ + 1 + ], + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "CPU usage", + "description": "System-wide non-idle CPU percentage.", "unit": "%", - "decimals": 1, - "description": "System-wide non-idle CPU percentage." + "decimals": 1 } } } @@ -1392,32 +1485,32 @@ "specVersion": 1, "spec": { "name": "System metrics · Load Average (system-wide, max per interval)", - "favorite": true, "description": "LoadAverage1 from system.asynchronous_metric_log.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "hide", + "legend": "hide", + "points": "hide", + "scale": "data" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "hide", - "scale": "data", - "legend": "hide", - "grid": "hide", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Load average", - "decimals": 2, - "description": "Maximum one-minute system load average." + "description": "Maximum one-minute system load average.", + "decimals": 2 } } } @@ -1438,34 +1531,34 @@ "specVersion": 1, "spec": { "name": "System metrics · IO utilization % (system-wide, average per interval)", - "favorite": true, "description": "BlockActiveTime per asynchronous-metrics update interval, one series per block device. Target is ClickHouse 25.8+, so the post-25.4 unit multiplier is used.", + "favorite": true, "view": "panel", "panel": { "cfg": { "type": "area", - "x": 0, - "y": [ - 2 - ], - "series": 1, "style": { + "axes": "show", "curve": "linear", + "grid": "auto", + "legend": "auto", "points": "auto", - "stack": "overlay", "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "stack": "overlay" + }, + "x": 0, + "y": [ + 2 + ], + "series": 1 }, "fieldConfig": { "columns": { "value": { "displayName": "I/O utilization", + "description": "Block-device active-time percentage.", "unit": "%", - "decimals": 1, - "description": "Block-device active-time percentage." + "decimals": 1 } } } @@ -1486,33 +1579,33 @@ "specVersion": 1, "spec": { "name": "System metrics · Network In & Out (system-wide, average per interval)", - "favorite": true, "description": "Network receive bits/s positive and send bits/s negative, one series per interface.", + "favorite": true, "view": "panel", "panel": { "cfg": { "type": "area", - "x": 0, - "y": [ - 2 - ], - "series": 1, "style": { + "axes": "show", "curve": "linear", + "grid": "auto", + "legend": "auto", "points": "auto", - "stack": "overlay", "scale": "data", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "stack": "overlay" + }, + "x": 0, + "y": [ + 2 + ], + "series": 1 }, "fieldConfig": { "columns": { "value": { "displayName": "Network traffic", - "unit": " bit/s", - "description": "Receive traffic is positive and send traffic is negative, split by interface." + "description": "Receive traffic is positive and send traffic is negative, split by interface.", + "unit": " bit/s" } } } @@ -1533,33 +1626,33 @@ "specVersion": 1, "spec": { "name": "System metrics · IOWait (ClickHouse-scoped, average per interval)", - "favorite": true, "description": "ProfileEvent_OSIOWaitMicroseconds converted to seconds.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "hide", + "legend": "hide", + "points": "hide", + "scale": "data" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "hide", - "scale": "data", - "legend": "hide", - "grid": "hide", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "I/O wait", + "description": "ClickHouse-scoped I/O wait time.", "unit": " s", - "decimals": 3, - "description": "ClickHouse-scoped I/O wait time." + "decimals": 3 } } } @@ -1580,33 +1673,33 @@ "specVersion": 1, "spec": { "name": "System metrics · CPUWait (ClickHouse-scoped, average per interval)", - "favorite": true, "description": "ProfileEvent_OSCPUWaitMicroseconds converted to seconds.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "hide", + "legend": "hide", + "points": "hide", + "scale": "data" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "hide", - "scale": "data", - "legend": "hide", - "grid": "hide", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "CPU wait", + "description": "ClickHouse-scoped CPU wait time.", "unit": " s", - "decimals": 3, - "description": "ClickHouse-scoped CPU wait time." + "decimals": 3 } } } @@ -1627,33 +1720,33 @@ "specVersion": 1, "spec": { "name": "System metrics · Disk reads & writes (ClickHouse-scoped, average per interval)", - "favorite": true, "description": "OS read bytes positive and write bytes negative.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "area", - "x": 0, - "y": [ - 2 - ], - "series": 1, "style": { + "axes": "show", "curve": "linear", + "grid": "auto", + "legend": "auto", "points": "auto", - "stack": "overlay", "scale": "data", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "stack": "overlay" + }, + "x": 0, + "y": [ + 2 + ], + "series": 1 }, "fieldConfig": { "columns": { "value": { "displayName": "Disk I/O", - "unit": " B/s", - "description": "Reads are positive and writes are negative." + "description": "Reads are positive and writes are negative.", + "unit": " B/s" } } } @@ -1674,33 +1767,33 @@ "specVersion": 1, "spec": { "name": "System metrics · Disk + page cache reads & writes (ClickHouse-scoped, average per interval)", - "favorite": true, "description": "OSReadChars/OSWriteChars include page-cache traffic; writes are plotted negative.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "area", - "x": 0, - "y": [ - 2 - ], - "series": 1, "style": { + "axes": "show", "curve": "linear", + "grid": "auto", + "legend": "auto", "points": "auto", - "stack": "overlay", "scale": "data", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "stack": "overlay" + }, + "x": 0, + "y": [ + 2 + ], + "series": 1 }, "fieldConfig": { "columns": { "value": { "displayName": "Disk and page-cache I/O", - "unit": " B/s", - "description": "Reads are positive and writes are negative, including page-cache traffic." + "description": "Reads are positive and writes are negative, including page-cache traffic.", + "unit": " B/s" } } } @@ -1721,32 +1814,32 @@ "specVersion": 1, "spec": { "name": "System metrics · IO bytes (system-wide, average per interval)", - "favorite": true, "description": "Block read bytes/s positive and write bytes/s negative, one series per block device.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "data" + }, "x": 0, "y": [ 2 ], - "series": 1, - "style": { - "curve": "linear", - "points": "auto", - "scale": "data", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": 1 }, "fieldConfig": { "columns": { "value": { "displayName": "I/O throughput", - "unit": " B/s", - "description": "Read values are positive and write values are negative, split by device." + "description": "Read values are positive and write values are negative, split by device.", + "unit": " B/s" } } } @@ -1767,33 +1860,33 @@ "specVersion": 1, "spec": { "name": "System metrics · IOPS (system-wide, average per interval)", - "favorite": true, "description": "Block read operations/s positive and write operations/s negative, one series per block device.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "line", - "x": 0, - "y": [ - 2 - ], - "series": 1, "style": { + "axes": "show", "curve": "linear", - "points": "auto", - "scale": "data", - "legend": "auto", "grid": "auto", - "axes": "show" - } + "legend": "auto", + "points": "auto", + "scale": "data" + }, + "x": 0, + "y": [ + 2 + ], + "series": 1 }, "fieldConfig": { "columns": { "value": { "displayName": "I/O operations", + "description": "Read operations are positive and write operations are negative, split by device.", "unit": " ops/s", - "decimals": 1, - "description": "Read operations are positive and write operations are negative, split by device." + "decimals": 1 } } } @@ -1814,33 +1907,33 @@ "specVersion": 1, "spec": { "name": "System metrics · Queries Started (ClickHouse, average per interval)", - "favorite": true, "description": "ProfileEvent_Query from system.metric_log.", + "favorite": true, "view": "panel", "panel": { "cfg": { "type": "line", + "style": { + "axes": "show", + "curve": "linear", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "zero" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "curve": "linear", - "points": "auto", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": null }, "fieldConfig": { "columns": { "value": { "displayName": "Queries started", + "description": "Average queries started in each bucket.", "unit": " queries", - "decimals": 1, - "description": "Average queries started in each bucket." + "decimals": 1 } } } @@ -1861,34 +1954,34 @@ "specVersion": 1, "spec": { "name": "System metrics · Connections active (ClickHouse, max per interval)", - "favorite": true, "description": "TCP, HTTP, interserver, and MySQL connections from CurrentMetric columns.", + "favorite": true, "view": "panel", "panel": { "cfg": { "type": "area", - "x": 0, - "y": [ - 2 - ], - "series": 1, "style": { + "axes": "show", "curve": "linear", + "grid": "auto", + "legend": "auto", "points": "auto", - "stack": "stacked", "scale": "data", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "stack": "stacked" + }, + "x": 0, + "y": [ + 2 + ], + "series": 1 }, "fieldConfig": { "columns": { "value": { "displayName": "Active connections", + "description": "Active TCP, HTTP, interserver, and MySQL connections.", "unit": " connections", - "decimals": 0, - "description": "Active TCP, HTTP, interserver, and MySQL connections." + "decimals": 0 } } } @@ -1905,37 +1998,37 @@ }, { "id": "gco-099-error-log", - "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n toDateTime(intDiv(toUInt32(event_time), bucket_s) * bucket_s) AS t,\n concat(error, ' (', toString(code), if(remote, ' remote', ''), ')') AS series,\n toFloat64(sum(value)) AS value\nFROM merge(system, '^error_log')\nWHERE event_time >= {from:DateTime}\n AND event_time <= {to:DateTime}\n /*[ AND code = {exception_code:Int32} ]*/\nGROUP BY t, series\nORDER BY t, series", + "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n toDateTime(intDiv(toUInt32(event_time), bucket_s) * bucket_s) AS t,\n concat(error, ' (', toString(code), if(remote, ' remote', ''), ')') AS series,\n toFloat64(sum(value)) AS value\nFROM merge(system, '^error_log')\nWHERE event_time >= {from:DateTime}\n AND event_time <= {to:DateTime}\n /*[ AND code = {exception_code:Array(Int32)} ]*/\nGROUP BY t, series\nORDER BY t, series", "specVersion": 1, "spec": { "name": "error_log (24.8+) · Errors over interval", - "favorite": true, "description": "Grafana status history converted to grouped columns. Series combine error name, code, and remote flag.", + "favorite": true, "view": "panel", "panel": { "cfg": { "type": "bar", + "style": { + "axes": "show", + "density": "normal", + "grid": "auto", + "legend": "auto", + "mode": "stacked", + "scale": "zero" + }, "x": 0, "y": [ 2 ], - "series": 1, - "style": { - "mode": "stacked", - "density": "normal", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": 1 }, "fieldConfig": { "columns": { "value": { "displayName": "Errors", + "description": "Error occurrences in each time bucket.", "unit": " errors", - "decimals": 0, - "description": "Error occurrences in each time bucket." + "decimals": 0 } } } @@ -1952,29 +2045,29 @@ }, { "id": "gco-025-query-hash", - "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n t,\n series,\n value\nFROM\n(\n SELECT * FROM (\n WITH\n intDiv(toUInt32(event_time), bucket_s) AS finish_bucket,\n intDiv(toUInt32(query_start_time), bucket_s) AS start_bucket,\n arrayMap(i -> toDateTime((start_bucket + i) * bucket_s), range(toUInt32(finish_bucket - start_bucket + 1))) AS buckets\n SELECT\n arrayJoin(buckets) AS t,\n toString(normalized_query_hash) AS series,\n multiIf(\n /*[ {metric:String} = 'avg_duration', toFloat64(avg(query_duration_ms)), ]*/\n /*[ {metric:String} = 'max_duration', toFloat64(max(query_duration_ms)), ]*/\n /*[ {metric:String} = 'cpu_time', toFloat64(sum(ProfileEvents['UserTimeMicroseconds']) + sum(ProfileEvents['SystemTimeMicroseconds'])), ]*/\n /*[ {metric:String} = 'read_bytes', toFloat64(sum(read_bytes)), ]*/\n /*[ {metric:String} = 'written_bytes', toFloat64(sum(written_bytes)), ]*/\n /*[ {metric:String} = 'avg_written_rows', toFloat64(avg(written_rows)), ]*/\n /*[ {metric:String} = 'result_bytes', toFloat64(sum(result_bytes)), ]*/\n /*[ {metric:String} = 'network_bytes', toFloat64(sum(ProfileEvents['NetworkReceiveBytes']) + sum(ProfileEvents['NetworkSendBytes'])), ]*/\n /*[ {metric:String} = 'memory', toFloat64(sum(memory_usage)), ]*/\n /*[ {metric:String} = 'max_memory', toFloat64(max(memory_usage)), ]*/\n /*[ {metric:String} = 'network_wait', toFloat64((sum(ProfileEvents['NetworkSendElapsedMicroseconds']) + sum(ProfileEvents['NetworkReceiveElapsedMicroseconds'])) / 1000000), ]*/\n /*[ {metric:String} = 'io_time', toFloat64((sum(ProfileEvents['DiskReadElapsedMicroseconds']) + sum(ProfileEvents['DiskWriteElapsedMicroseconds'])) / 1000000), ]*/\n /*[ {metric:String} = 'io_wait', toFloat64(sum(ProfileEvents['OSIOWaitMicroseconds']) / 1000000), ]*/\n /*[ {metric:String} = 'zk_txns', toFloat64(sum(ProfileEvents['ZooKeeperTransactions'])), ]*/\n /*[ {metric:String} = 'read_bps', toFloat64(sum(read_bytes) * 1000 / nullIf(sum(query_duration_ms), 0)), ]*/\n /*[ {metric:String} = 'write_bps', toFloat64(sum(written_bytes) * 1000 / nullIf(sum(query_duration_ms), 0)), ]*/\n /*[ {metric:String} = 'parts_inserted', toFloat64(sum(ProfileEvents['InsertedCompactParts'] + ProfileEvents['InsertedWideParts'])), ]*/\n /*[ {metric:String} = 'parts_inserted_avg', toFloat64(avg(ProfileEvents['InsertedCompactParts'] + ProfileEvents['InsertedWideParts'])), ]*/\n /*[ {metric:String} = 'marks_load_time', toFloat64(sum(ProfileEvents['WaitMarksLoadMicroseconds'])), ]*/\n /*[ {metric:String} = 'marks_miss_rate', toFloat64(sum(ProfileEvents['MarkCacheMisses']) / nullIf(sum(ProfileEvents['MarkCacheHits']) + sum(ProfileEvents['MarkCacheMisses']), 0)), ]*/\n /*[ {metric:String} = 'selected_parts', toFloat64(sum(ProfileEvents['SelectedParts'])), ]*/\n /*[ {metric:String} = 'selected_ranges', toFloat64(sum(ProfileEvents['SelectedRanges'])), ]*/\n /*[ {metric:String} = 'selected_marks', toFloat64(sum(ProfileEvents['SelectedMarks'])), ]*/\n /*[ {metric:String} = 'exceptions', toFloat64(countIf(exception_code != 0)), ]*/\n /*[ {metric:String} = 'open_files', toFloat64(sum(ProfileEvents['FileOpen'])), ]*/\n /*[ {metric:String} = 'external_processing_files', toFloat64(sum(ProfileEvents['ExternalProcessingFilesTotal'])), ]*/\n /*[ {metric:String} = 'threads1', toFloat64(max(peak_threads_usage)), ]*/\n /*[ {metric:String} = 'threads2', toFloat64(max(length(thread_ids))), ]*/\n /*[ {metric:String} = 'threads3', toFloat64(sum(ProfileEvents['RealTimeMicroseconds']) / nullIf(1000 * sum(query_duration_ms), 0)), ]*/\n 1, toFloat64(count()),\n toFloat64(count())\n ) AS value\n FROM merge(system, '^query_log')\n WHERE event_time >= {from:DateTime} - INTERVAL 20 MINUTE\n AND event_time <= {to:DateTime} + INTERVAL 20 MINUTE\n AND type != 'QueryStart'\n /*[ AND is_initial_query = {is_initial_query:UInt8} ]*/\n /*[ AND query_kind = {query_kind:String} ]*/\n /*[ AND exception_code = {exception_code:Int32} ]*/\n /*[ AND initial_user = {user:String} ]*/\n /*[ AND normalized_query_hash = {query_hash:UInt64} ]*/\n GROUP BY t, series\n )\n ORDER BY count() OVER (PARTITION BY series) DESC, sum(value) OVER (PARTITION BY series) DESC\n LIMIT 100 BY t\n)\nWHERE t >= {from:DateTime} AND t <= {to:DateTime}\nORDER BY t, series", + "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n t,\n series,\n value\nFROM\n(\n SELECT * FROM (\n WITH\n intDiv(toUInt32(event_time), bucket_s) AS finish_bucket,\n intDiv(toUInt32(query_start_time), bucket_s) AS start_bucket,\n arrayMap(i -> toDateTime((start_bucket + i) * bucket_s), range(toUInt32(finish_bucket - start_bucket + 1))) AS buckets\n SELECT\n arrayJoin(buckets) AS t,\n toString(normalized_query_hash) AS series,\n multiIf(\n /*[ {metric:String} = 'avg_duration', toFloat64(avg(query_duration_ms)), ]*/\n /*[ {metric:String} = 'max_duration', toFloat64(max(query_duration_ms)), ]*/\n /*[ {metric:String} = 'cpu_time', toFloat64(sum(ProfileEvents['UserTimeMicroseconds']) + sum(ProfileEvents['SystemTimeMicroseconds'])), ]*/\n /*[ {metric:String} = 'read_bytes', toFloat64(sum(read_bytes)), ]*/\n /*[ {metric:String} = 'written_bytes', toFloat64(sum(written_bytes)), ]*/\n /*[ {metric:String} = 'avg_written_rows', toFloat64(avg(written_rows)), ]*/\n /*[ {metric:String} = 'result_bytes', toFloat64(sum(result_bytes)), ]*/\n /*[ {metric:String} = 'network_bytes', toFloat64(sum(ProfileEvents['NetworkReceiveBytes']) + sum(ProfileEvents['NetworkSendBytes'])), ]*/\n /*[ {metric:String} = 'memory', toFloat64(sum(memory_usage)), ]*/\n /*[ {metric:String} = 'max_memory', toFloat64(max(memory_usage)), ]*/\n /*[ {metric:String} = 'network_wait', toFloat64((sum(ProfileEvents['NetworkSendElapsedMicroseconds']) + sum(ProfileEvents['NetworkReceiveElapsedMicroseconds'])) / 1000000), ]*/\n /*[ {metric:String} = 'io_time', toFloat64((sum(ProfileEvents['DiskReadElapsedMicroseconds']) + sum(ProfileEvents['DiskWriteElapsedMicroseconds'])) / 1000000), ]*/\n /*[ {metric:String} = 'io_wait', toFloat64(sum(ProfileEvents['OSIOWaitMicroseconds']) / 1000000), ]*/\n /*[ {metric:String} = 'zk_txns', toFloat64(sum(ProfileEvents['ZooKeeperTransactions'])), ]*/\n /*[ {metric:String} = 'read_bps', toFloat64(sum(read_bytes) * 1000 / nullIf(sum(query_duration_ms), 0)), ]*/\n /*[ {metric:String} = 'write_bps', toFloat64(sum(written_bytes) * 1000 / nullIf(sum(query_duration_ms), 0)), ]*/\n /*[ {metric:String} = 'parts_inserted', toFloat64(sum(ProfileEvents['InsertedCompactParts'] + ProfileEvents['InsertedWideParts'])), ]*/\n /*[ {metric:String} = 'parts_inserted_avg', toFloat64(avg(ProfileEvents['InsertedCompactParts'] + ProfileEvents['InsertedWideParts'])), ]*/\n /*[ {metric:String} = 'marks_load_time', toFloat64(sum(ProfileEvents['WaitMarksLoadMicroseconds'])), ]*/\n /*[ {metric:String} = 'marks_miss_rate', toFloat64(sum(ProfileEvents['MarkCacheMisses']) / nullIf(sum(ProfileEvents['MarkCacheHits']) + sum(ProfileEvents['MarkCacheMisses']), 0)), ]*/\n /*[ {metric:String} = 'selected_parts', toFloat64(sum(ProfileEvents['SelectedParts'])), ]*/\n /*[ {metric:String} = 'selected_ranges', toFloat64(sum(ProfileEvents['SelectedRanges'])), ]*/\n /*[ {metric:String} = 'selected_marks', toFloat64(sum(ProfileEvents['SelectedMarks'])), ]*/\n /*[ {metric:String} = 'exceptions', toFloat64(countIf(exception_code != 0)), ]*/\n /*[ {metric:String} = 'open_files', toFloat64(sum(ProfileEvents['FileOpen'])), ]*/\n /*[ {metric:String} = 'external_processing_files', toFloat64(sum(ProfileEvents['ExternalProcessingFilesTotal'])), ]*/\n /*[ {metric:String} = 'threads1', toFloat64(max(peak_threads_usage)), ]*/\n /*[ {metric:String} = 'threads2', toFloat64(max(length(thread_ids))), ]*/\n /*[ {metric:String} = 'threads3', toFloat64(sum(ProfileEvents['RealTimeMicroseconds']) / nullIf(1000 * sum(query_duration_ms), 0)), ]*/\n 1, toFloat64(count()),\n toFloat64(count())\n ) AS value\n FROM merge(system, '^query_log')\n WHERE event_time >= {from:DateTime} - INTERVAL 20 MINUTE\n AND event_time <= {to:DateTime} + INTERVAL 20 MINUTE\n AND type != 'QueryStart'\n /*[ AND is_initial_query = {is_initial_query:UInt8} ]*/\n /*[ AND has({query_kind:Array(String)}, query_kind) ]*/\n /*[ AND has({exception_code:Array(Int32)}, exception_code) ]*/\n /*[ AND has({user:Array(String)}, initial_user) ]*/\n /*[ AND has({query_hash:Array(UInt64)}, normalized_query_hash) ]*/\n GROUP BY t, series\n )\n ORDER BY count() OVER (PARTITION BY series) DESC, sum(value) OVER (PARTITION BY series) DESC\n LIMIT 100 BY t\n)\nWHERE t >= {from:DateTime} AND t <= {to:DateTime}\nORDER BY t, series", "specVersion": 1, "spec": { "name": "query_log by query hash · Selected metric by normalized query hash", - "favorite": true, "description": "Grafana status history converted to grouped columns. Long-running queries are represented in every automatic bucket they overlap. Blank metric means count.", + "favorite": true, "view": "panel", "panel": { "cfg": { "type": "bar", + "style": { + "axes": "show", + "density": "normal", + "grid": "auto", + "legend": "auto", + "mode": "stacked", + "scale": "zero" + }, "x": 0, "y": [ 2 ], - "series": 1, - "style": { - "mode": "stacked", - "density": "normal", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": 1 }, "fieldConfig": { "columns": { @@ -1997,12 +2090,12 @@ }, { "id": "gco-030-query-hash-details", - "sql": "SELECT\n toString(normalized_query_hash) AS query_hash,\n argMax(query_id, ProfileEvents['OSCPUVirtualTimeMicroseconds']) AS sample_query_id,\n replaceAll(argMax(query, ProfileEvents['OSCPUVirtualTimeMicroseconds']), '\\n', ' ') AS query,\n count() AS count,\n max(query_duration_ms) AS max_duration_ms,\n avg(query_duration_ms) AS avg_duration_ms,\n sum(ProfileEvents['OSCPUVirtualTimeMicroseconds']) / 1000000 AS cpu_time_s,\n quantile(0.99)(memory_usage) AS memory_q99,\n sum(read_rows) AS read_rows,\n sum(read_bytes) AS read_bytes,\n sum(written_rows) AS total_written_rows,\n avg(written_rows) AS avg_written_rows,\n sum(written_bytes) AS written_bytes,\n sum(result_rows) AS result_rows,\n sum(result_bytes) AS result_bytes,\n sum(ProfileEvents['NetworkReceiveBytes']) AS network_receive_bytes,\n sum(ProfileEvents['NetworkSendBytes']) AS network_send_bytes,\n sum(ProfileEvents['OSCPUVirtualTimeMicroseconds']) / nullIf(1000 * sum(query_duration_ms), 0) AS cpu_usage,\n sum(ProfileEvents['OSIOWaitMicroseconds']) / nullIf(sum(ProfileEvents['RealTimeMicroseconds']), 0) AS io_wait_ratio,\n sum(ProfileEvents['RealTimeMicroseconds']) / nullIf(1000 * sum(query_duration_ms), 0) AS concurrency,\n arrayStringConcat(groupUniqArrayIf(5)(errorCodeToName(exception_code), exception_code != 0), ',') AS exceptions,\n arrayStringConcat(groupUniqArray(5)(initial_user), ',') AS users,\n sum(ProfileEvents['OSIOWaitMicroseconds']) / 1000000 AS os_io_wait_s,\n sum(ProfileEvents['DiskReadElapsedMicroseconds']) / 1000000 AS disk_read_s,\n sum(ProfileEvents['DiskWriteElapsedMicroseconds']) / 1000000 AS disk_write_s,\n sum(ProfileEvents['RealTimeMicroseconds']) / 1000000 AS real_time_s,\n sum(ProfileEvents['UserTimeMicroseconds']) / 1000000 AS user_time_s,\n sum(ProfileEvents['SystemTimeMicroseconds']) / 1000000 AS system_time_s,\n sum(ProfileEvents['NetworkSendElapsedMicroseconds']) / 1000000 AS network_send_s,\n sum(ProfileEvents['NetworkReceiveElapsedMicroseconds']) / 1000000 AS network_receive_s,\n sum(ProfileEvents['SelectedParts']) AS selected_parts,\n sum(ProfileEvents['SelectedRanges']) AS selected_ranges,\n sum(ProfileEvents['SelectedMarks']) AS selected_marks,\n sum(ProfileEvents['SelectedRows']) AS selected_rows,\n sum(ProfileEvents['SelectedBytes']) AS selected_bytes,\n sum(ProfileEvents['FileOpen']) AS file_open,\n sum(ProfileEvents['ZooKeeperTransactions']) AS zookeeper_transactions,\n sum(ProfileEvents['OSReadBytes']) AS os_read_bytes_excluding_page_cache,\n sum(ProfileEvents['OSWriteBytes']) AS os_write_bytes_excluding_page_cache,\n sum(ProfileEvents['OSReadChars']) AS os_read_chars_including_page_cache,\n sum(ProfileEvents['OSWriteChars']) AS os_write_chars_including_page_cache,\n anyIf(exception, exception != '') AS exception_sample,\n min(event_time) AS min_event_time,\n max(event_time) AS max_event_time\nFROM merge(system, '^query_log')\nWHERE event_time >= {from:DateTime}\n AND event_time <= {to:DateTime}\n AND type != 'QueryStart'\n /*[ AND is_initial_query = {is_initial_query:UInt8} ]*/\n /*[ AND query_kind = {query_kind:String} ]*/\n /*[ AND exception_code = {exception_code:Int32} ]*/\n /*[ AND initial_user = {user:String} ]*/\n /*[ AND normalized_query_hash = {query_hash:UInt64} ]*/\nGROUP BY normalized_query_hash\nORDER BY count DESC\nLIMIT 200", + "sql": "SELECT\n toString(normalized_query_hash) AS query_hash,\n argMax(query_id, ProfileEvents['OSCPUVirtualTimeMicroseconds']) AS sample_query_id,\n replaceAll(argMax(query, ProfileEvents['OSCPUVirtualTimeMicroseconds']), '\\n', ' ') AS query,\n count() AS count,\n max(query_duration_ms) AS max_duration_ms,\n avg(query_duration_ms) AS avg_duration_ms,\n sum(ProfileEvents['OSCPUVirtualTimeMicroseconds']) / 1000000 AS cpu_time_s,\n quantile(0.99)(memory_usage) AS memory_q99,\n sum(read_rows) AS read_rows,\n sum(read_bytes) AS read_bytes,\n sum(written_rows) AS total_written_rows,\n avg(written_rows) AS avg_written_rows,\n sum(written_bytes) AS written_bytes,\n sum(result_rows) AS result_rows,\n sum(result_bytes) AS result_bytes,\n sum(ProfileEvents['NetworkReceiveBytes']) AS network_receive_bytes,\n sum(ProfileEvents['NetworkSendBytes']) AS network_send_bytes,\n sum(ProfileEvents['OSCPUVirtualTimeMicroseconds']) / nullIf(1000 * sum(query_duration_ms), 0) AS cpu_usage,\n sum(ProfileEvents['OSIOWaitMicroseconds']) / nullIf(sum(ProfileEvents['RealTimeMicroseconds']), 0) AS io_wait_ratio,\n sum(ProfileEvents['RealTimeMicroseconds']) / nullIf(1000 * sum(query_duration_ms), 0) AS concurrency,\n arrayStringConcat(groupUniqArrayIf(5)(errorCodeToName(exception_code), exception_code != 0), ',') AS exceptions,\n arrayStringConcat(groupUniqArray(5)(initial_user), ',') AS users,\n sum(ProfileEvents['OSIOWaitMicroseconds']) / 1000000 AS os_io_wait_s,\n sum(ProfileEvents['DiskReadElapsedMicroseconds']) / 1000000 AS disk_read_s,\n sum(ProfileEvents['DiskWriteElapsedMicroseconds']) / 1000000 AS disk_write_s,\n sum(ProfileEvents['RealTimeMicroseconds']) / 1000000 AS real_time_s,\n sum(ProfileEvents['UserTimeMicroseconds']) / 1000000 AS user_time_s,\n sum(ProfileEvents['SystemTimeMicroseconds']) / 1000000 AS system_time_s,\n sum(ProfileEvents['NetworkSendElapsedMicroseconds']) / 1000000 AS network_send_s,\n sum(ProfileEvents['NetworkReceiveElapsedMicroseconds']) / 1000000 AS network_receive_s,\n sum(ProfileEvents['SelectedParts']) AS selected_parts,\n sum(ProfileEvents['SelectedRanges']) AS selected_ranges,\n sum(ProfileEvents['SelectedMarks']) AS selected_marks,\n sum(ProfileEvents['SelectedRows']) AS selected_rows,\n sum(ProfileEvents['SelectedBytes']) AS selected_bytes,\n sum(ProfileEvents['FileOpen']) AS file_open,\n sum(ProfileEvents['ZooKeeperTransactions']) AS zookeeper_transactions,\n sum(ProfileEvents['OSReadBytes']) AS os_read_bytes_excluding_page_cache,\n sum(ProfileEvents['OSWriteBytes']) AS os_write_bytes_excluding_page_cache,\n sum(ProfileEvents['OSReadChars']) AS os_read_chars_including_page_cache,\n sum(ProfileEvents['OSWriteChars']) AS os_write_chars_including_page_cache,\n anyIf(exception, exception != '') AS exception_sample,\n min(event_time) AS min_event_time,\n max(event_time) AS max_event_time\nFROM merge(system, '^query_log')\nWHERE event_time >= {from:DateTime}\n AND event_time <= {to:DateTime}\n AND type != 'QueryStart'\n /*[ AND is_initial_query = {is_initial_query:UInt8} ]*/\n /*[ AND has({query_kind:Array(String)}, query_kind) ]*/\n /*[ AND has({exception_code:Array(Int32)}, exception_code) ]*/\n /*[ AND has({user:Array(String)}, initial_user) ]*/\n /*[ AND has({query_hash:Array(UInt64)}, normalized_query_hash) ]*/\nGROUP BY normalized_query_hash\nORDER BY count DESC\nLIMIT 200", "specVersion": 1, "spec": { "name": "query_log by query hash DETAILS · Query hash details", - "favorite": true, "description": "Resource and timing breakdown per normalized query hash, adapted to a single node and SQL Browser parameters.", + "favorite": true, "view": "panel", "panel": { "cfg": { @@ -2021,29 +2114,29 @@ }, { "id": "gco-056-query-table", - "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n t,\n series,\n value\nFROM\n(\n SELECT * FROM (\n WITH\n intDiv(toUInt32(event_time), bucket_s) AS finish_bucket,\n intDiv(toUInt32(query_start_time), bucket_s) AS start_bucket,\n arrayMap(i -> toDateTime((start_bucket + i) * bucket_s), range(toUInt32(finish_bucket - start_bucket + 1))) AS buckets\n SELECT\n arrayJoin(buckets) AS t,\n arrayJoin(arrayFilter(name -> name NOT LIKE '%temporary%', tables)) AS series,\n multiIf(\n /*[ {metric:String} = 'avg_duration', toFloat64(avg(query_duration_ms)), ]*/\n /*[ {metric:String} = 'max_duration', toFloat64(max(query_duration_ms)), ]*/\n /*[ {metric:String} = 'cpu_time', toFloat64(sum(ProfileEvents['UserTimeMicroseconds']) + sum(ProfileEvents['SystemTimeMicroseconds'])), ]*/\n /*[ {metric:String} = 'read_bytes', toFloat64(sum(read_bytes)), ]*/\n /*[ {metric:String} = 'written_bytes', toFloat64(sum(written_bytes)), ]*/\n /*[ {metric:String} = 'avg_written_rows', toFloat64(avg(written_rows)), ]*/\n /*[ {metric:String} = 'result_bytes', toFloat64(sum(result_bytes)), ]*/\n /*[ {metric:String} = 'network_bytes', toFloat64(sum(ProfileEvents['NetworkReceiveBytes']) + sum(ProfileEvents['NetworkSendBytes'])), ]*/\n /*[ {metric:String} = 'memory', toFloat64(sum(memory_usage)), ]*/\n /*[ {metric:String} = 'max_memory', toFloat64(max(memory_usage)), ]*/\n /*[ {metric:String} = 'network_wait', toFloat64((sum(ProfileEvents['NetworkSendElapsedMicroseconds']) + sum(ProfileEvents['NetworkReceiveElapsedMicroseconds'])) / 1000000), ]*/\n /*[ {metric:String} = 'io_time', toFloat64((sum(ProfileEvents['DiskReadElapsedMicroseconds']) + sum(ProfileEvents['DiskWriteElapsedMicroseconds'])) / 1000000), ]*/\n /*[ {metric:String} = 'io_wait', toFloat64(sum(ProfileEvents['OSIOWaitMicroseconds']) / 1000000), ]*/\n /*[ {metric:String} = 'zk_txns', toFloat64(sum(ProfileEvents['ZooKeeperTransactions'])), ]*/\n /*[ {metric:String} = 'read_bps', toFloat64(sum(read_bytes) * 1000 / nullIf(sum(query_duration_ms), 0)), ]*/\n /*[ {metric:String} = 'write_bps', toFloat64(sum(written_bytes) * 1000 / nullIf(sum(query_duration_ms), 0)), ]*/\n /*[ {metric:String} = 'parts_inserted', toFloat64(sum(ProfileEvents['InsertedCompactParts'] + ProfileEvents['InsertedWideParts'])), ]*/\n /*[ {metric:String} = 'parts_inserted_avg', toFloat64(avg(ProfileEvents['InsertedCompactParts'] + ProfileEvents['InsertedWideParts'])), ]*/\n /*[ {metric:String} = 'marks_load_time', toFloat64(sum(ProfileEvents['WaitMarksLoadMicroseconds'])), ]*/\n /*[ {metric:String} = 'marks_miss_rate', toFloat64(sum(ProfileEvents['MarkCacheMisses']) / nullIf(sum(ProfileEvents['MarkCacheHits']) + sum(ProfileEvents['MarkCacheMisses']), 0)), ]*/\n /*[ {metric:String} = 'selected_parts', toFloat64(sum(ProfileEvents['SelectedParts'])), ]*/\n /*[ {metric:String} = 'selected_ranges', toFloat64(sum(ProfileEvents['SelectedRanges'])), ]*/\n /*[ {metric:String} = 'selected_marks', toFloat64(sum(ProfileEvents['SelectedMarks'])), ]*/\n /*[ {metric:String} = 'exceptions', toFloat64(countIf(exception_code != 0)), ]*/\n /*[ {metric:String} = 'open_files', toFloat64(sum(ProfileEvents['FileOpen'])), ]*/\n /*[ {metric:String} = 'external_processing_files', toFloat64(sum(ProfileEvents['ExternalProcessingFilesTotal'])), ]*/\n /*[ {metric:String} = 'threads1', toFloat64(max(peak_threads_usage)), ]*/\n /*[ {metric:String} = 'threads2', toFloat64(max(length(thread_ids))), ]*/\n /*[ {metric:String} = 'threads3', toFloat64(sum(ProfileEvents['RealTimeMicroseconds']) / nullIf(1000 * sum(query_duration_ms), 0)), ]*/\n 1, toFloat64(count()),\n toFloat64(count())\n ) AS value\n FROM merge(system, '^query_log')\n WHERE event_time >= {from:DateTime} - INTERVAL 20 MINUTE\n AND event_time <= {to:DateTime} + INTERVAL 20 MINUTE\n AND type != 'QueryStart'\n /*[ AND is_initial_query = {is_initial_query:UInt8} ]*/\n /*[ AND query_kind = {query_kind:String} ]*/\n /*[ AND exception_code = {exception_code:Int32} ]*/\n /*[ AND initial_user = {user:String} ]*/\n /*[ AND normalized_query_hash = {query_hash:UInt64} ]*/\n GROUP BY t, series\n )\n ORDER BY count() OVER (PARTITION BY series) DESC, sum(value) OVER (PARTITION BY series) DESC\n LIMIT 50 BY t\n)\nWHERE t >= {from:DateTime} AND t <= {to:DateTime}\nORDER BY t, series", + "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n t,\n series,\n value\nFROM\n(\n SELECT * FROM (\n WITH\n intDiv(toUInt32(event_time), bucket_s) AS finish_bucket,\n intDiv(toUInt32(query_start_time), bucket_s) AS start_bucket,\n arrayMap(i -> toDateTime((start_bucket + i) * bucket_s), range(toUInt32(finish_bucket - start_bucket + 1))) AS buckets\n SELECT\n arrayJoin(buckets) AS t,\n arrayJoin(arrayFilter(name -> name NOT LIKE '%temporary%', tables)) AS series,\n multiIf(\n /*[ {metric:String} = 'avg_duration', toFloat64(avg(query_duration_ms)), ]*/\n /*[ {metric:String} = 'max_duration', toFloat64(max(query_duration_ms)), ]*/\n /*[ {metric:String} = 'cpu_time', toFloat64(sum(ProfileEvents['UserTimeMicroseconds']) + sum(ProfileEvents['SystemTimeMicroseconds'])), ]*/\n /*[ {metric:String} = 'read_bytes', toFloat64(sum(read_bytes)), ]*/\n /*[ {metric:String} = 'written_bytes', toFloat64(sum(written_bytes)), ]*/\n /*[ {metric:String} = 'avg_written_rows', toFloat64(avg(written_rows)), ]*/\n /*[ {metric:String} = 'result_bytes', toFloat64(sum(result_bytes)), ]*/\n /*[ {metric:String} = 'network_bytes', toFloat64(sum(ProfileEvents['NetworkReceiveBytes']) + sum(ProfileEvents['NetworkSendBytes'])), ]*/\n /*[ {metric:String} = 'memory', toFloat64(sum(memory_usage)), ]*/\n /*[ {metric:String} = 'max_memory', toFloat64(max(memory_usage)), ]*/\n /*[ {metric:String} = 'network_wait', toFloat64((sum(ProfileEvents['NetworkSendElapsedMicroseconds']) + sum(ProfileEvents['NetworkReceiveElapsedMicroseconds'])) / 1000000), ]*/\n /*[ {metric:String} = 'io_time', toFloat64((sum(ProfileEvents['DiskReadElapsedMicroseconds']) + sum(ProfileEvents['DiskWriteElapsedMicroseconds'])) / 1000000), ]*/\n /*[ {metric:String} = 'io_wait', toFloat64(sum(ProfileEvents['OSIOWaitMicroseconds']) / 1000000), ]*/\n /*[ {metric:String} = 'zk_txns', toFloat64(sum(ProfileEvents['ZooKeeperTransactions'])), ]*/\n /*[ {metric:String} = 'read_bps', toFloat64(sum(read_bytes) * 1000 / nullIf(sum(query_duration_ms), 0)), ]*/\n /*[ {metric:String} = 'write_bps', toFloat64(sum(written_bytes) * 1000 / nullIf(sum(query_duration_ms), 0)), ]*/\n /*[ {metric:String} = 'parts_inserted', toFloat64(sum(ProfileEvents['InsertedCompactParts'] + ProfileEvents['InsertedWideParts'])), ]*/\n /*[ {metric:String} = 'parts_inserted_avg', toFloat64(avg(ProfileEvents['InsertedCompactParts'] + ProfileEvents['InsertedWideParts'])), ]*/\n /*[ {metric:String} = 'marks_load_time', toFloat64(sum(ProfileEvents['WaitMarksLoadMicroseconds'])), ]*/\n /*[ {metric:String} = 'marks_miss_rate', toFloat64(sum(ProfileEvents['MarkCacheMisses']) / nullIf(sum(ProfileEvents['MarkCacheHits']) + sum(ProfileEvents['MarkCacheMisses']), 0)), ]*/\n /*[ {metric:String} = 'selected_parts', toFloat64(sum(ProfileEvents['SelectedParts'])), ]*/\n /*[ {metric:String} = 'selected_ranges', toFloat64(sum(ProfileEvents['SelectedRanges'])), ]*/\n /*[ {metric:String} = 'selected_marks', toFloat64(sum(ProfileEvents['SelectedMarks'])), ]*/\n /*[ {metric:String} = 'exceptions', toFloat64(countIf(exception_code != 0)), ]*/\n /*[ {metric:String} = 'open_files', toFloat64(sum(ProfileEvents['FileOpen'])), ]*/\n /*[ {metric:String} = 'external_processing_files', toFloat64(sum(ProfileEvents['ExternalProcessingFilesTotal'])), ]*/\n /*[ {metric:String} = 'threads1', toFloat64(max(peak_threads_usage)), ]*/\n /*[ {metric:String} = 'threads2', toFloat64(max(length(thread_ids))), ]*/\n /*[ {metric:String} = 'threads3', toFloat64(sum(ProfileEvents['RealTimeMicroseconds']) / nullIf(1000 * sum(query_duration_ms), 0)), ]*/\n 1, toFloat64(count()),\n toFloat64(count())\n ) AS value\n FROM merge(system, '^query_log')\n WHERE event_time >= {from:DateTime} - INTERVAL 20 MINUTE\n AND event_time <= {to:DateTime} + INTERVAL 20 MINUTE\n AND type != 'QueryStart'\n /*[ AND is_initial_query = {is_initial_query:UInt8} ]*/\n /*[ AND has({query_kind:Array(String)}, query_kind) ]*/\n /*[ AND has({exception_code:Array(Int32)}, exception_code) ]*/\n /*[ AND has({user:Array(String)}, initial_user) ]*/\n /*[ AND has({query_hash:Array(UInt64)}, normalized_query_hash) ]*/\n GROUP BY t, series\n )\n ORDER BY count() OVER (PARTITION BY series) DESC, sum(value) OVER (PARTITION BY series) DESC\n LIMIT 50 BY t\n)\nWHERE t >= {from:DateTime} AND t <= {to:DateTime}\nORDER BY t, series", "specVersion": 1, "spec": { "name": "query_log by table · Selected metric by table", - "favorite": true, "description": "Grafana status history converted to grouped columns. One query touching several tables contributes to each table series.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "bar", + "style": { + "axes": "show", + "density": "normal", + "grid": "auto", + "legend": "auto", + "mode": "stacked", + "scale": "zero" + }, "x": 0, "y": [ 2 ], - "series": 1, - "style": { - "mode": "stacked", - "density": "normal", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": 1 }, "fieldConfig": { "columns": { @@ -2066,29 +2159,29 @@ }, { "id": "gco-037-query-user", - "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n t,\n series,\n value\nFROM\n(\n SELECT * FROM (\n WITH\n intDiv(toUInt32(event_time), bucket_s) AS finish_bucket,\n intDiv(toUInt32(query_start_time), bucket_s) AS start_bucket,\n arrayMap(i -> toDateTime((start_bucket + i) * bucket_s), range(toUInt32(finish_bucket - start_bucket + 1))) AS buckets\n SELECT\n arrayJoin(buckets) AS t,\n if(initial_user = '', '', initial_user) AS series,\n multiIf(\n /*[ {metric:String} = 'avg_duration', toFloat64(avg(query_duration_ms)), ]*/\n /*[ {metric:String} = 'max_duration', toFloat64(max(query_duration_ms)), ]*/\n /*[ {metric:String} = 'cpu_time', toFloat64(sum(ProfileEvents['UserTimeMicroseconds']) + sum(ProfileEvents['SystemTimeMicroseconds'])), ]*/\n /*[ {metric:String} = 'read_bytes', toFloat64(sum(read_bytes)), ]*/\n /*[ {metric:String} = 'written_bytes', toFloat64(sum(written_bytes)), ]*/\n /*[ {metric:String} = 'avg_written_rows', toFloat64(avg(written_rows)), ]*/\n /*[ {metric:String} = 'result_bytes', toFloat64(sum(result_bytes)), ]*/\n /*[ {metric:String} = 'network_bytes', toFloat64(sum(ProfileEvents['NetworkReceiveBytes']) + sum(ProfileEvents['NetworkSendBytes'])), ]*/\n /*[ {metric:String} = 'memory', toFloat64(sum(memory_usage)), ]*/\n /*[ {metric:String} = 'max_memory', toFloat64(max(memory_usage)), ]*/\n /*[ {metric:String} = 'network_wait', toFloat64((sum(ProfileEvents['NetworkSendElapsedMicroseconds']) + sum(ProfileEvents['NetworkReceiveElapsedMicroseconds'])) / 1000000), ]*/\n /*[ {metric:String} = 'io_time', toFloat64((sum(ProfileEvents['DiskReadElapsedMicroseconds']) + sum(ProfileEvents['DiskWriteElapsedMicroseconds'])) / 1000000), ]*/\n /*[ {metric:String} = 'io_wait', toFloat64(sum(ProfileEvents['OSIOWaitMicroseconds']) / 1000000), ]*/\n /*[ {metric:String} = 'zk_txns', toFloat64(sum(ProfileEvents['ZooKeeperTransactions'])), ]*/\n /*[ {metric:String} = 'read_bps', toFloat64(sum(read_bytes) * 1000 / nullIf(sum(query_duration_ms), 0)), ]*/\n /*[ {metric:String} = 'write_bps', toFloat64(sum(written_bytes) * 1000 / nullIf(sum(query_duration_ms), 0)), ]*/\n /*[ {metric:String} = 'parts_inserted', toFloat64(sum(ProfileEvents['InsertedCompactParts'] + ProfileEvents['InsertedWideParts'])), ]*/\n /*[ {metric:String} = 'parts_inserted_avg', toFloat64(avg(ProfileEvents['InsertedCompactParts'] + ProfileEvents['InsertedWideParts'])), ]*/\n /*[ {metric:String} = 'marks_load_time', toFloat64(sum(ProfileEvents['WaitMarksLoadMicroseconds'])), ]*/\n /*[ {metric:String} = 'marks_miss_rate', toFloat64(sum(ProfileEvents['MarkCacheMisses']) / nullIf(sum(ProfileEvents['MarkCacheHits']) + sum(ProfileEvents['MarkCacheMisses']), 0)), ]*/\n /*[ {metric:String} = 'selected_parts', toFloat64(sum(ProfileEvents['SelectedParts'])), ]*/\n /*[ {metric:String} = 'selected_ranges', toFloat64(sum(ProfileEvents['SelectedRanges'])), ]*/\n /*[ {metric:String} = 'selected_marks', toFloat64(sum(ProfileEvents['SelectedMarks'])), ]*/\n /*[ {metric:String} = 'exceptions', toFloat64(countIf(exception_code != 0)), ]*/\n /*[ {metric:String} = 'open_files', toFloat64(sum(ProfileEvents['FileOpen'])), ]*/\n /*[ {metric:String} = 'external_processing_files', toFloat64(sum(ProfileEvents['ExternalProcessingFilesTotal'])), ]*/\n /*[ {metric:String} = 'threads1', toFloat64(max(peak_threads_usage)), ]*/\n /*[ {metric:String} = 'threads2', toFloat64(max(length(thread_ids))), ]*/\n /*[ {metric:String} = 'threads3', toFloat64(sum(ProfileEvents['RealTimeMicroseconds']) / nullIf(1000 * sum(query_duration_ms), 0)), ]*/\n 1, toFloat64(count()),\n toFloat64(count())\n ) AS value\n FROM merge(system, '^query_log')\n WHERE event_time >= {from:DateTime} - INTERVAL 20 MINUTE\n AND event_time <= {to:DateTime} + INTERVAL 20 MINUTE\n AND type != 'QueryStart'\n /*[ AND is_initial_query = {is_initial_query:UInt8} ]*/\n /*[ AND query_kind = {query_kind:String} ]*/\n /*[ AND exception_code = {exception_code:Int32} ]*/\n /*[ AND initial_user = {user:String} ]*/\n /*[ AND normalized_query_hash = {query_hash:UInt64} ]*/\n GROUP BY t, series\n )\n ORDER BY count() OVER (PARTITION BY series) DESC, sum(value) OVER (PARTITION BY series) DESC\n LIMIT 100 BY t\n)\nWHERE t >= {from:DateTime} AND t <= {to:DateTime}\nORDER BY t, series", + "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n t,\n series,\n value\nFROM\n(\n SELECT * FROM (\n WITH\n intDiv(toUInt32(event_time), bucket_s) AS finish_bucket,\n intDiv(toUInt32(query_start_time), bucket_s) AS start_bucket,\n arrayMap(i -> toDateTime((start_bucket + i) * bucket_s), range(toUInt32(finish_bucket - start_bucket + 1))) AS buckets\n SELECT\n arrayJoin(buckets) AS t,\n if(initial_user = '', '', initial_user) AS series,\n multiIf(\n /*[ {metric:String} = 'avg_duration', toFloat64(avg(query_duration_ms)), ]*/\n /*[ {metric:String} = 'max_duration', toFloat64(max(query_duration_ms)), ]*/\n /*[ {metric:String} = 'cpu_time', toFloat64(sum(ProfileEvents['UserTimeMicroseconds']) + sum(ProfileEvents['SystemTimeMicroseconds'])), ]*/\n /*[ {metric:String} = 'read_bytes', toFloat64(sum(read_bytes)), ]*/\n /*[ {metric:String} = 'written_bytes', toFloat64(sum(written_bytes)), ]*/\n /*[ {metric:String} = 'avg_written_rows', toFloat64(avg(written_rows)), ]*/\n /*[ {metric:String} = 'result_bytes', toFloat64(sum(result_bytes)), ]*/\n /*[ {metric:String} = 'network_bytes', toFloat64(sum(ProfileEvents['NetworkReceiveBytes']) + sum(ProfileEvents['NetworkSendBytes'])), ]*/\n /*[ {metric:String} = 'memory', toFloat64(sum(memory_usage)), ]*/\n /*[ {metric:String} = 'max_memory', toFloat64(max(memory_usage)), ]*/\n /*[ {metric:String} = 'network_wait', toFloat64((sum(ProfileEvents['NetworkSendElapsedMicroseconds']) + sum(ProfileEvents['NetworkReceiveElapsedMicroseconds'])) / 1000000), ]*/\n /*[ {metric:String} = 'io_time', toFloat64((sum(ProfileEvents['DiskReadElapsedMicroseconds']) + sum(ProfileEvents['DiskWriteElapsedMicroseconds'])) / 1000000), ]*/\n /*[ {metric:String} = 'io_wait', toFloat64(sum(ProfileEvents['OSIOWaitMicroseconds']) / 1000000), ]*/\n /*[ {metric:String} = 'zk_txns', toFloat64(sum(ProfileEvents['ZooKeeperTransactions'])), ]*/\n /*[ {metric:String} = 'read_bps', toFloat64(sum(read_bytes) * 1000 / nullIf(sum(query_duration_ms), 0)), ]*/\n /*[ {metric:String} = 'write_bps', toFloat64(sum(written_bytes) * 1000 / nullIf(sum(query_duration_ms), 0)), ]*/\n /*[ {metric:String} = 'parts_inserted', toFloat64(sum(ProfileEvents['InsertedCompactParts'] + ProfileEvents['InsertedWideParts'])), ]*/\n /*[ {metric:String} = 'parts_inserted_avg', toFloat64(avg(ProfileEvents['InsertedCompactParts'] + ProfileEvents['InsertedWideParts'])), ]*/\n /*[ {metric:String} = 'marks_load_time', toFloat64(sum(ProfileEvents['WaitMarksLoadMicroseconds'])), ]*/\n /*[ {metric:String} = 'marks_miss_rate', toFloat64(sum(ProfileEvents['MarkCacheMisses']) / nullIf(sum(ProfileEvents['MarkCacheHits']) + sum(ProfileEvents['MarkCacheMisses']), 0)), ]*/\n /*[ {metric:String} = 'selected_parts', toFloat64(sum(ProfileEvents['SelectedParts'])), ]*/\n /*[ {metric:String} = 'selected_ranges', toFloat64(sum(ProfileEvents['SelectedRanges'])), ]*/\n /*[ {metric:String} = 'selected_marks', toFloat64(sum(ProfileEvents['SelectedMarks'])), ]*/\n /*[ {metric:String} = 'exceptions', toFloat64(countIf(exception_code != 0)), ]*/\n /*[ {metric:String} = 'open_files', toFloat64(sum(ProfileEvents['FileOpen'])), ]*/\n /*[ {metric:String} = 'external_processing_files', toFloat64(sum(ProfileEvents['ExternalProcessingFilesTotal'])), ]*/\n /*[ {metric:String} = 'threads1', toFloat64(max(peak_threads_usage)), ]*/\n /*[ {metric:String} = 'threads2', toFloat64(max(length(thread_ids))), ]*/\n /*[ {metric:String} = 'threads3', toFloat64(sum(ProfileEvents['RealTimeMicroseconds']) / nullIf(1000 * sum(query_duration_ms), 0)), ]*/\n 1, toFloat64(count()),\n toFloat64(count())\n ) AS value\n FROM merge(system, '^query_log')\n WHERE event_time >= {from:DateTime} - INTERVAL 20 MINUTE\n AND event_time <= {to:DateTime} + INTERVAL 20 MINUTE\n AND type != 'QueryStart'\n /*[ AND is_initial_query = {is_initial_query:UInt8} ]*/\n /*[ AND has({query_kind:Array(String)}, query_kind) ]*/\n /*[ AND has({exception_code:Array(Int32)}, exception_code) ]*/\n /*[ AND has({user:Array(String)}, initial_user) ]*/\n /*[ AND has({query_hash:Array(UInt64)}, normalized_query_hash) ]*/\n GROUP BY t, series\n )\n ORDER BY count() OVER (PARTITION BY series) DESC, sum(value) OVER (PARTITION BY series) DESC\n LIMIT 100 BY t\n)\nWHERE t >= {from:DateTime} AND t <= {to:DateTime}\nORDER BY t, series", "specVersion": 1, "spec": { "name": "query_log by user · Selected metric by initial user", - "favorite": true, "description": "Grafana status history converted to grouped columns and grouped by initial_user.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "bar", + "style": { + "axes": "show", + "density": "normal", + "grid": "auto", + "legend": "auto", + "mode": "stacked", + "scale": "zero" + }, "x": 0, "y": [ 2 ], - "series": 1, - "style": { - "mode": "stacked", - "density": "normal", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": 1 }, "fieldConfig": { "columns": { @@ -2111,29 +2204,29 @@ }, { "id": "gco-026-query-host", - "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n t,\n series,\n value\nFROM\n(\n SELECT * FROM (\n WITH\n intDiv(toUInt32(event_time), bucket_s) AS finish_bucket,\n intDiv(toUInt32(query_start_time), bucket_s) AS start_bucket,\n arrayMap(i -> toDateTime((start_bucket + i) * bucket_s), range(toUInt32(finish_bucket - start_bucket + 1))) AS buckets\n SELECT\n arrayJoin(buckets) AS t,\n hostName() AS series,\n multiIf(\n /*[ {metric:String} = 'avg_duration', toFloat64(avg(query_duration_ms)), ]*/\n /*[ {metric:String} = 'max_duration', toFloat64(max(query_duration_ms)), ]*/\n /*[ {metric:String} = 'cpu_time', toFloat64(sum(ProfileEvents['UserTimeMicroseconds']) + sum(ProfileEvents['SystemTimeMicroseconds'])), ]*/\n /*[ {metric:String} = 'read_bytes', toFloat64(sum(read_bytes)), ]*/\n /*[ {metric:String} = 'written_bytes', toFloat64(sum(written_bytes)), ]*/\n /*[ {metric:String} = 'avg_written_rows', toFloat64(avg(written_rows)), ]*/\n /*[ {metric:String} = 'result_bytes', toFloat64(sum(result_bytes)), ]*/\n /*[ {metric:String} = 'network_bytes', toFloat64(sum(ProfileEvents['NetworkReceiveBytes']) + sum(ProfileEvents['NetworkSendBytes'])), ]*/\n /*[ {metric:String} = 'memory', toFloat64(sum(memory_usage)), ]*/\n /*[ {metric:String} = 'max_memory', toFloat64(max(memory_usage)), ]*/\n /*[ {metric:String} = 'network_wait', toFloat64((sum(ProfileEvents['NetworkSendElapsedMicroseconds']) + sum(ProfileEvents['NetworkReceiveElapsedMicroseconds'])) / 1000000), ]*/\n /*[ {metric:String} = 'io_time', toFloat64((sum(ProfileEvents['DiskReadElapsedMicroseconds']) + sum(ProfileEvents['DiskWriteElapsedMicroseconds'])) / 1000000), ]*/\n /*[ {metric:String} = 'io_wait', toFloat64(sum(ProfileEvents['OSIOWaitMicroseconds']) / 1000000), ]*/\n /*[ {metric:String} = 'zk_txns', toFloat64(sum(ProfileEvents['ZooKeeperTransactions'])), ]*/\n /*[ {metric:String} = 'read_bps', toFloat64(sum(read_bytes) * 1000 / nullIf(sum(query_duration_ms), 0)), ]*/\n /*[ {metric:String} = 'write_bps', toFloat64(sum(written_bytes) * 1000 / nullIf(sum(query_duration_ms), 0)), ]*/\n /*[ {metric:String} = 'parts_inserted', toFloat64(sum(ProfileEvents['InsertedCompactParts'] + ProfileEvents['InsertedWideParts'])), ]*/\n /*[ {metric:String} = 'parts_inserted_avg', toFloat64(avg(ProfileEvents['InsertedCompactParts'] + ProfileEvents['InsertedWideParts'])), ]*/\n /*[ {metric:String} = 'marks_load_time', toFloat64(sum(ProfileEvents['WaitMarksLoadMicroseconds'])), ]*/\n /*[ {metric:String} = 'marks_miss_rate', toFloat64(sum(ProfileEvents['MarkCacheMisses']) / nullIf(sum(ProfileEvents['MarkCacheHits']) + sum(ProfileEvents['MarkCacheMisses']), 0)), ]*/\n /*[ {metric:String} = 'selected_parts', toFloat64(sum(ProfileEvents['SelectedParts'])), ]*/\n /*[ {metric:String} = 'selected_ranges', toFloat64(sum(ProfileEvents['SelectedRanges'])), ]*/\n /*[ {metric:String} = 'selected_marks', toFloat64(sum(ProfileEvents['SelectedMarks'])), ]*/\n /*[ {metric:String} = 'exceptions', toFloat64(countIf(exception_code != 0)), ]*/\n /*[ {metric:String} = 'open_files', toFloat64(sum(ProfileEvents['FileOpen'])), ]*/\n /*[ {metric:String} = 'external_processing_files', toFloat64(sum(ProfileEvents['ExternalProcessingFilesTotal'])), ]*/\n /*[ {metric:String} = 'threads1', toFloat64(max(peak_threads_usage)), ]*/\n /*[ {metric:String} = 'threads2', toFloat64(max(length(thread_ids))), ]*/\n /*[ {metric:String} = 'threads3', toFloat64(sum(ProfileEvents['RealTimeMicroseconds']) / nullIf(1000 * sum(query_duration_ms), 0)), ]*/\n 1, toFloat64(count()),\n toFloat64(count())\n ) AS value\n FROM merge(system, '^query_log')\n WHERE event_time >= {from:DateTime} - INTERVAL 20 MINUTE\n AND event_time <= {to:DateTime} + INTERVAL 20 MINUTE\n AND type != 'QueryStart'\n /*[ AND is_initial_query = {is_initial_query:UInt8} ]*/\n /*[ AND query_kind = {query_kind:String} ]*/\n /*[ AND exception_code = {exception_code:Int32} ]*/\n /*[ AND initial_user = {user:String} ]*/\n /*[ AND normalized_query_hash = {query_hash:UInt64} ]*/\n GROUP BY t, series\n )\n ORDER BY count() OVER (PARTITION BY series) DESC, sum(value) OVER (PARTITION BY series) DESC\n LIMIT 100 BY t\n)\nWHERE t >= {from:DateTime} AND t <= {to:DateTime}\nORDER BY t, series", + "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n t,\n series,\n value\nFROM\n(\n SELECT * FROM (\n WITH\n intDiv(toUInt32(event_time), bucket_s) AS finish_bucket,\n intDiv(toUInt32(query_start_time), bucket_s) AS start_bucket,\n arrayMap(i -> toDateTime((start_bucket + i) * bucket_s), range(toUInt32(finish_bucket - start_bucket + 1))) AS buckets\n SELECT\n arrayJoin(buckets) AS t,\n hostName() AS series,\n multiIf(\n /*[ {metric:String} = 'avg_duration', toFloat64(avg(query_duration_ms)), ]*/\n /*[ {metric:String} = 'max_duration', toFloat64(max(query_duration_ms)), ]*/\n /*[ {metric:String} = 'cpu_time', toFloat64(sum(ProfileEvents['UserTimeMicroseconds']) + sum(ProfileEvents['SystemTimeMicroseconds'])), ]*/\n /*[ {metric:String} = 'read_bytes', toFloat64(sum(read_bytes)), ]*/\n /*[ {metric:String} = 'written_bytes', toFloat64(sum(written_bytes)), ]*/\n /*[ {metric:String} = 'avg_written_rows', toFloat64(avg(written_rows)), ]*/\n /*[ {metric:String} = 'result_bytes', toFloat64(sum(result_bytes)), ]*/\n /*[ {metric:String} = 'network_bytes', toFloat64(sum(ProfileEvents['NetworkReceiveBytes']) + sum(ProfileEvents['NetworkSendBytes'])), ]*/\n /*[ {metric:String} = 'memory', toFloat64(sum(memory_usage)), ]*/\n /*[ {metric:String} = 'max_memory', toFloat64(max(memory_usage)), ]*/\n /*[ {metric:String} = 'network_wait', toFloat64((sum(ProfileEvents['NetworkSendElapsedMicroseconds']) + sum(ProfileEvents['NetworkReceiveElapsedMicroseconds'])) / 1000000), ]*/\n /*[ {metric:String} = 'io_time', toFloat64((sum(ProfileEvents['DiskReadElapsedMicroseconds']) + sum(ProfileEvents['DiskWriteElapsedMicroseconds'])) / 1000000), ]*/\n /*[ {metric:String} = 'io_wait', toFloat64(sum(ProfileEvents['OSIOWaitMicroseconds']) / 1000000), ]*/\n /*[ {metric:String} = 'zk_txns', toFloat64(sum(ProfileEvents['ZooKeeperTransactions'])), ]*/\n /*[ {metric:String} = 'read_bps', toFloat64(sum(read_bytes) * 1000 / nullIf(sum(query_duration_ms), 0)), ]*/\n /*[ {metric:String} = 'write_bps', toFloat64(sum(written_bytes) * 1000 / nullIf(sum(query_duration_ms), 0)), ]*/\n /*[ {metric:String} = 'parts_inserted', toFloat64(sum(ProfileEvents['InsertedCompactParts'] + ProfileEvents['InsertedWideParts'])), ]*/\n /*[ {metric:String} = 'parts_inserted_avg', toFloat64(avg(ProfileEvents['InsertedCompactParts'] + ProfileEvents['InsertedWideParts'])), ]*/\n /*[ {metric:String} = 'marks_load_time', toFloat64(sum(ProfileEvents['WaitMarksLoadMicroseconds'])), ]*/\n /*[ {metric:String} = 'marks_miss_rate', toFloat64(sum(ProfileEvents['MarkCacheMisses']) / nullIf(sum(ProfileEvents['MarkCacheHits']) + sum(ProfileEvents['MarkCacheMisses']), 0)), ]*/\n /*[ {metric:String} = 'selected_parts', toFloat64(sum(ProfileEvents['SelectedParts'])), ]*/\n /*[ {metric:String} = 'selected_ranges', toFloat64(sum(ProfileEvents['SelectedRanges'])), ]*/\n /*[ {metric:String} = 'selected_marks', toFloat64(sum(ProfileEvents['SelectedMarks'])), ]*/\n /*[ {metric:String} = 'exceptions', toFloat64(countIf(exception_code != 0)), ]*/\n /*[ {metric:String} = 'open_files', toFloat64(sum(ProfileEvents['FileOpen'])), ]*/\n /*[ {metric:String} = 'external_processing_files', toFloat64(sum(ProfileEvents['ExternalProcessingFilesTotal'])), ]*/\n /*[ {metric:String} = 'threads1', toFloat64(max(peak_threads_usage)), ]*/\n /*[ {metric:String} = 'threads2', toFloat64(max(length(thread_ids))), ]*/\n /*[ {metric:String} = 'threads3', toFloat64(sum(ProfileEvents['RealTimeMicroseconds']) / nullIf(1000 * sum(query_duration_ms), 0)), ]*/\n 1, toFloat64(count()),\n toFloat64(count())\n ) AS value\n FROM merge(system, '^query_log')\n WHERE event_time >= {from:DateTime} - INTERVAL 20 MINUTE\n AND event_time <= {to:DateTime} + INTERVAL 20 MINUTE\n AND type != 'QueryStart'\n /*[ AND is_initial_query = {is_initial_query:UInt8} ]*/\n /*[ AND has({query_kind:Array(String)}, query_kind) ]*/\n /*[ AND has({exception_code:Array(Int32)}, exception_code) ]*/\n /*[ AND has({user:Array(String)}, initial_user) ]*/\n /*[ AND has({query_hash:Array(UInt64)}, normalized_query_hash) ]*/\n GROUP BY t, series\n )\n ORDER BY count() OVER (PARTITION BY series) DESC, sum(value) OVER (PARTITION BY series) DESC\n LIMIT 100 BY t\n)\nWHERE t >= {from:DateTime} AND t <= {to:DateTime}\nORDER BY t, series", "specVersion": 1, "spec": { "name": "query_log by host · Selected metric by host", - "favorite": true, "description": "Retained for source fidelity. In the single-node port this normally contains one host series.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "bar", + "style": { + "axes": "show", + "density": "normal", + "grid": "auto", + "legend": "auto", + "mode": "stacked", + "scale": "zero" + }, "x": 0, "y": [ 2 ], - "series": 1, - "style": { - "mode": "stacked", - "density": "normal", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": 1 }, "fieldConfig": { "columns": { @@ -2160,25 +2253,25 @@ "specVersion": 1, "spec": { "name": "query_views_log · Selected metric by view name", - "favorite": true, "description": "Grafana status history converted to grouped columns. query_views_log fields are remapped to the common metric vocabulary; blank metric means count.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "bar", + "style": { + "axes": "show", + "density": "normal", + "grid": "auto", + "legend": "auto", + "mode": "stacked", + "scale": "zero" + }, "x": 0, "y": [ 2 ], - "series": 1, - "style": { - "mode": "stacked", - "density": "normal", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": 1 }, "fieldConfig": { "columns": { @@ -2205,25 +2298,25 @@ "specVersion": 1, "spec": { "name": "part_log: NewPart · part_log metric (NewPart by table)", - "favorite": true, "description": "Approximation of Grafana status history. The selected optional metric is grouped by time bucket and table for system.part_log event_type='NewPart'. Blank metric means count.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "bar", + "style": { + "axes": "show", + "density": "normal", + "grid": "auto", + "legend": "auto", + "mode": "stacked", + "scale": "zero" + }, "x": 0, "y": [ 2 ], - "series": 1, - "style": { - "mode": "stacked", - "density": "normal", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": 1 }, "fieldConfig": { "columns": { @@ -2250,25 +2343,25 @@ "specVersion": 1, "spec": { "name": "part_log: DownloadPart · part_log metric (DownloadPart by table)", - "favorite": true, "description": "Approximation of Grafana status history. The selected optional metric is grouped by time bucket and table for system.part_log event_type='DownloadPart'. Blank metric means count.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "bar", + "style": { + "axes": "show", + "density": "normal", + "grid": "auto", + "legend": "auto", + "mode": "stacked", + "scale": "zero" + }, "x": 0, "y": [ 2 ], - "series": 1, - "style": { - "mode": "stacked", - "density": "normal", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": 1 }, "fieldConfig": { "columns": { @@ -2295,25 +2388,25 @@ "specVersion": 1, "spec": { "name": "part_log: MergeParts · part_log metric (MergeParts by table)", - "favorite": true, "description": "Approximation of Grafana status history. The selected optional metric is grouped by time bucket and table for system.part_log event_type='MergeParts'. Blank metric means count.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "bar", + "style": { + "axes": "show", + "density": "normal", + "grid": "auto", + "legend": "auto", + "mode": "stacked", + "scale": "zero" + }, "x": 0, "y": [ 2 ], - "series": 1, - "style": { - "mode": "stacked", - "density": "normal", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": 1 }, "fieldConfig": { "columns": { @@ -2340,25 +2433,25 @@ "specVersion": 1, "spec": { "name": "part_log: MutatePart · part_log metric (MutatePart by table)", - "favorite": true, "description": "Approximation of Grafana status history. The selected optional metric is grouped by time bucket and table for system.part_log event_type='MutatePart'. Blank metric means count.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "bar", + "style": { + "axes": "show", + "density": "normal", + "grid": "auto", + "legend": "auto", + "mode": "stacked", + "scale": "zero" + }, "x": 0, "y": [ 2 ], - "series": 1, - "style": { - "mode": "stacked", - "density": "normal", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": 1 }, "fieldConfig": { "columns": { @@ -2385,25 +2478,25 @@ "specVersion": 1, "spec": { "name": "part_log: MovePart · part_log metric (MovePart by table)", - "favorite": true, "description": "Approximation of Grafana status history. The selected optional metric is grouped by time bucket and table for system.part_log event_type='MovePart'. Blank metric means count.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "bar", + "style": { + "axes": "show", + "density": "normal", + "grid": "auto", + "legend": "auto", + "mode": "stacked", + "scale": "zero" + }, "x": 0, "y": [ 2 ], - "series": 1, - "style": { - "mode": "stacked", - "density": "normal", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": 1 }, "fieldConfig": { "columns": { @@ -2430,25 +2523,25 @@ "specVersion": 1, "spec": { "name": "part_log: RemovePart · part_log metric (RemovePart by table)", - "favorite": true, "description": "Approximation of Grafana status history. The selected optional metric is grouped by time bucket and table for system.part_log event_type='RemovePart'. Blank metric means count.", + "favorite": false, "view": "panel", "panel": { "cfg": { "type": "bar", + "style": { + "axes": "show", + "density": "normal", + "grid": "auto", + "legend": "auto", + "mode": "stacked", + "scale": "zero" + }, "x": 0, "y": [ 2 ], - "series": 1, - "style": { - "mode": "stacked", - "density": "normal", - "scale": "zero", - "legend": "auto", - "grid": "auto", - "axes": "show" - } + "series": 1 }, "fieldConfig": { "columns": { @@ -2470,54 +2563,37 @@ } }, { - "id": "gco-kpi-overview", - "sql": "WITH snap AS (\n SELECT\n (SELECT max(event_time) FROM merge(system, '^metric_log')) - 300 AS t_prev,\n argMax(CurrentMetric_Query, event_time) AS q_now,\n round(argMax(CurrentMetric_MemoryTracking, event_time) / 1048576) AS mem_now,\n round(argMaxIf(CurrentMetric_MemoryTracking, event_time, event_time <= t_prev) / 1048576) AS mem_prev,\n argMax(CurrentMetric_PartsActive, event_time) AS parts_now,\n argMaxIf(CurrentMetric_PartsActive, event_time, event_time <= t_prev) AS parts_prev,\n round(max(CurrentMetric_MemoryTracking) / 1048576) AS mem_peak\n FROM merge(system, '^metric_log')\n)\nSELECT\n q_now AS running_queries,\n CAST((mem_now, mem_now - mem_prev) AS Tuple(value Int64, delta Int64)) AS memory_used,\n CAST((parts_now, parts_now - parts_prev) AS Tuple(value Int64, delta Int64)) AS active_parts,\n mem_peak AS peak_memory\nFROM snap", + "id": "gco-spark-memory", + "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n toDateTime(intDiv(toUInt32(event_time), bucket_s) * bucket_s) AS t,\n round(avg(CurrentMetric_MemoryTracking) / 1048576) AS value\nFROM merge(system, '^metric_log')\nWHERE event_time >= {from:DateTime}\n AND event_time <= {to:DateTime}\nGROUP BY t\nORDER BY t", "specVersion": 1, "spec": { - "name": "Overview · Live KPIs", - "favorite": true, - "description": "Current running queries, tracked/peak memory (MiB), and active parts from the latest system.metric_log snapshot. Memory and parts carry a delta against five minutes earlier.", + "name": "Memory · Tracked (Sparkline)", + "description": "Average tracked memory (MiB) per bucket from system.metric_log, drawn with the Sparkline Style preset (no axes, grid, legend, or markers — hover still works).", + "favorite": false, "view": "panel", "panel": { "cfg": { - "type": "kpi" + "type": "line", + "style": { + "axes": "hide", + "curve": "linear", + "grid": "hide", + "legend": "hide", + "points": "hide", + "scale": "data" + }, + "x": 0, + "y": [ + 1 + ], + "series": null }, "fieldConfig": { - "defaults": { - "noValue": "—" - }, "columns": { - "running_queries": { - "displayName": "Running queries", - "decimals": 0, - "color": "#4f8cff", - "description": "Concurrent queries at the latest sample." - }, - "memory_used": { + "value": { "displayName": "Memory tracked", "unit": " MiB", - "decimals": 0, - "description": "Server-tracked memory at the latest sample.", - "delta": { - "unit": " MiB", - "decimals": 0, - "positiveIsGood": false - } - }, - "active_parts": { - "displayName": "Active parts", - "decimals": 0, - "description": "Active data parts across all tables.", - "delta": { - "decimals": 0, - "positiveIsGood": false - } - }, - "peak_memory": { - "displayName": "Peak memory", - "unit": " MiB", - "decimals": 0, - "description": "Maximum tracked memory over the window." + "decimals": 0 } } } @@ -2525,42 +2601,45 @@ "dashboard": { "role": "panel", "sizeHints": { - "preferred": "compact", + "preferred": "medium", "minimum": "compact", - "aspectRatio": 2 + "aspectRatio": 1.5 } } } }, { - "id": "gco-donut-query-kind", - "sql": "SELECT query_kind, count() AS queries\nFROM merge(system, '^query_log')\nWHERE event_time >= {from:DateTime}\n AND event_time <= {to:DateTime}\n AND type != 'QueryStart'\n AND query_kind != ''\nGROUP BY query_kind\nORDER BY queries DESC", + "id": "gco-smooth-load", + "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n toDateTime(intDiv(toUInt32(event_time), bucket_s) * bucket_s) AS t,\n avg(CurrentMetric_QueryThread) AS value\nFROM merge(system, '^metric_log')\nWHERE event_time >= {from:DateTime}\n AND event_time <= {to:DateTime}\nGROUP BY t\nORDER BY t", "specVersion": 1, "spec": { - "name": "Queries · Mix by kind (Donut)", - "favorite": true, - "description": "Share of query kinds (Select, Insert, …) from system.query_log over the dashboard window. Uses the Donut Style preset.", + "name": "Threads · Query threads (Smooth)", + "description": "Average active query threads per bucket from system.metric_log, drawn with the Smooth Style preset (monotone interpolation — no overshoot past the data).", + "favorite": false, "view": "panel", "panel": { "cfg": { - "type": "pie", + "type": "line", + "style": { + "axes": "show", + "curve": "smooth", + "grid": "auto", + "legend": "auto", + "points": "auto", + "scale": "data" + }, "x": 0, "y": [ 1 ], - "series": null, - "style": { - "shape": "donut", - "legend": "show", - "frame": "normal" - } + "series": null }, "fieldConfig": { "columns": { - "queries": { - "displayName": "Queries", - "unit": " queries", - "decimals": 0 + "value": { + "displayName": "Query threads", + "unit": " threads", + "decimals": 1 } } } @@ -2576,93 +2655,62 @@ } }, { - "id": "gco-spark-memory", - "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n toDateTime(intDiv(toUInt32(event_time), bucket_s) * bucket_s) AS t,\n round(avg(CurrentMetric_MemoryTracking) / 1048576) AS value\nFROM merge(system, '^metric_log')\nWHERE event_time >= {from:DateTime}\n AND event_time <= {to:DateTime}\nGROUP BY t\nORDER BY t", + "id": "gco-filter", + "sql": "SELECT\n [toUInt8(1), toUInt8(0)] AS is_initial_query,\n (\n SELECT arraySort(groupUniqArray(query_kind))\n FROM merge(system, '^query_log')\n WHERE event_time >= now() - INTERVAL 7 DAY AND query_kind != ''\n ) AS query_kind,\n (\n SELECT arraySort(groupUniqArray(initial_user))\n FROM merge(system, '^query_log')\n WHERE event_time >= now() - INTERVAL 7 DAY AND initial_user != ''\n ) AS user,\n (\n SELECT groupArray((exception_code AS value, concat(toString(exception_code), ' · ', errorCodeToName(exception_code)) AS label))\n FROM\n (\n SELECT exception_code\n FROM merge(system, '^query_log')\n WHERE event_time >= now() - INTERVAL 7 DAY AND exception_code != 0\n GROUP BY exception_code\n ORDER BY count() DESC\n LIMIT 100\n )\n ) AS exception_code,\n (\n SELECT groupArray((normalized_query_hash AS value, concat(toString(normalized_query_hash), ' · ', substring(sample_query, 1, 80)) AS label))\n FROM\n (\n SELECT normalized_query_hash, anyHeavy(replaceAll(query, '\\n', ' ')) AS sample_query\n FROM merge(system, '^query_log')\n WHERE event_time >= now() - INTERVAL 7 DAY AND normalized_query_hash != 0\n GROUP BY normalized_query_hash\n ORDER BY count() DESC\n LIMIT 80\n )\n ) AS query_hash,\n ['count', 'avg_duration', 'max_duration', 'cpu_time', 'read_bytes', 'written_bytes', 'avg_written_rows', 'result_bytes', 'network_bytes', 'memory', 'max_memory', 'network_wait', 'io_time', 'io_wait', 'zk_txns', 'read_bps', 'write_bps', 'parts_inserted', 'parts_inserted_avg', 'marks_load_time', 'marks_miss_rate', 'selected_parts', 'selected_ranges', 'selected_marks', 'exceptions', 'open_files', 'external_processing_files', 'threads1', 'threads2', 'threads3'] AS metric\nSETTINGS enable_named_columns_in_function_tuple = 1", "specVersion": 1, "spec": { - "name": "Memory · Tracked (Sparkline)", + "name": "Grafana port filters", + "description": "Filter source for single-select metric/is_initial_query and inferred multiselect query_kind, user, exception_code, and query_hash controls. Blank means all.", + "favorite": false, + "dashboard": { + "role": "filter" + } + } + }, + { + "id": "ops-largest-tables", + "sql": "SELECT concat(database, '.', table) AS table, formatReadableSize(sum(bytes_on_disk)) AS disk, sum(bytes_on_disk) AS disk_bytes FROM system.parts WHERE active GROUP BY database, table ORDER BY disk_bytes DESC LIMIT 15", + "specVersion": 1, + "spec": { + "name": "Largest tables by disk", + "description": "Largest active tables by compressed bytes on disk.", "favorite": true, - "description": "Average tracked memory (MiB) per bucket from system.metric_log, drawn with the Sparkline Style preset (no axes, grid, legend, or markers — hover still works).", "view": "panel", "panel": { "cfg": { - "type": "line", - "x": 0, - "y": [ - 1 - ], - "series": null, - "style": { - "scale": "data", - "legend": "hide", - "grid": "hide", - "axes": "hide", - "curve": "linear", - "points": "hide" - } - }, - "fieldConfig": { - "columns": { - "value": { - "displayName": "Memory tracked", - "unit": " MiB", - "decimals": 0 - } - } + "type": "table" } }, "dashboard": { "role": "panel", "sizeHints": { - "preferred": "medium", - "minimum": "compact", - "aspectRatio": 1.5 + "preferred": "wide", + "minimum": "medium", + "aspectRatio": 1.6 } } } }, { - "id": "gco-smooth-load", - "sql": "WITH\n greatest(1, toUInt32(ceil(greatest(1, dateDiff('second', {from:DateTime}, {to:DateTime})) / 300))) AS bucket_s\nSELECT\n toDateTime(intDiv(toUInt32(event_time), bucket_s) * bucket_s) AS t,\n avg(CurrentMetric_QueryThread) AS value\nFROM merge(system, '^metric_log')\nWHERE event_time >= {from:DateTime}\n AND event_time <= {to:DateTime}\nGROUP BY t\nORDER BY t", + "id": "ops-recent-server-logs", + "sql": "SELECT event_time, level, logger_name, message FROM system.text_log WHERE event_time BETWEEN {from:DateTime} AND {to:DateTime}\n/*[ AND positionCaseInsensitive(message, {search:String}) > 0 ]*/\nORDER BY event_time DESC LIMIT 500", "specVersion": 1, "spec": { - "name": "Threads · Query threads (Smooth)", + "name": "Recent server logs", + "description": "Most recent server log entries, optionally narrowed by message text.", "favorite": true, - "description": "Average active query threads per bucket from system.metric_log, drawn with the Smooth Style preset (monotone interpolation — no overshoot past the data).", "view": "panel", "panel": { "cfg": { - "type": "line", - "x": 0, - "y": [ - 1 - ], - "series": null, - "style": { - "scale": "data", - "legend": "auto", - "grid": "auto", - "axes": "show", - "curve": "smooth", - "points": "auto" - } - }, - "fieldConfig": { - "columns": { - "value": { - "displayName": "Query threads", - "unit": " threads", - "decimals": 1 - } - } + "type": "logs" } }, "dashboard": { "role": "panel", "sizeHints": { - "preferred": "medium", - "minimum": "compact", - "aspectRatio": 1.5 + "preferred": "wide", + "minimum": "medium", + "aspectRatio": 1.6 } } } @@ -2671,389 +2719,270 @@ "dashboards": [ { "documentVersion": 1, - "id": "clickhouse-ops-enhanced", - "title": "ClickHouse operations", - "description": "Operational ClickHouse dashboard adapted from the Grafana dashboard.", + "id": "clickhouse-operations", + "title": "ClickHouse Operations", + "description": "Operator-first server overview, resources, background work, and investigation views.", "revision": 1, "layout": { - "type": "flow", + "type": "grafana-grid", "version": 1, - "preset": "columns-2", "items": { - "tile-gco-029-running-queries": { - "span": 1, - "height": "large" - }, - "tile-gco-047-merges": { - "span": 1, - "height": "large" - }, - "tile-gco-048-mutations": { - "span": 1, - "height": "large" - }, - "tile-gco-049-moves": { - "span": 1, - "height": "large" - }, - "tile-gco-050-distributed-send": { - "span": 1, - "height": "large" - }, - "tile-gco-064-replicated-checks": { - "span": 1, - "height": "large" - }, - "tile-gco-061-replicated-fetch": { - "span": 1, - "height": "large" - }, - "tile-gco-062-replicated-send": { - "span": 1, - "height": "large" - }, - "tile-gco-063-kafka-background-reads": { - "span": 1, - "height": "large" - }, - "tile-gco-065-refreshing-views": { - "span": 1, - "height": "large" - }, - "tile-gco-077-kafka-writes": { - "span": 1, - "height": "large" - }, - "tile-gco-071-background-schedule": { - "span": 1, - "height": "large" - }, - "tile-gco-068-background-common": { - "span": 1, - "height": "large" - }, - "tile-gco-069-background-move": { - "span": 1, - "height": "large" - }, - "tile-gco-067-background-fetches": { - "span": 1, - "height": "large" - }, - "tile-gco-066-background-merges-mutations": { - "span": 1, - "height": "large" - }, - "tile-gco-074-background-distributed": { - "span": 1, - "height": "large" - }, - "tile-gco-075-background-message-broker": { - "span": 1, - "height": "large" - }, - "tile-gco-073-background-buffer-flush": { - "span": 1, - "height": "large" - }, - "tile-gco-078-global-thread-active": { - "span": 1, - "height": "large" - }, - "tile-gco-094-accounted-time": { - "span": 1, - "height": "large" - }, - "tile-gco-076-active-pools": { - "span": 1, - "height": "large" - }, - "tile-gco-095-zk-transactions": { - "span": 1, - "height": "large" + "tile-gco-kpi-overview": { + "span": 12, + "height": 2 }, - "tile-gco-096-zk-latency": { - "span": 1, - "height": "large" + "tile-gco-029-running-queries": { + "span": 4, + "height": 3 }, - "tile-gco-097-zk-inflight": { - "span": 1, - "height": "large" + "tile-gco-040-queries-started": { + "span": 4, + "height": 3 }, - "tile-gco-098-zk-traffic": { - "span": 1, - "height": "large" + "tile-gco-055-connections": { + "span": 4, + "height": 3 }, "tile-gco-041-cpus-loaded": { - "span": 1, - "height": "large" + "span": 6, + "height": 3 }, "tile-gco-053-memory": { - "span": 1, - "height": "large" - }, - "tile-gco-093-cpu-system": { - "span": 1, - "height": "large" - }, - "tile-gco-028-load-average": { - "span": 1, - "height": "large" + "span": 6, + "height": 3 }, "tile-gco-058-io-util": { - "span": 1, - "height": "large" + "span": 6, + "height": 3 }, "tile-gco-046-network": { - "span": 1, - "height": "large" - }, - "tile-gco-042-io-wait": { - "span": 1, - "height": "large" - }, - "tile-gco-043-cpu-wait": { - "span": 1, - "height": "large" - }, - "tile-gco-044-disk-io": { - "span": 1, - "height": "large" + "span": 6, + "height": 3 }, - "tile-gco-045-page-cache-io": { - "span": 1, - "height": "large" - }, - "tile-gco-059-io-bytes": { - "span": 1, - "height": "large" - }, - "tile-gco-060-iops": { - "span": 1, - "height": "large" + "tile-gco-047-merges": { + "span": 6, + "height": 3 }, - "tile-gco-040-queries-started": { - "span": 1, - "height": "large" + "tile-gco-048-mutations": { + "span": 6, + "height": 3 }, - "tile-gco-055-connections": { - "span": 1, - "height": "large" + "tile-gco-064-replicated-checks": { + "span": 6, + "height": 3 }, "tile-gco-099-error-log": { - "span": 1, - "height": "large" + "span": 6, + "height": 3 + }, + "tile-ops-largest-tables": { + "span": 6, + "height": 3 }, "tile-gco-025-query-hash": { - "span": 1, - "height": "large" + "span": 6, + "height": 3 }, "tile-gco-030-query-hash-details": { - "span": 2, - "height": "large" - }, - "tile-gco-056-query-table": { - "span": 1, - "height": "large" - }, - "tile-gco-037-query-user": { - "span": 1, - "height": "large" - }, - "tile-gco-026-query-host": { - "span": 1, - "height": "large" - }, - "tile-gco-079-query-views": { - "span": 1, - "height": "large" - }, - "tile-gco-part-newpart": { - "span": 1, - "height": "large" - }, - "tile-gco-part-downloadpart": { - "span": 1, - "height": "large" - }, - "tile-gco-part-mergeparts": { - "span": 1, - "height": "large" - }, - "tile-gco-part-mutatepart": { - "span": 1, - "height": "large" - }, - "tile-gco-part-movepart": { - "span": 1, - "height": "large" - }, - "tile-gco-part-removepart": { - "span": 1, - "height": "large" - }, - "tile-gco-kpi-overview": { - "span": 1, - "height": "compact" - }, - "tile-gco-donut-query-kind": { - "span": 1, - "height": "large" - }, - "tile-gco-spark-memory": { - "span": 1, - "height": "large" - }, - "tile-gco-smooth-load": { - "span": 1, - "height": "large" + "span": 6, + "height": 3 + }, + "tile-ops-recent-server-logs": { + "span": 6, + "height": 3 + } + }, + "fallback": { + "type": "flow", + "version": 1, + "preset": "columns-2", + "items": { + "tile-gco-kpi-overview": { + "span": 2, + "height": "compact" + }, + "tile-gco-029-running-queries": { + "span": 1, + "height": "large" + }, + "tile-gco-040-queries-started": { + "span": 1, + "height": "large" + }, + "tile-gco-055-connections": { + "span": 1, + "height": "large" + }, + "tile-gco-041-cpus-loaded": { + "span": 1, + "height": "large" + }, + "tile-gco-053-memory": { + "span": 1, + "height": "large" + }, + "tile-gco-058-io-util": { + "span": 1, + "height": "large" + }, + "tile-gco-046-network": { + "span": 1, + "height": "large" + }, + "tile-gco-047-merges": { + "span": 1, + "height": "large" + }, + "tile-gco-048-mutations": { + "span": 1, + "height": "large" + }, + "tile-gco-064-replicated-checks": { + "span": 1, + "height": "large" + }, + "tile-gco-099-error-log": { + "span": 1, + "height": "large" + }, + "tile-ops-largest-tables": { + "span": 2, + "height": "large" + }, + "tile-gco-025-query-hash": { + "span": 1, + "height": "large" + }, + "tile-gco-030-query-hash-details": { + "span": 2, + "height": "large" + }, + "tile-ops-recent-server-logs": { + "span": 2, + "height": "large" + } } } }, "filters": [ { - "id": "filter-from", - "parameter": "from" + "id": "ops-from", + "parameter": "from", + "label": "From", + "defaultValue": "-1h", + "defaultActive": true, + "targets": [ + "tile-gco-029-running-queries", + "tile-gco-040-queries-started", + "tile-gco-055-connections", + "tile-gco-041-cpus-loaded", + "tile-gco-053-memory", + "tile-gco-058-io-util", + "tile-gco-046-network", + "tile-gco-047-merges", + "tile-gco-048-mutations", + "tile-gco-064-replicated-checks", + "tile-gco-099-error-log", + "tile-gco-025-query-hash", + "tile-gco-030-query-hash-details", + "tile-ops-recent-server-logs" + ] + }, + { + "id": "ops-to", + "parameter": "to", + "label": "To", + "defaultValue": "now", + "defaultActive": true, + "targets": [ + "tile-gco-029-running-queries", + "tile-gco-040-queries-started", + "tile-gco-055-connections", + "tile-gco-041-cpus-loaded", + "tile-gco-053-memory", + "tile-gco-058-io-util", + "tile-gco-046-network", + "tile-gco-047-merges", + "tile-gco-048-mutations", + "tile-gco-064-replicated-checks", + "tile-gco-099-error-log", + "tile-gco-025-query-hash", + "tile-gco-030-query-hash-details", + "tile-ops-recent-server-logs" + ] + }, + { + "id": "ops-user", + "parameter": "user", + "label": "user", + "sourceQueryId": "gco-filter", + "defaultValue": [], + "defaultActive": false }, { - "id": "filter-to", - "parameter": "to" + "id": "ops-query_kind", + "parameter": "query_kind", + "label": "query kind", + "sourceQueryId": "gco-filter", + "defaultValue": [], + "defaultActive": false }, { - "id": "filter-exception_code", + "id": "ops-exception_code", "parameter": "exception_code", - "sourceQueryId": "gco-filter" - }, - { - "id": "filter-metric", - "parameter": "metric", - "sourceQueryId": "gco-filter" + "label": "exception code", + "sourceQueryId": "gco-filter", + "defaultValue": [], + "defaultActive": false }, { - "id": "filter-is_initial_query", - "parameter": "is_initial_query", - "sourceQueryId": "gco-filter" + "id": "ops-query_hash", + "parameter": "query_hash", + "label": "query hash", + "sourceQueryId": "gco-filter", + "defaultValue": [], + "defaultActive": false }, { - "id": "filter-query_kind", - "parameter": "query_kind", - "sourceQueryId": "gco-filter" + "id": "ops-metric", + "parameter": "metric", + "label": "Metric", + "sourceQueryId": "gco-filter", + "defaultValue": "count", + "defaultActive": true }, { - "id": "filter-user", - "parameter": "user", - "sourceQueryId": "gco-filter" + "id": "ops-initial", + "parameter": "is_initial_query", + "label": "Initial query", + "sourceQueryId": "gco-filter", + "defaultValue": 1, + "defaultActive": false }, { - "id": "filter-query_hash", - "parameter": "query_hash", - "sourceQueryId": "gco-filter" + "id": "ops-search", + "parameter": "search", + "label": "Log search", + "defaultValue": "", + "defaultActive": false } ], "tiles": [ { - "id": "tile-gco-029-running-queries", - "queryId": "gco-029-running-queries" - }, - { - "id": "tile-gco-047-merges", - "queryId": "gco-047-merges" - }, - { - "id": "tile-gco-048-mutations", - "queryId": "gco-048-mutations" - }, - { - "id": "tile-gco-049-moves", - "queryId": "gco-049-moves" - }, - { - "id": "tile-gco-050-distributed-send", - "queryId": "gco-050-distributed-send" - }, - { - "id": "tile-gco-064-replicated-checks", - "queryId": "gco-064-replicated-checks" - }, - { - "id": "tile-gco-061-replicated-fetch", - "queryId": "gco-061-replicated-fetch" - }, - { - "id": "tile-gco-062-replicated-send", - "queryId": "gco-062-replicated-send" - }, - { - "id": "tile-gco-063-kafka-background-reads", - "queryId": "gco-063-kafka-background-reads" - }, - { - "id": "tile-gco-065-refreshing-views", - "queryId": "gco-065-refreshing-views" - }, - { - "id": "tile-gco-077-kafka-writes", - "queryId": "gco-077-kafka-writes" - }, - { - "id": "tile-gco-071-background-schedule", - "queryId": "gco-071-background-schedule" - }, - { - "id": "tile-gco-068-background-common", - "queryId": "gco-068-background-common" - }, - { - "id": "tile-gco-069-background-move", - "queryId": "gco-069-background-move" - }, - { - "id": "tile-gco-067-background-fetches", - "queryId": "gco-067-background-fetches" - }, - { - "id": "tile-gco-066-background-merges-mutations", - "queryId": "gco-066-background-merges-mutations" - }, - { - "id": "tile-gco-074-background-distributed", - "queryId": "gco-074-background-distributed" - }, - { - "id": "tile-gco-075-background-message-broker", - "queryId": "gco-075-background-message-broker" - }, - { - "id": "tile-gco-073-background-buffer-flush", - "queryId": "gco-073-background-buffer-flush" - }, - { - "id": "tile-gco-078-global-thread-active", - "queryId": "gco-078-global-thread-active" - }, - { - "id": "tile-gco-094-accounted-time", - "queryId": "gco-094-accounted-time" - }, - { - "id": "tile-gco-076-active-pools", - "queryId": "gco-076-active-pools" - }, - { - "id": "tile-gco-095-zk-transactions", - "queryId": "gco-095-zk-transactions" + "id": "tile-gco-kpi-overview", + "queryId": "gco-kpi-overview" }, { - "id": "tile-gco-096-zk-latency", - "queryId": "gco-096-zk-latency" + "id": "tile-gco-029-running-queries", + "queryId": "gco-029-running-queries" }, { - "id": "tile-gco-097-zk-inflight", - "queryId": "gco-097-zk-inflight" + "id": "tile-gco-040-queries-started", + "queryId": "gco-040-queries-started" }, { - "id": "tile-gco-098-zk-traffic", - "queryId": "gco-098-zk-traffic" + "id": "tile-gco-055-connections", + "queryId": "gco-055-connections" }, { "id": "tile-gco-041-cpus-loaded", @@ -3063,14 +2992,6 @@ "id": "tile-gco-053-memory", "queryId": "gco-053-memory" }, - { - "id": "tile-gco-093-cpu-system", - "queryId": "gco-093-cpu-system" - }, - { - "id": "tile-gco-028-load-average", - "queryId": "gco-028-load-average" - }, { "id": "tile-gco-058-io-util", "queryId": "gco-058-io-util" @@ -3080,41 +3001,25 @@ "queryId": "gco-046-network" }, { - "id": "tile-gco-042-io-wait", - "queryId": "gco-042-io-wait" - }, - { - "id": "tile-gco-043-cpu-wait", - "queryId": "gco-043-cpu-wait" - }, - { - "id": "tile-gco-044-disk-io", - "queryId": "gco-044-disk-io" - }, - { - "id": "tile-gco-045-page-cache-io", - "queryId": "gco-045-page-cache-io" - }, - { - "id": "tile-gco-059-io-bytes", - "queryId": "gco-059-io-bytes" - }, - { - "id": "tile-gco-060-iops", - "queryId": "gco-060-iops" + "id": "tile-gco-047-merges", + "queryId": "gco-047-merges" }, { - "id": "tile-gco-040-queries-started", - "queryId": "gco-040-queries-started" + "id": "tile-gco-048-mutations", + "queryId": "gco-048-mutations" }, { - "id": "tile-gco-055-connections", - "queryId": "gco-055-connections" + "id": "tile-gco-064-replicated-checks", + "queryId": "gco-064-replicated-checks" }, { "id": "tile-gco-099-error-log", "queryId": "gco-099-error-log" }, + { + "id": "tile-ops-largest-tables", + "queryId": "ops-largest-tables" + }, { "id": "tile-gco-025-query-hash", "queryId": "gco-025-query-hash" @@ -3124,60 +3029,8 @@ "queryId": "gco-030-query-hash-details" }, { - "id": "tile-gco-056-query-table", - "queryId": "gco-056-query-table" - }, - { - "id": "tile-gco-037-query-user", - "queryId": "gco-037-query-user" - }, - { - "id": "tile-gco-026-query-host", - "queryId": "gco-026-query-host" - }, - { - "id": "tile-gco-079-query-views", - "queryId": "gco-079-query-views" - }, - { - "id": "tile-gco-part-newpart", - "queryId": "gco-part-newpart" - }, - { - "id": "tile-gco-part-downloadpart", - "queryId": "gco-part-downloadpart" - }, - { - "id": "tile-gco-part-mergeparts", - "queryId": "gco-part-mergeparts" - }, - { - "id": "tile-gco-part-mutatepart", - "queryId": "gco-part-mutatepart" - }, - { - "id": "tile-gco-part-movepart", - "queryId": "gco-part-movepart" - }, - { - "id": "tile-gco-part-removepart", - "queryId": "gco-part-removepart" - }, - { - "id": "tile-gco-kpi-overview", - "queryId": "gco-kpi-overview" - }, - { - "id": "tile-gco-donut-query-kind", - "queryId": "gco-donut-query-kind" - }, - { - "id": "tile-gco-spark-memory", - "queryId": "gco-spark-memory" - }, - { - "id": "tile-gco-smooth-load", - "queryId": "gco-smooth-load" + "id": "tile-ops-recent-server-logs", + "queryId": "ops-recent-server-logs" } ] } diff --git a/examples/kpi-panel.json b/examples/kpi-panel.json deleted file mode 100644 index ca839b08..00000000 --- a/examples/kpi-panel.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "$schema": "https://altinity.com/schemas/altinity-sql-browser/portable-bundle-v1.schema.json", - "format": "altinity-sql-browser/portable-bundle", - "version": 1, - "exportedAt": "2026-07-14T00:00:00.000Z", - "metadata": { - "name": "KPI panel example", - "description": "Scalar and named-tuple KPI presentation example." - }, - "queries": [ - { - "id": "kpi-service-health", - "sql": "SELECT count() AS active_users, (99.95 AS value, 0.08 AS delta) AS availability SETTINGS enable_named_columns_in_function_tuple = 1", - "specVersion": 1, - "spec": { - "name": "Service KPIs", - "description": "Scalar and named-tuple KPI cards from one SQL row.", - "favorite": true, - "view": "panel", - "panel": { - "cfg": { - "type": "kpi" - }, - "fieldConfig": { - "defaults": { - "noValue": "—" - }, - "columns": { - "active_users": { - "displayName": "Active users", - "color": "#4f8cff" - }, - "availability": { - "displayName": "Availability", - "description": "Current service availability.", - "unit": "%", - "decimals": 2, - "delta": { - "unit": " pp", - "decimals": 2, - "positiveIsGood": true - } - } - } - } - }, - "dashboard": { - "role": "panel", - "sizeHints": { - "preferred": "compact", - "minimum": "compact", - "aspectRatio": 2 - } - } - } - } - ], - "dashboards": [ - { - "documentVersion": 1, - "id": "kpi-panel-example", - "title": "KPI panel example", - "description": "Scalar and named-tuple KPI presentation example.", - "revision": 1, - "layout": { - "type": "flow", - "version": 1, - "preset": "report", - "items": { - "tile-kpi-service-health": { - "span": 1, - "height": "compact" - } - } - }, - "filters": [], - "tiles": [ - { - "id": "tile-kpi-service-health", - "queryId": "kpi-service-health" - } - ] - } - ] -} diff --git a/examples/mjs/README.md b/examples/mjs/README.md index f5526b95..43f41c15 100644 --- a/examples/mjs/README.md +++ b/examples/mjs/README.md @@ -2,8 +2,9 @@ The checked-in JSON files under `examples/` are canonical **portable bundle v1** documents. Query definitions use saved-query **Spec v1** and every -Dashboard example includes an explicit **Dashboard document v1** with tile -membership, flow-layout placement, and filter definitions. +Dashboard example includes an explicit **Dashboard document v1** with semantic +tile order, filter definitions, and either `flow@1` or `grafana-grid@1` layout. +Every grid layout carries a complete `flow@1` fallback. Legacy Library v1/v2 JSON remains importable for compatibility, but it is not an authoring format for new or regenerated examples. @@ -18,14 +19,15 @@ authoring format for new or regenerated examples. ## Generators -- `build-ontime-charts.mjs` regenerates `ontime-charts.json`. -- `build-system-explorer-charts.mjs` regenerates - `system-explorer-charts.json`. +- `build-ontime-charts.mjs` refreshes the live panel schema keys in + `ontime-charts.json` while preserving its authored grid, filters, KPI + configuration, tile order, and flow fallback. - `build-iceberg-install.mjs` regenerates `iceberg-install.json`. - `build-iceberg-dashboards.mjs` regenerates `iceberg-catalog-dashboard.json` and `iceberg-dba-dashboard.json`. - `example-bundle.mjs` owns the shared portable-bundle and Dashboard authoring - helpers used by those generators. + helpers, including explicit grid sizing, filters/defaults/targets, and flow + fallback generation. The dashboard generators that derive live result schema keys require an appropriately privileged ClickHouse client connection. The install generator diff --git a/examples/mjs/build-iceberg-dashboards.mjs b/examples/mjs/build-iceberg-dashboards.mjs index e4318173..b024c358 100644 --- a/examples/mjs/build-iceberg-dashboards.mjs +++ b/examples/mjs/build-iceberg-dashboards.mjs @@ -16,7 +16,7 @@ // `panel.key` exactly equals schemaKey(resultColumns) = "name:type|…"; a // wrong key silently re-derives axes (worse than none), so keys are derived // live from the real cluster through FORMAT JSON — same rationale as -// build-system-explorer-charts.mjs. +// the other example generators. // // Run: node examples/mjs/build-iceberg-dashboards.mjs // (needs the ice_meta_all views installed; ICE_CH_CMD overrides the diff --git a/examples/mjs/build-ontime-charts.mjs b/examples/mjs/build-ontime-charts.mjs index c5ece1ab..cb0de77d 100644 --- a/examples/mjs/build-ontime-charts.mjs +++ b/examples/mjs/build-ontime-charts.mjs @@ -1,208 +1,51 @@ -// Generator for examples/ontime-charts.json — a saved-queries "Library" file for -// the Altinity SQL Browser that demonstrates every chart feature against the -// public `ontime` flights dataset on the antalya cluster. +// Refresh the live schema keys in examples/ontime-charts.json against the +// configured antalya ClickHouse connection. The checked-in bundle is the +// authored source of truth for SQL, semantic tile order, grafana-grid sizing, +// filters/defaults/targets, KPI field configuration, and the flow fallback. // -// Why a generator: the browser only restores a saved chart config when the -// entry's `spec.panel.key` exactly equals schemaKey(resultColumns) = "name:type|…" -// (see src/ui/results.js chartCfgFor / src/core/chart-data.js schemaKey). -// Hand-writing those type strings is error-prone, so we derive each key live -// from `DESCRIBE ()` against the real cluster. -// -// Run: node examples/mjs/build-ontime-charts.mjs (needs `clickhouse-client --connection antalya`) -// Out: examples/ontime-charts.json +// Run: node examples/mjs/build-ontime-charts.mjs import { execFileSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; -import { buildDashboard, writeExampleBundle } from './example-bundle.mjs'; +import { writeExampleBundle } from './example-bundle.mjs'; const here = dirname(fileURLToPath(import.meta.url)); -const CONNECTION = 'antalya'; - -// Each spec: a query + the chart we want it to open with. `cfg` matches the -// app's shape { type, x, y:[...], series }; x/series are column indices, y a -// list of measure-column indices. `view:'panel'` makes a click open the panel. -const SPECS = [ - { - name: 'Busiest origin airports — 2023', - description: 'Top 15 departure airports by flight count (joined to dim_airports for readable names). Horizontal Bar — hover any bar, long or short, to read its exact value.', - cfg: { type: 'hbar', x: 0, y: [1], series: null }, - sql: `SELECT - a.DisplayAirportName AS airport, - count() AS flights -FROM ontime.fact_ontime AS f -INNER JOIN ontime.dim_airports AS a - ON a.AirportCode = f.OriginCode AND a.IsLatest = 1 -WHERE f.Year = 2023 -GROUP BY airport -ORDER BY flights DESC -LIMIT 15`, - }, - { - name: 'Flights by month — 2023', - description: 'Monthly US flight volume. A numeric column named "month" is detected as an ordinal axis → vertical Column chart, with K/M-humanised value ticks.', - cfg: { type: 'bar', x: 0, y: [1], series: null }, - sql: `SELECT Month AS month, count() AS flights -FROM ontime.fact_ontime -WHERE Year = 2023 -GROUP BY month -ORDER BY month`, - }, - { - name: 'Daily flights — 2023', - description: 'One point per day across 2023 (~365 rows). A Date X axis is auto-detected as a time series → Line chart.', - cfg: { type: 'line', x: 0, y: [1], series: null }, - sql: `SELECT FlightDate AS date, count() AS flights -FROM ontime.fact_ontime -WHERE Year = 2023 -GROUP BY date -ORDER BY date`, - }, - { - name: 'Daily on-time rate — 2023', - description: 'Share of flights arriving on time (< 15 min late) per day, as a percentage. Rendered as a filled Area chart.', - cfg: { type: 'area', x: 0, y: [1], series: null }, - sql: `SELECT - FlightDate AS date, - round(100 * countIf(ArrDel15 = 0) / count(), 1) AS on_time_pct -FROM ontime.fact_ontime -WHERE Year = 2023 -GROUP BY date -ORDER BY date`, - }, - { - name: 'Cancellation reasons — 2023', - description: 'Why flights were cancelled in 2023 (carrier / weather / national air system / security). A small categorical breakdown → Pie chart with a legend.', - cfg: { type: 'pie', x: 0, y: [1], series: null }, - sql: `SELECT - multiIf(CancellationCode = 'A', 'Carrier', - CancellationCode = 'B', 'Weather', - CancellationCode = 'C', 'National Air System', - CancellationCode = 'D', 'Security', 'Other') AS reason, - count() AS cancellations -FROM ontime.fact_ontime -WHERE Year = 2023 AND Cancelled = 1 -GROUP BY reason -ORDER BY cancellations DESC`, - }, - { - name: 'Monthly flights by carrier — 2023', - description: 'Flights per month split across four major carriers (WN, AA, DL, UA). The "carrier" column is used as the Series, producing grouped bars with a per-carrier legend.', - cfg: { type: 'bar', x: 0, y: [2], series: 1 }, - sql: `SELECT - Month AS month, - Carrier AS carrier, - count() AS flights -FROM ontime.fact_ontime -WHERE Year = 2023 AND Carrier IN ('WN', 'AA', 'DL', 'UA') -GROUP BY month, carrier -ORDER BY month, carrier`, - }, - { - name: 'Average delay breakdown by carrier — 2023', - description: 'Mean minutes of each delay cause (carrier, weather, NAS, late aircraft) for delayed flights, per carrier. Four measures plotted at once ("All measures") as grouped columns.', - cfg: { type: 'bar', x: 0, y: [1, 2, 3, 4], series: null }, - sql: `SELECT - Carrier AS carrier, - round(avg(CarrierDelay), 1) AS carrier_delay, - round(avg(WeatherDelay), 1) AS weather_delay, - round(avg(NASDelay), 1) AS nas_delay, - round(avg(LateAircraftDelay), 1) AS late_aircraft_delay -FROM ontime.fact_ontime -WHERE Year = 2023 AND ArrDel15 = 1 -GROUP BY carrier -ORDER BY carrier_delay DESC -LIMIT 12`, - }, - { - name: 'Daily flights since 2022', - description: 'Every day from 2022 onward (~1,460 points). The chart plots the first 500 and shows a "first 500 of N rows" note — the table view still has them all.', - cfg: { type: 'line', x: 0, y: [1], series: null }, - sql: `SELECT FlightDate AS date, count() AS flights -FROM ontime.fact_ontime -WHERE FlightDate >= '2022-01-01' -GROUP BY date -ORDER BY date`, - }, - { - name: 'Flights by day of week — 2023', - description: 'Volume by day of week (1 = Monday … 7 = Sunday). "dayofweek" is recognised as an ordinal axis → Column chart.', - cfg: { type: 'bar', x: 0, y: [1], series: null }, - sql: `SELECT DayOfWeek AS dayofweek, count() AS flights -FROM ontime.fact_ontime -WHERE Year = 2023 -GROUP BY dayofweek -ORDER BY dayofweek`, - }, - { - name: 'Worst average departure delay by airport — 2023', - description: 'Airports with the highest mean departure delay (minutes) among those with ≥ 10,000 departures in 2023. Horizontal Bar of a non-count measure, joined for names.', - cfg: { type: 'hbar', x: 0, y: [1], series: null }, - sql: `SELECT - a.DisplayAirportName AS airport, - round(avg(f.DepDelayMinutes), 1) AS avg_dep_delay -FROM ontime.fact_ontime AS f -INNER JOIN ontime.dim_airports AS a - ON a.AirportCode = f.OriginCode AND a.IsLatest = 1 -WHERE f.Year = 2023 -GROUP BY airport -HAVING count() >= 10000 -ORDER BY avg_dep_delay DESC -LIMIT 15`, - }, -]; +const outPath = resolve(here, '..', 'ontime-charts.json'); +const document = JSON.parse(readFileSync(outPath, 'utf8')); + +const ch = (query) => execFileSync( + 'clickhouse-client', [ + '--connection', 'antalya', + '--param_from', '2023-01-01', '--param_to', '2023-12-31', + '--query', query, + ], + { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }, +); -const ch = (query) => - execFileSync('clickhouse-client', ['--connection', CONNECTION, '--query', query], { - encoding: 'utf8', - maxBuffer: 64 * 1024 * 1024, - }); - -// schemaKey == columns.map(c => c.name + ':' + c.type).join('|'), derived from -// DESCRIBE so it matches exactly what the app receives at run time. function schemaKey(sql) { - const out = ch(`DESCRIBE (${sql})`); - return out + return ch(`DESCRIBE (${sql})`) .split('\n') - .filter((l) => l.trim()) - .map((l) => { const [name, type] = l.split('\t'); return `${name}:${type}`; }) + .filter((line) => line.trim()) + .map((line) => { + const [name, type] = line.split('\t'); + return `${name}:${type}`; + }) .join('|'); } -const resultRows = (sql) => Number(ch(`SELECT count() FROM (${sql})`).trim()); - -const queries = SPECS.map((s, i) => { - const key = schemaKey(s.sql); - const rows = resultRows(s.sql); - console.log(`#${i + 1} ${s.cfg.type.padEnd(4)} rows=${String(rows).padStart(5)} key=${key}`); - return { - id: 's' + (i + 1), - sql: s.sql, - specVersion: 1, - spec: { - name: s.name, - favorite: false, - description: s.description, - panel: { cfg: s.cfg, key }, - view: 'panel', - }, - }; -}); - -const dashboard = buildDashboard({ - id: 'ontime-chart-gallery', - title: 'On-time chart gallery', - description: 'Chart gallery over the public ontime flight dataset.', - queries, - tileQueryIds: queries.map((query) => query.id), - preset: 'columns-2', -}); +for (const query of document.queries) { + if (!query.spec?.panel?.cfg?.type) continue; + const key = schemaKey(query.sql); + query.spec.panel.key = key; + console.log(`${query.id.padEnd(14)} ${query.spec.panel.cfg.type.padEnd(5)} ${key}`); +} -const outPath = resolve(here, 'ontime-charts.json'); writeExampleBundle(outPath, { exportedAt: new Date().toISOString(), - metadata: { name: dashboard.title, description: dashboard.description }, - queries, - dashboards: [dashboard], + metadata: document.metadata, + queries: document.queries, + dashboards: document.dashboards, }); -console.log(`\nwrote ${outPath} (${queries.length} queries)`); +console.log(`wrote ${outPath}`); diff --git a/examples/mjs/build-system-explorer-charts.mjs b/examples/mjs/build-system-explorer-charts.mjs deleted file mode 100644 index 5fc117c6..00000000 --- a/examples/mjs/build-system-explorer-charts.mjs +++ /dev/null @@ -1,257 +0,0 @@ -// Generator for examples/system-explorer-charts.json — a saved-queries -// "Library" file for the Altinity SQL Browser that explores ClickHouse's own -// system database: currently-running work, merges/mutations/replication -// health, storage, and historical query/part/error activity from the *_log -// tables. Ideas and query shapes are adapted (not ported 1:1 — no Grafana -// template macros) from Mikhail Filimonov's ClickHouse ops dashboard: -// https://gist.github.com/filimonov/271e5b27c085356c67db3c1bf2204506 -// -// Why a generator: the browser only restores a saved chart config when the -// entry's `spec.panel.key` exactly equals schemaKey(resultColumns) = "name:type|…" -// (see src/ui/results.js chartCfgFor / src/core/chart-data.js schemaKey). -// Hand-writing those type strings is error-prone (Enum8/LowCardinality wrap -// exactly), so we derive each key live from `DESCRIBE ()` against a -// real cluster, read through FORMAT JSON so the type string matches exactly -// what the app's HTTP+JSON interface receives (clickhouse-client's default -// TSV output escapes embedded quotes differently and will silently produce a -// key that never matches at runtime). -// -// A handful of entries are plain live-snapshot tables (system.processes, -// system.merges, …) with no chart — those need SELECT privilege on the -// underlying table but no query history, and are commonly close to empty on -// an idle cluster (that's a legitimate result, not a bug). -// -// Every time-ranged *_log query shares two ClickHouse native query parameters, -// `{from:String}`/`{to:String}` (parsed via parseDateTimeBestEffort), instead -// of a hardcoded `now() - INTERVAL …`. Same param names across every entry -// means the Dashboard's global filter bar (#149 D3) renders ONE From/To pair -// that drives all six time-ranged tiles at once. DESCRIBE can't resolve an -// unbound parameter, so schemaKey() below binds throwaway test values via -// `--param_from`/`--param_to` purely to derive column types — the *shipped* -// SQL keeps the placeholders unbound for the browser to fill in. -// -// Run: node examples/mjs/build-system-explorer-charts.mjs [connection-name] -// Needs a `clickhouse-client` connection with SELECT on system.* — NOT the -// narrow "demo" fixture user some clusters expose by default (it can't read -// system.processes/query_log/etc). Defaults to `github-admin`; this file was -// authored against the github.demo cluster via -// kubectl exec chi-github-github-0-0-0 -c clickhouse-pod -- -// clickhouse-client --user clickhouse_operator --password "$PASS" ... -// since no adequately-privileged named CLI connection existed in that session. -// Out: examples/system-explorer-charts.json - -import { execFileSync } from 'node:child_process'; -import { fileURLToPath } from 'node:url'; -import { dirname, resolve } from 'node:path'; -import { buildDashboard, writeExampleBundle } from './example-bundle.mjs'; - -const here = dirname(fileURLToPath(import.meta.url)); -const CONNECTION = process.argv[2] || 'github-admin'; - -// Each spec: a query + (for chartable ones) the chart we want it to open -// with. `cfg` matches the app's shape { type, x, y:[...], series }; x/series -// are column indices, y a list of measure-column indices. A spec with no -// `cfg` is a live-snapshot table — no chart, opens in Table view. -const SPECS = [ - { - name: 'Currently running queries', - description: 'Live snapshot of system.processes — every query executing right now, slowest first. Empty when the cluster is idle; that\'s a real "nothing running" result, not an error.', - sql: `SELECT query_id, user, elapsed, read_rows, formatReadableSize(memory_usage) AS memory, left(query, 80) AS query -FROM system.processes -ORDER BY elapsed DESC -LIMIT 20`, - }, - { - name: 'Merges in progress', - description: 'Live snapshot of system.merges — background merges currently running, with progress and compressed size. Usually empty between merge cycles on a small cluster.', - sql: `SELECT database, table, elapsed, round(progress * 100, 1) AS pct_done, num_parts, is_mutation, formatReadableSize(total_size_bytes_compressed) AS size -FROM system.merges -ORDER BY elapsed DESC -LIMIT 20`, - }, - { - name: 'Mutations in progress', - description: 'Unfinished ALTER UPDATE/DELETE mutations from system.mutations, with the failure reason if one is stuck retrying.', - sql: `SELECT database, table, mutation_id, command, parts_to_do, latest_fail_reason -FROM system.mutations -WHERE NOT is_done -ORDER BY create_time -LIMIT 20`, - }, - { - name: 'Replication status', - description: 'system.replicas health per table — leadership, read-only state, replication delay, and queue depth. Sorted worst-lag-first.', - sql: `SELECT database, table, is_leader, is_readonly, absolute_delay, queue_size, inserts_in_queue, merges_in_queue -FROM system.replicas -ORDER BY absolute_delay DESC -LIMIT 20`, - }, - { - name: 'Stuck replication queue entries', - description: 'system.replication_queue entries that have already failed and retried at least once, with the last exception — the first place to look when a replica falls behind.', - sql: `SELECT database, table, type, create_time, num_tries, last_exception -FROM system.replication_queue -WHERE num_tries > 0 -ORDER BY num_tries DESC -LIMIT 20`, - }, - { - name: 'Largest tables by disk usage', - description: 'Every active part in system.parts, summed per table, largest first. Horizontal Bar — hover any bar for the exact byte count.', - cfg: { type: 'hbar', x: 0, y: [1], series: null }, - sql: `SELECT concat(database, '.', table) AS table, sum(bytes_on_disk) AS disk_bytes -FROM system.parts -WHERE active -GROUP BY database, table -ORDER BY disk_bytes DESC -LIMIT 15`, - }, - { - name: 'Active parts by table', - description: 'Active part *count* per table (not size) — a table climbing here between refreshes is trending toward "too many parts". Horizontal Bar.', - cfg: { type: 'hbar', x: 0, y: [1], series: null }, - sql: `SELECT concat(database, '.', table) AS table, count() AS parts -FROM system.parts -WHERE active -GROUP BY database, table -ORDER BY parts DESC -LIMIT 15`, - }, - { - name: 'Cumulative error counters', - description: 'system.errors — every error code the server has hit since last restart, most frequent first. A quick "what\'s actually going wrong here" check. Horizontal Bar.', - cfg: { type: 'hbar', x: 0, y: [1], series: null }, - sql: `SELECT name, value AS times -FROM system.errors -WHERE value > 0 -ORDER BY value DESC -LIMIT 15`, - }, - { - name: 'Queries per minute', - description: 'Finished-query volume from system.query_log, bucketed per minute, over a {from:String}/{to:String} range (shared with every other time-ranged query below — the Dashboard filter bar renders one From/To pair that drives them all). A DateTime X axis is auto-detected as a time series → Line chart.', - cfg: { type: 'line', x: 0, y: [1], series: null }, - sql: `SELECT toStartOfMinute(event_time) AS t, count() AS queries -FROM system.query_log -WHERE event_time BETWEEN parseDateTimeBestEffort({from:String}) AND parseDateTimeBestEffort({to:String}) AND type = 'QueryFinish' -GROUP BY t -ORDER BY t`, - }, - { - name: 'Slowest query patterns — avg duration', - description: 'Distinct query shapes (system.query_log grouped by normalized_query_hash) ranked by average duration over a {from:String}/{to:String} range. Horizontal Bar of a non-count measure.', - cfg: { type: 'hbar', x: 0, y: [1], series: null }, - sql: `SELECT left(any(query), 50) AS query, avg(query_duration_ms) AS avg_duration_ms -FROM system.query_log -WHERE event_time BETWEEN parseDateTimeBestEffort({from:String}) AND parseDateTimeBestEffort({to:String}) AND type = 'QueryFinish' -GROUP BY normalized_query_hash -ORDER BY avg_duration_ms DESC -LIMIT 15`, - }, - { - name: 'Query errors over time', - description: 'Failed queries from system.query_log over a {from:String}/{to:String} range, broken down by ClickHouse error name. The "error" column is used as the Series, producing grouped/stacked bars per error code.', - cfg: { type: 'bar', x: 0, y: [2], series: 1 }, - sql: `SELECT toStartOfHour(event_time) AS t, errorCodeToName(exception_code) AS error, count() AS n -FROM system.query_log -WHERE event_time BETWEEN parseDateTimeBestEffort({from:String}) AND parseDateTimeBestEffort({to:String}) AND exception_code != 0 -GROUP BY t, error -ORDER BY t`, - }, - { - name: 'Part lifecycle events over time', - description: 'system.part_log over a {from:String}/{to:String} range — new/merged/mutated/downloaded/removed parts per hour, one query instead of five separate panels. "event_type" is the Series.', - cfg: { type: 'bar', x: 0, y: [2], series: 1 }, - sql: `SELECT toStartOfHour(event_time) AS t, event_type, count() AS n -FROM system.part_log -WHERE event_time BETWEEN parseDateTimeBestEffort({from:String}) AND parseDateTimeBestEffort({to:String}) -GROUP BY t, event_type -ORDER BY t`, - }, - { - name: 'Memory usage over time', - description: 'Average tracked memory (system.metric_log\'s CurrentMetric_MemoryTracking) per minute over a {from:String}/{to:String} range. Line chart.', - cfg: { type: 'line', x: 0, y: [1], series: null }, - sql: `SELECT toStartOfMinute(event_time) AS t, avg(CurrentMetric_MemoryTracking) AS memory_bytes -FROM system.metric_log -WHERE event_time BETWEEN parseDateTimeBestEffort({from:String}) AND parseDateTimeBestEffort({to:String}) -GROUP BY t -ORDER BY t`, - }, - { - name: 'Query cost breakdown — slowest patterns (detail)', - description: 'The deep-dive version of "Slowest query patterns": executions, max/avg duration, rows and bytes read, and p99 memory per query shape over a {from:String}/{to:String} range. Table view — too many columns for one chart, but the full picture behind the bar chart above.', - sql: `SELECT - normalized_query_hash, - left(argMax(query, query_duration_ms), 60) AS sample_query, - count() AS executions, - max(query_duration_ms) AS max_ms, - avg(query_duration_ms) AS avg_ms, - sum(read_rows) AS read_rows, - formatReadableSize(sum(read_bytes)) AS read_bytes, - quantile(0.99)(memory_usage) AS p99_memory -FROM system.query_log -WHERE event_time BETWEEN parseDateTimeBestEffort({from:String}) AND parseDateTimeBestEffort({to:String}) AND type = 'QueryFinish' -GROUP BY normalized_query_hash -ORDER BY avg_ms DESC -LIMIT 15`, - }, -]; - -// Throwaway values just to let DESCRIBE/FORMAT JSON resolve column types for -// queries that reference {from:String}/{to:String} — never shipped in the -// output; the JSON's `sql` keeps the placeholders unbound. -const TEST_FROM = '2026-07-01 00:00:00'; -const TEST_TO = '2026-07-08 00:00:00'; - -const ch = (query) => - execFileSync('clickhouse-client', [ - '--connection', CONNECTION, - '--param_from', TEST_FROM, - '--param_to', TEST_TO, - '--query', query, - ], { - encoding: 'utf8', - maxBuffer: 64 * 1024 * 1024, - }); - -// schemaKey == columns.map(c => c.name + ':' + c.type).join('|'), derived via -// FORMAT JSON (not clickhouse-client's default TSV, which escapes embedded -// quotes in e.g. an Enum8(...) type string differently from the HTTP+JSON -// interface the app actually uses) so it matches exactly what the browser -// receives at run time. -function schemaKey(sql) { - const out = JSON.parse(ch(`SELECT * FROM (${sql}) LIMIT 1 FORMAT JSON`)); - return out.meta.map((m) => `${m.name}:${m.type}`).join('|'); -} - -const queries = SPECS.map((s, i) => { - const base = { - id: 'sys-' + (i + 1), - sql: s.sql, - specVersion: 1, - spec: { name: s.name, favorite: !!s.cfg, description: s.description }, - }; - if (!s.cfg) return base; - const key = schemaKey(s.sql); - console.log(`#${i + 1} ${s.cfg.type.padEnd(4)} key=${key}`); - return { ...base, spec: { ...base.spec, panel: { cfg: s.cfg, key }, view: 'panel' } }; -}); - -const dashboard = buildDashboard({ - id: 'system-explorer', - title: 'ClickHouse system explorer', - description: 'Operational views over ClickHouse system tables.', - queries, - tileQueryIds: queries.filter((query) => query.spec.favorite).map((query) => query.id), - preset: 'columns-2', -}); - -const outPath = resolve(here, 'system-explorer-charts.json'); -writeExampleBundle(outPath, { - exportedAt: new Date().toISOString(), - metadata: { name: dashboard.title, description: dashboard.description }, - queries, - dashboards: [dashboard], -}); -console.log(`\nwrote ${outPath} (${queries.length} queries, ${queries.filter((q) => q.spec.favorite).length} favorited for the Dashboard)`); diff --git a/examples/mjs/example-bundle.mjs b/examples/mjs/example-bundle.mjs index 7cd3e566..6aa83f0c 100644 --- a/examples/mjs/example-bundle.mjs +++ b/examples/mjs/example-bundle.mjs @@ -47,6 +47,14 @@ function placementFor(query, preset) { return { span: 1, height: 'large' }; } +function flowLayout(selected, tiles, preset, placements = {}) { + const items = Object.fromEntries(selected.map((query, index) => [ + tiles[index].id, + placements[query.id] || placementFor(query, preset), + ])); + return { type: 'flow', version: 1, preset, items }; +} + function scanParameterNames(sql) { const names = []; const seen = new Set(); @@ -68,6 +76,10 @@ export function buildDashboard({ tileQueryIds, sourceByParameter = {}, preset = 'columns-2', + filters: authoredFilters, + grid, + flowPlacements = {}, + revision = 1, }) { if (!id || !title) throw new Error('Dashboard id and title are required'); if (!['report', 'columns-2', 'columns-3'].includes(preset)) { @@ -85,10 +97,7 @@ export function buildDashboard({ }); const tiles = selected.map((query) => ({ id: `tile-${query.id}`, queryId: query.id })); - const items = Object.fromEntries(selected.map((query, index) => [ - tiles[index].id, - placementFor(query, preset), - ])); + const fallback = flowLayout(selected, tiles, preset, flowPlacements); const parameterNames = []; const seenParameters = new Set(); @@ -101,19 +110,32 @@ export function buildDashboard({ } } - const filters = parameterNames.map((parameter) => ({ + const inferredFilters = parameterNames.map((parameter) => ({ id: `filter-${parameter}`, parameter, ...(sourceByParameter[parameter] ? { sourceQueryId: sourceByParameter[parameter] } : {}), })); + const filters = authoredFilters === undefined ? inferredFilters : clone(authoredFilters); + const layout = grid + ? { + type: 'grafana-grid', + version: 1, + items: Object.fromEntries(selected.map((query, index) => [ + tiles[index].id, + grid[query.id] || { span: 6, height: 2 }, + ])), + fallback, + } + : fallback; + return { documentVersion: 1, id, title, ...(description ? { description } : {}), - revision: 1, - layout: { type: 'flow', version: 1, preset, items }, + revision, + layout, filters, tiles, }; @@ -190,8 +212,15 @@ export function assertValidExampleBundle(document) { if (!Array.isArray(dashboard.tiles) || !Array.isArray(dashboard.filters)) { throw new Error(`Dashboard ${JSON.stringify(dashboard.id)} requires tiles and filters arrays`); } - if (dashboard.layout?.type !== 'flow' || dashboard.layout?.version !== 1) { - throw new Error(`Dashboard ${JSON.stringify(dashboard.id)} must use flow@1`); + const layout = dashboard.layout; + const supportedLayout = layout?.version === 1 + && (layout.type === 'flow' || layout.type === 'grafana-grid'); + if (!supportedLayout) { + throw new Error(`Dashboard ${JSON.stringify(dashboard.id)} must use flow@1 or grafana-grid@1`); + } + if (layout.type === 'grafana-grid' + && (layout.fallback?.type !== 'flow' || layout.fallback?.version !== 1)) { + throw new Error(`Dashboard ${JSON.stringify(dashboard.id)} grafana-grid@1 layout requires a flow@1 fallback`); } for (const tile of dashboard.tiles) { if (!queryIds.has(tile.queryId)) { diff --git a/examples/mjs/normalize-examples.mjs b/examples/mjs/normalize-examples.mjs index 4d72897c..ab413f57 100644 --- a/examples/mjs/normalize-examples.mjs +++ b/examples/mjs/normalize-examples.mjs @@ -19,42 +19,17 @@ const examples = resolve(here, '..'); const checkOnly = process.argv.includes('--check'); const CONFIG = { - 'kpi-panel.json': { - id: 'kpi-panel-example', title: 'KPI panel example', preset: 'report', - description: 'Scalar and named-tuple KPI presentation example.', - }, - 'text-log-panel.json': { - id: 'text-log-panel-example', title: 'Server log panel example', preset: 'report', - description: 'Parameterized system.text_log dashboard example.', - }, 'shop-charts.json': { - id: 'shop-charts', title: 'Shop analytics', preset: 'columns-2', - description: 'Chart examples over the sample shop dataset.', + id: 'shop-analytics', title: 'Shop analytics', authoredDashboard: true, + description: 'Revenue, buyers, products, geography, and traffic over the shop-demo.sql dataset.', }, - 'query-log-explorer.json': { - id: 'query-log-explorer', title: 'Query log explorer', preset: 'columns-2', - description: 'ClickHouse query-log health, performance, and usage dashboard.', - sourceByParameter: { user: 'qle-filter', query_kind: 'qle-filter' }, - }, - 'grafana-clickhouse-ops-enhanced.json': { - id: 'clickhouse-ops-enhanced', title: 'ClickHouse operations', preset: 'columns-2', - description: 'Operational ClickHouse dashboard adapted from the Grafana dashboard.', - sourceByParameter: { - is_initial_query: 'gco-filter', - query_kind: 'gco-filter', - user: 'gco-filter', - exception_code: 'gco-filter', - query_hash: 'gco-filter', - metric: 'gco-filter', - }, + 'clickhouse-operations.json': { + id: 'clickhouse-operations', title: 'ClickHouse Operations', authoredDashboard: true, + description: 'Operator-first server overview, resources, background work, and investigation views.', }, 'ontime-charts.json': { - id: 'ontime-chart-gallery', title: 'On-time chart gallery', preset: 'columns-2', allPanels: true, - description: 'Chart gallery over the public ontime flight dataset.', - }, - 'system-explorer-charts.json': { - id: 'system-explorer', title: 'ClickHouse system explorer', preset: 'columns-2', - description: 'Operational views over ClickHouse system tables.', + id: 'ontime-flights', title: 'On-time flights', authoredDashboard: true, + description: 'Flight punctuality, volume, carriers, airports, delays, and cancellations with a shared 2023 slice.', }, 'iceberg-catalog-dashboard.json': { id: 'iceberg-catalog-explorer', title: 'Iceberg catalog explorer — BI', preset: 'columns-2', @@ -111,7 +86,9 @@ function tileQueryIds(queries, config) { function normalizeDocument(name, document, config) { const queries = cleanStaleWording(queriesOf(document, name)); const selectedIds = config.dashboard === false ? [] : tileQueryIds(queries, config); - const dashboards = selectedIds.length ? [buildDashboard({ + const authored = config.authoredDashboard ? cleanStaleWording(document.dashboards?.[0]) : null; + if (config.authoredDashboard && !authored) throw new Error(`${name}: expected an authored Dashboard`); + const dashboards = authored ? [authored] : selectedIds.length ? [buildDashboard({ id: config.id, title: config.title, description: config.description, diff --git a/examples/ontime-charts.json b/examples/ontime-charts.json index 4390ae38..4193aa3a 100644 --- a/examples/ontime-charts.json +++ b/examples/ontime-charts.json @@ -2,18 +2,64 @@ "$schema": "https://altinity.com/schemas/altinity-sql-browser/portable-bundle-v1.schema.json", "format": "altinity-sql-browser/portable-bundle", "version": 1, - "exportedAt": "2026-06-24T13:22:11.940Z", + "exportedAt": "2026-07-22T00:00:00.000Z", "metadata": { - "name": "On-time chart gallery", - "description": "Chart gallery over the public ontime flight dataset." + "name": "On-time flights", + "description": "Flight punctuality, volume, carriers, airports, delays, and cancellations with a shared 2023 slice." }, "queries": [ + { + "id": "ontime-kpis", + "sql": "SELECT count() AS flights, round(100 * countIf(f.ArrDel15 = 0) / count(), 1) AS on_time_pct, countIf(f.Cancelled = 1) AS cancellations, round(avg(f.DepDelayMinutes), 1) AS avg_departure_delay\nFROM ontime.fact_ontime AS f\nWHERE f.FlightDate BETWEEN {from:Date} AND {to:Date}\n/*[ AND has({carrier:Array(String)}, f.Carrier) ]*/\n/*[ AND has({origin:Array(String)}, f.OriginCode) ]*/", + "specVersion": 1, + "spec": { + "name": "Flight KPIs", + "description": "Flight volume, punctuality, cancellations, and mean departure delay for the selected slice.", + "favorite": true, + "view": "panel", + "panel": { + "cfg": { + "type": "kpi" + }, + "fieldConfig": { + "columns": { + "flights": { + "displayName": "Flights", + "decimals": 0 + }, + "on_time_pct": { + "displayName": "On time", + "unit": "%", + "decimals": 1 + }, + "cancellations": { + "displayName": "Cancellations", + "decimals": 0 + }, + "avg_departure_delay": { + "displayName": "Avg departure delay", + "unit": " min", + "decimals": 1 + } + } + } + }, + "dashboard": { + "role": "panel", + "sizeHints": { + "preferred": "compact", + "minimum": "compact", + "aspectRatio": 2 + } + } + } + }, { "id": "s1", - "sql": "SELECT\n a.DisplayAirportName AS airport,\n count() AS flights\nFROM ontime.fact_ontime AS f\nINNER JOIN ontime.dim_airports AS a\n ON a.AirportCode = f.OriginCode AND a.IsLatest = 1\nWHERE f.Year = 2023\nGROUP BY airport\nORDER BY flights DESC\nLIMIT 15", + "sql": "SELECT a.DisplayAirportName AS airport, count() AS flights\nFROM ontime.fact_ontime AS f\nINNER JOIN ontime.dim_airports AS a ON a.AirportCode = f.OriginCode AND a.IsLatest = 1\nWHERE f.FlightDate BETWEEN {from:Date} AND {to:Date}\n/*[ AND has({carrier:Array(String)}, f.Carrier) ]*/\n/*[ AND has({origin:Array(String)}, f.OriginCode) ]*/\nGROUP BY airport ORDER BY flights DESC LIMIT 15", "specVersion": 1, "spec": { - "name": "Busiest origin airports — 2023", + "name": "Busiest origins", "favorite": true, "description": "Top 15 departure airports by flight count (joined to dim_airports for readable names). Horizontal Bar — hover any bar, long or short, to read its exact value.", "view": "panel", @@ -44,7 +90,7 @@ "specVersion": 1, "spec": { "name": "Flights by month — 2023", - "favorite": true, + "favorite": false, "description": "Monthly US flight volume. A numeric column named \"month\" is detected as an ordinal axis → vertical Column chart, with K/M-humanised value ticks.", "view": "panel", "panel": { @@ -70,10 +116,10 @@ }, { "id": "s3", - "sql": "SELECT FlightDate AS date, count() AS flights\nFROM ontime.fact_ontime\nWHERE Year = 2023\nGROUP BY date\nORDER BY date", + "sql": "SELECT f.FlightDate AS date, count() AS flights\nFROM ontime.fact_ontime AS f\nWHERE f.FlightDate BETWEEN {from:Date} AND {to:Date}\n/*[ AND has({carrier:Array(String)}, f.Carrier) ]*/\n/*[ AND has({origin:Array(String)}, f.OriginCode) ]*/\nGROUP BY date ORDER BY date", "specVersion": 1, "spec": { - "name": "Daily flights — 2023", + "name": "Daily flights", "favorite": true, "description": "One point per day across 2023 (~365 rows). A Date X axis is auto-detected as a time series → Line chart.", "view": "panel", @@ -100,10 +146,10 @@ }, { "id": "s4", - "sql": "SELECT\n FlightDate AS date,\n round(100 * countIf(ArrDel15 = 0) / count(), 1) AS on_time_pct\nFROM ontime.fact_ontime\nWHERE Year = 2023\nGROUP BY date\nORDER BY date", + "sql": "SELECT f.FlightDate AS date, round(100 * countIf(f.ArrDel15 = 0) / count(), 1) AS on_time_pct\nFROM ontime.fact_ontime AS f\nWHERE f.FlightDate BETWEEN {from:Date} AND {to:Date}\n/*[ AND has({carrier:Array(String)}, f.Carrier) ]*/\n/*[ AND has({origin:Array(String)}, f.OriginCode) ]*/\nGROUP BY date ORDER BY date", "specVersion": 1, "spec": { - "name": "Daily on-time rate — 2023", + "name": "On-time rate", "favorite": true, "description": "Share of flights arriving on time (< 15 min late) per day, as a percentage. Rendered as a filled Area chart.", "view": "panel", @@ -130,10 +176,10 @@ }, { "id": "s5", - "sql": "SELECT\n multiIf(CancellationCode = 'A', 'Carrier',\n CancellationCode = 'B', 'Weather',\n CancellationCode = 'C', 'National Air System',\n CancellationCode = 'D', 'Security', 'Other') AS reason,\n count() AS cancellations\nFROM ontime.fact_ontime\nWHERE Year = 2023 AND Cancelled = 1\nGROUP BY reason\nORDER BY cancellations DESC", + "sql": "SELECT multiIf(f.CancellationCode = 'A', 'Carrier', f.CancellationCode = 'B', 'Weather', f.CancellationCode = 'C', 'National Air System', f.CancellationCode = 'D', 'Security', 'Other') AS reason, count() AS cancellations\nFROM ontime.fact_ontime AS f\nWHERE f.FlightDate BETWEEN {from:Date} AND {to:Date}\n/*[ AND has({carrier:Array(String)}, f.Carrier) ]*/\n/*[ AND has({origin:Array(String)}, f.OriginCode) ]*/ AND f.Cancelled = 1\nGROUP BY reason ORDER BY cancellations DESC", "specVersion": 1, "spec": { - "name": "Cancellation reasons — 2023", + "name": "Cancellation reasons", "favorite": true, "description": "Why flights were cancelled in 2023 (carrier / weather / national air system / security). A small categorical breakdown → Pie chart with a legend.", "view": "panel", @@ -160,10 +206,10 @@ }, { "id": "s6", - "sql": "SELECT\n Month AS month,\n Carrier AS carrier,\n count() AS flights\nFROM ontime.fact_ontime\nWHERE Year = 2023 AND Carrier IN ('WN', 'AA', 'DL', 'UA')\nGROUP BY month, carrier\nORDER BY month, carrier", + "sql": "SELECT toStartOfMonth(f.FlightDate) AS month, f.Carrier AS carrier, count() AS flights\nFROM ontime.fact_ontime AS f\nWHERE f.FlightDate BETWEEN {from:Date} AND {to:Date}\n/*[ AND has({carrier:Array(String)}, f.Carrier) ]*/\n/*[ AND has({origin:Array(String)}, f.OriginCode) ]*/\nGROUP BY month, carrier ORDER BY month, carrier", "specVersion": 1, "spec": { - "name": "Monthly flights by carrier — 2023", + "name": "Monthly carrier volume", "favorite": true, "description": "Flights per month split across four major carriers (WN, AA, DL, UA). The \"carrier\" column is used as the Series, producing grouped bars with a per-carrier legend.", "view": "panel", @@ -176,7 +222,7 @@ ], "series": 1 }, - "key": "month:UInt8|carrier:LowCardinality(String)|flights:UInt64" + "key": "month:Date|carrier:LowCardinality(String)|flights:UInt64" }, "dashboard": { "role": "panel", @@ -190,10 +236,10 @@ }, { "id": "s7", - "sql": "SELECT\n Carrier AS carrier,\n round(avg(CarrierDelay), 1) AS carrier_delay,\n round(avg(WeatherDelay), 1) AS weather_delay,\n round(avg(NASDelay), 1) AS nas_delay,\n round(avg(LateAircraftDelay), 1) AS late_aircraft_delay\nFROM ontime.fact_ontime\nWHERE Year = 2023 AND ArrDel15 = 1\nGROUP BY carrier\nORDER BY carrier_delay DESC\nLIMIT 12", + "sql": "SELECT f.Carrier AS carrier, round(avg(f.CarrierDelay), 1) AS carrier_delay, round(avg(f.WeatherDelay), 1) AS weather_delay, round(avg(f.NASDelay), 1) AS nas_delay, round(avg(f.LateAircraftDelay), 1) AS late_aircraft_delay\nFROM ontime.fact_ontime AS f\nWHERE f.FlightDate BETWEEN {from:Date} AND {to:Date}\n/*[ AND has({carrier:Array(String)}, f.Carrier) ]*/\n/*[ AND has({origin:Array(String)}, f.OriginCode) ]*/ AND f.ArrDel15 = 1\nGROUP BY carrier ORDER BY carrier_delay DESC LIMIT 12", "specVersion": 1, "spec": { - "name": "Average delay breakdown by carrier — 2023", + "name": "Delay causes by carrier", "favorite": true, "description": "Mean minutes of each delay cause (carrier, weather, NAS, late aircraft) for delayed flights, per carrier. Four measures plotted at once (\"All measures\") as grouped columns.", "view": "panel", @@ -227,7 +273,7 @@ "specVersion": 1, "spec": { "name": "Daily flights since 2022", - "favorite": true, + "favorite": false, "description": "Every day from 2022 onward (~1,460 points). The chart plots the first 500 and shows a \"first 500 of N rows\" note — the table view still has them all.", "view": "panel", "panel": { @@ -257,7 +303,7 @@ "specVersion": 1, "spec": { "name": "Flights by day of week — 2023", - "favorite": true, + "favorite": false, "description": "Volume by day of week (1 = Monday … 7 = Sunday). \"dayofweek\" is recognised as an ordinal axis → Column chart.", "view": "panel", "panel": { @@ -287,7 +333,7 @@ "specVersion": 1, "spec": { "name": "Worst average departure delay by airport — 2023", - "favorite": true, + "favorite": false, "description": "Airports with the highest mean departure delay (minutes) among those with ≥ 10,000 departures in 2023. Horizontal Bar of a non-count measure, joined for names.", "view": "panel", "panel": { @@ -310,71 +356,169 @@ } } } + }, + { + "id": "ontime-filter-options", + "sql": "SELECT\n (SELECT arraySort(groupUniqArray(Carrier)) FROM ontime.fact_ontime WHERE Carrier != '') AS carrier,\n (SELECT groupArray((AirportCode AS value, DisplayAirportName AS label)) FROM (SELECT AirportCode, any(DisplayAirportName) AS DisplayAirportName FROM ontime.dim_airports WHERE IsLatest = 1 GROUP BY AirportCode ORDER BY DisplayAirportName)) AS origin", + "specVersion": 1, + "spec": { + "name": "On-time dimensions", + "description": "Carrier codes and airport-code values with readable airport labels.", + "favorite": false, + "dashboard": { + "role": "filter" + } + } } ], "dashboards": [ { "documentVersion": 1, - "id": "ontime-chart-gallery", - "title": "On-time chart gallery", - "description": "Chart gallery over the public ontime flight dataset.", + "id": "ontime-flights", + "title": "On-time flights", + "description": "Flight punctuality, volume, carriers, airports, delays, and cancellations with a shared 2023 slice.", "revision": 1, "layout": { - "type": "flow", + "type": "grafana-grid", "version": 1, - "preset": "columns-2", "items": { - "tile-s1": { - "span": 1, - "height": "large" - }, - "tile-s2": { - "span": 1, - "height": "large" + "tile-ontime-kpis": { + "span": 12, + "height": 2 }, "tile-s3": { - "span": 1, - "height": "large" + "span": 8, + "height": 3 }, "tile-s4": { - "span": 1, - "height": "large" + "span": 4, + "height": 3 }, - "tile-s5": { - "span": 1, - "height": "large" + "tile-s1": { + "span": 6, + "height": 3 }, "tile-s6": { - "span": 1, - "height": "large" + "span": 6, + "height": 3 }, "tile-s7": { - "span": 1, - "height": "large" + "span": 8, + "height": 3 }, - "tile-s8": { - "span": 1, - "height": "large" - }, - "tile-s9": { - "span": 1, - "height": "large" - }, - "tile-s10": { - "span": 1, - "height": "large" + "tile-s5": { + "span": 4, + "height": 3 + } + }, + "fallback": { + "type": "flow", + "version": 1, + "preset": "columns-2", + "items": { + "tile-ontime-kpis": { + "span": 2, + "height": "compact" + }, + "tile-s3": { + "span": 1, + "height": "large" + }, + "tile-s4": { + "span": 1, + "height": "large" + }, + "tile-s1": { + "span": 1, + "height": "large" + }, + "tile-s6": { + "span": 1, + "height": "large" + }, + "tile-s7": { + "span": 1, + "height": "large" + }, + "tile-s5": { + "span": 1, + "height": "large" + } } } }, - "filters": [], - "tiles": [ + "filters": [ { - "id": "tile-s1", - "queryId": "s1" + "id": "ontime-date-from", + "parameter": "from", + "label": "From", + "defaultValue": "2023-01-01", + "defaultActive": true, + "targets": [ + "tile-ontime-kpis", + "tile-s3", + "tile-s4", + "tile-s1", + "tile-s6", + "tile-s7", + "tile-s5" + ] }, { - "id": "tile-s2", - "queryId": "s2" + "id": "ontime-date-to", + "parameter": "to", + "label": "To", + "defaultValue": "2023-12-31", + "defaultActive": true, + "targets": [ + "tile-ontime-kpis", + "tile-s3", + "tile-s4", + "tile-s1", + "tile-s6", + "tile-s7", + "tile-s5" + ] + }, + { + "id": "ontime-carrier", + "parameter": "carrier", + "label": "Carrier", + "sourceQueryId": "ontime-filter-options", + "defaultValue": [], + "defaultActive": false, + "targets": [ + "tile-ontime-kpis", + "tile-s3", + "tile-s4", + "tile-s1", + "tile-s6", + "tile-s7", + "tile-s5" + ] + }, + { + "id": "ontime-origin", + "parameter": "origin", + "label": "Origin airport", + "sourceQueryId": "ontime-filter-options", + "defaultValue": [], + "defaultActive": false, + "targets": [ + "tile-ontime-kpis", + "tile-s3", + "tile-s4", + "tile-s1", + "tile-s6", + "tile-s7", + "tile-s5" + ] + } + ], + "tiles": [ + { + "id": "tile-ontime-kpis", + "queryId": "ontime-kpis" }, { "id": "tile-s3", @@ -385,8 +529,8 @@ "queryId": "s4" }, { - "id": "tile-s5", - "queryId": "s5" + "id": "tile-s1", + "queryId": "s1" }, { "id": "tile-s6", @@ -397,16 +541,8 @@ "queryId": "s7" }, { - "id": "tile-s8", - "queryId": "s8" - }, - { - "id": "tile-s9", - "queryId": "s9" - }, - { - "id": "tile-s10", - "queryId": "s10" + "id": "tile-s5", + "queryId": "s5" } ] } diff --git a/examples/query-log-explorer.json b/examples/query-log-explorer.json deleted file mode 100644 index dafff817..00000000 --- a/examples/query-log-explorer.json +++ /dev/null @@ -1,305 +0,0 @@ -{ - "$schema": "https://altinity.com/schemas/altinity-sql-browser/portable-bundle-v1.schema.json", - "format": "altinity-sql-browser/portable-bundle", - "version": 1, - "exportedAt": "2026-07-15T00:00:00.000Z", - "metadata": { - "name": "Query log explorer", - "description": "ClickHouse query-log health, performance, and usage dashboard." - }, - "queries": [ - { - "id": "qle-filter", - "sql": "SELECT\n arraySort(item -> item.label, groupUniqArray((user AS value, splitByChar('@', user)[1] AS label))) AS user,\n arraySort(groupUniqArray(query_kind)) AS query_kind\nFROM system.query_log\nWHERE query_log.user != ''\n AND event_time >= {from:DateTime}\n /*[ AND event_time <= {to:DateTime} ]*/\nSETTINGS enable_named_columns_in_function_tuple = 1", - "specVersion": 1, - "spec": { - "name": "Filter", - "favorite": false, - "description": "Single Dashboard Filter source. `user` is a value/label bundle (Array(Tuple(value, label)) — #160): the value is the full user that binds to a Panel's {user:String}, the label is the name before '@' for display. `query_kind` is a plain Array(String). Upgrades any Panel's {user:String} and {query_kind:String} parameters to searchable single-select dropdowns.", - "dashboard": { - "role": "filter" - } - } - }, - { - "id": "qle-kpi", - "sql": "SELECT\n count() AS total_queries,\n countIf(exception_code != 0) AS errors,\n round(100 * countIf(exception_code != 0) / count(), 2) AS error_rate_pct,\n round(avg(query_duration_ms), 1) AS avg_duration_ms,\n round(quantile(0.99)(query_duration_ms), 1) AS p99_duration_ms,\n uniqExact(user) AS active_users\nFROM system.query_log\nWHERE type IN ('QueryFinish', 'ExceptionWhileProcessing') AND query_kind = 'Select'\n AND event_time >= {from:DateTime}\n/*[ AND event_time <= {to:DateTime} ]*/\n/*[ AND positionCaseInsensitive(query, {search:String}) > 0 ]*/", - "specVersion": 1, - "spec": { - "name": "Query log health (KPI)", - "favorite": true, - "description": "Top-line SELECT-query health for this cluster: volume, error rate, and latency. The time window is a real DateTime range shared by every data Panel: {from:DateTime} is REQUIRED (a Panel stays unfilled until you pick a start), {to:DateTime} is optional (blank means \"up to now\"). Both render as relative-time fields — type an absolute timestamp or a relative expression like -24h / -7d / now. The optional {search:String} field substring-matches the query text across every Panel too.", - "view": "panel", - "panel": { - "cfg": { - "type": "kpi" - }, - "fieldConfig": { - "columns": { - "total_queries": { - "displayName": "SELECT queries" - }, - "errors": { - "displayName": "Errors" - }, - "error_rate_pct": { - "displayName": "Error rate", - "unit": "%", - "decimals": 2 - }, - "avg_duration_ms": { - "displayName": "Avg duration", - "unit": " ms", - "decimals": 1 - }, - "p99_duration_ms": { - "displayName": "P99 duration", - "unit": " ms", - "decimals": 1 - }, - "active_users": { - "displayName": "Active users" - } - } - } - }, - "dashboard": { - "role": "panel", - "sizeHints": { - "preferred": "compact", - "minimum": "compact", - "aspectRatio": 2 - } - } - } - }, - { - "id": "qle-text", - "sql": "", - "specVersion": 1, - "spec": { - "name": "About this demo", - "favorite": true, - "description": "Static Text panel explaining the dashboard.", - "view": "panel", - "panel": { - "cfg": { - "type": "text", - "content": "# Dashboard Filter sources demo\n\nEvery bundled Dashboard tile below is a Panel; one separate **Filter**-role source (#160) that populates the filter bar above with `user` and `query_kind` option lists queried live from `system.query_log` on the **otel** demo cluster.\n\n**Shared filters** — plain, type-detected controls (no Filter source), declared by the data Panels:\n\n- `from` — **required** window start (`DateTime`); type an absolute time or a relative expression like `-24h`, `-7d`, `now`\n- `to` — optional window end (`DateTime`); blank means \"up to now\"\n- `search` — optional case-insensitive substring search over the query text (and, in the log panel, the exception message)\n- `minDurationMs` — optional minimum query-duration threshold, in ms (slowest-queries panel)\n\n**Curated filters** — sourced from the single favorited **Filter** query, upgrading matching Panel parameters to searchable dropdowns:\n\n- `user` — real ClickHouse users seen in the query log\n- `query_kind` — the query kinds seen in the query log (Select, Insert, …)\n\nAnalytical panels are adapted from the Altinity KB page [\"Handy queries for system.query_log\"](https://kb.altinity.com/altinity-kb-useful-queries/query_log/)." - } - }, - "dashboard": { - "role": "panel", - "sizeHints": { - "preferred": "wide", - "minimum": "medium", - "aspectRatio": 2 - } - } - } - }, - { - "id": "qle-slowest", - "sql": "SELECT\n event_time,\n user,\n round(query_duration_ms) AS duration_ms,\n formatReadableSize(memory_usage) AS memory,\n read_rows,\n substring(query, 1, 100) AS query_preview\nFROM system.query_log\nWHERE type = 'QueryFinish' AND query_kind = 'Select'\n AND event_time >= {from:DateTime}\n/*[ AND event_time <= {to:DateTime} ]*/\n/*[ AND user = {user:String} ]*/\n/*[ AND query_duration_ms >= {minDurationMs:UInt32} ]*/\n/*[ AND positionCaseInsensitive(query, {search:String}) > 0 ]*/\nORDER BY query_duration_ms DESC\nLIMIT 50", - "specVersion": 1, - "spec": { - "name": "Slowest SELECT queries", - "favorite": true, - "description": "Adapted from the Altinity KB's \"Most resource-intensive queries\" — the highest-latency completed SELECTs, with duration/memory/rows-read to spot what's actually expensive. Demonstrates the shared time range ({from:DateTime} required, {to:DateTime} optional), the CURATED {user:String} dropdown (from the Filter source), the AUTO {minDurationMs:UInt32} numeric threshold, and the universal {search:String} query-text search.", - "view": "panel", - "panel": { - "cfg": { - "type": "table" - } - }, - "dashboard": { - "role": "panel", - "sizeHints": { - "preferred": "wide", - "minimum": "medium", - "aspectRatio": 1.6 - } - } - } - }, - { - "id": "qle-columns", - "sql": "SELECT arrayJoin(columns) AS column, count() AS hits\nFROM system.query_log\nWHERE type = 'QueryFinish'\n AND event_time >= {from:DateTime}\n AND notEmpty(columns)\n/*[ AND event_time <= {to:DateTime} ]*/\n/*[ AND query_kind = {query_kind:String} ]*/\n/*[ AND positionCaseInsensitive(query, {search:String}) > 0 ]*/\nGROUP BY column\nORDER BY hits DESC\nLIMIT 20", - "specVersion": 1, - "spec": { - "name": "Most-selected columns", - "favorite": true, - "description": "Adapted from the Altinity KB's \"Most-selected columns\" — which columns have been referenced most often. Demonstrates the CURATED {query_kind:String} dropdown (from the Filter source) alongside the shared time range ({from:DateTime} required, {to:DateTime} optional) and the universal {search:String} search.", - "view": "panel", - "panel": { - "cfg": { - "type": "hbar", - "x": 0, - "y": [ - 1 - ], - "series": null - } - }, - "dashboard": { - "role": "panel", - "sizeHints": { - "preferred": "medium", - "minimum": "compact", - "aspectRatio": 1.5 - } - } - } - }, - { - "id": "qle-functions", - "sql": "SELECT arrayJoin(used_functions) AS function, count() AS hits\nFROM system.query_log\nWHERE type = 'QueryFinish'\n AND event_time >= {from:DateTime}\n AND notEmpty(used_functions)\n/*[ AND event_time <= {to:DateTime} ]*/\n/*[ AND positionCaseInsensitive(query, {search:String}) > 0 ]*/\nGROUP BY function\nORDER BY hits DESC\nLIMIT 20", - "specVersion": 1, - "spec": { - "name": "Most-used functions", - "favorite": true, - "description": "Adapted from the Altinity KB's \"Most-used functions\" — which SQL functions appear most often across executed queries. Uses the shared time range ({from:DateTime} required, {to:DateTime} optional) and the universal {search:String} query-text search, the same fields every other Panel declares.", - "view": "panel", - "panel": { - "cfg": { - "type": "hbar", - "x": 0, - "y": [ - 1 - ], - "series": null - } - }, - "dashboard": { - "role": "panel", - "sizeHints": { - "preferred": "medium", - "minimum": "compact", - "aspectRatio": 1.5 - } - } - } - }, - { - "id": "qle-logs", - "sql": "SELECT\n event_time,\n multiIf(exception_code != 0, 'error', query_duration_ms > 1000, 'warn', 'info') AS level,\n if(exception_code != 0, exception, query) AS message,\n user,\n query_kind,\n query_duration_ms,\n formatReadableSize(memory_usage) AS memory\nFROM system.query_log\nWHERE type IN ('QueryFinish', 'ExceptionWhileProcessing')\n AND query_kind NOT IN ('Insert', 'AsyncInsertFlush')\n AND event_time >= {from:DateTime}\n/*[ AND event_time <= {to:DateTime} ]*/\n/*[ AND user = {user:String} ]*/\n/*[ AND (positionCaseInsensitive(query, {search:String}) > 0 OR positionCaseInsensitive(exception, {search:String}) > 0) ]*/\nORDER BY event_time DESC\nLIMIT 300", - "specVersion": 1, - "spec": { - "name": "Recent query activity (log)", - "favorite": true, - "description": "Raw query_log rows as a Logs view, level-colored by outcome (error/slow/info) and excluding the noisy internal Insert/AsyncInsertFlush firehose so the feed reads as actual query activity. Uses the shared time range ({from:DateTime} required, {to:DateTime} optional), the CURATED {user:String} dropdown (from the Filter source), and the universal {search:String} search — which here matches both the query text and the exception message.", - "view": "panel", - "panel": { - "cfg": { - "type": "logs", - "time": "event_time", - "msg": "message", - "level": "level" - } - }, - "dashboard": { - "role": "panel", - "sizeHints": { - "preferred": "wide", - "minimum": "medium", - "aspectRatio": 1.6 - } - } - } - } - ], - "dashboards": [ - { - "documentVersion": 1, - "id": "query-log-explorer", - "title": "Query log explorer", - "description": "ClickHouse query-log health, performance, and usage dashboard.", - "revision": 1, - "layout": { - "type": "flow", - "version": 1, - "preset": "columns-2", - "items": { - "tile-qle-kpi": { - "span": 1, - "height": "compact" - }, - "tile-qle-text": { - "span": 2, - "height": "large" - }, - "tile-qle-slowest": { - "span": 2, - "height": "large" - }, - "tile-qle-columns": { - "span": 1, - "height": "large" - }, - "tile-qle-functions": { - "span": 1, - "height": "large" - }, - "tile-qle-logs": { - "span": 2, - "height": "large" - } - } - }, - "filters": [ - { - "id": "filter-from", - "parameter": "from" - }, - { - "id": "filter-to", - "parameter": "to" - }, - { - "id": "filter-search", - "parameter": "search" - }, - { - "id": "filter-user", - "parameter": "user", - "sourceQueryId": "qle-filter" - }, - { - "id": "filter-minDurationMs", - "parameter": "minDurationMs" - }, - { - "id": "filter-query_kind", - "parameter": "query_kind", - "sourceQueryId": "qle-filter" - } - ], - "tiles": [ - { - "id": "tile-qle-kpi", - "queryId": "qle-kpi" - }, - { - "id": "tile-qle-text", - "queryId": "qle-text" - }, - { - "id": "tile-qle-slowest", - "queryId": "qle-slowest" - }, - { - "id": "tile-qle-columns", - "queryId": "qle-columns" - }, - { - "id": "tile-qle-functions", - "queryId": "qle-functions" - }, - { - "id": "tile-qle-logs", - "queryId": "qle-logs" - } - ] - } - ] -} diff --git a/examples/shop-charts.json b/examples/shop-charts.json index 5dffcb0e..9a13c397 100644 --- a/examples/shop-charts.json +++ b/examples/shop-charts.json @@ -2,50 +2,66 @@ "$schema": "https://altinity.com/schemas/altinity-sql-browser/portable-bundle-v1.schema.json", "format": "altinity-sql-browser/portable-bundle", "version": 1, - "exportedAt": "2026-06-28T00:00:00.000Z", + "exportedAt": "2026-07-22T00:00:00.000Z", "metadata": { "name": "Shop analytics", - "description": "Chart examples over the sample shop dataset." + "description": "Revenue, buyers, products, geography, and traffic over the shop-demo.sql dataset." }, "queries": [ { - "id": "shop-revenue-by-country", - "sql": "SELECT country, sum(revenue) AS revenue\nFROM shop.daily_sales\nGROUP BY country\nORDER BY revenue DESC", + "id": "shop-kpis", + "sql": "SELECT round(sumIf(amount, event_type = 'purchase'), 2) AS revenue, countIf(event_type = 'purchase') AS purchases, uniqIf(user_id, event_type = 'purchase') AS active_buyers, round(100 * countIf(event_type = 'purchase') / count(), 1) AS conversion_rate\nFROM shop.events_raw\nWHERE event_time BETWEEN {from:DateTime} AND {to:DateTime}\n/*[ AND country IN {country:Array(String)} ]*/\n/*[ AND has({category:Array(String)}, dictGet('shop.products_dict', 'category', toUInt64(product_id))) ]*/", "specVersion": 1, "spec": { - "name": "Revenue by country", + "name": "Sales KPIs", + "description": "Revenue, purchases, active buyers, and purchase conversion for the selected slice.", "favorite": true, - "description": "Total revenue per country from the daily_sales aggregate (fed by the mv_daily_sales materialized view). A LowCardinality category on the X axis → horizontal Bar.", "view": "panel", "panel": { "cfg": { - "type": "hbar", - "x": 0, - "y": [ - 1 - ], - "series": null + "type": "kpi" }, - "key": "country:LowCardinality(String)|revenue:Decimal(38, 2)" + "fieldConfig": { + "columns": { + "revenue": { + "displayName": "Revenue", + "unit": " USD", + "decimals": 2 + }, + "purchases": { + "displayName": "Purchases", + "decimals": 0 + }, + "active_buyers": { + "displayName": "Active buyers", + "decimals": 0 + }, + "conversion_rate": { + "displayName": "Conversion rate", + "unit": "%", + "decimals": 1 + } + } + } }, "dashboard": { "role": "panel", "sizeHints": { - "preferred": "medium", + "preferred": "compact", "minimum": "compact", - "aspectRatio": 1.5 + "aspectRatio": 2 } } } }, { "id": "shop-daily-revenue", - "sql": "SELECT day, sum(revenue) AS revenue\nFROM shop.daily_sales\nGROUP BY day\nORDER BY day", + "sql": "SELECT day, sum(revenue) AS revenue FROM shop.daily_sales WHERE day BETWEEN toDate({from:DateTime}) AND toDate({to:DateTime})\n/*[ AND country IN {country:Array(String)} ]*/\nGROUP BY day ORDER BY day", "specVersion": 1, "spec": { "name": "Daily revenue", - "favorite": false, - "description": "Revenue per day across the last ~90 days, from daily_sales. A Date X axis is auto-detected as a time series → filled Area chart.", + "description": "", + "favorite": true, "view": "panel", "panel": { "cfg": { @@ -57,28 +73,36 @@ "series": null }, "key": "day:Date|revenue:Decimal(38, 2)" + }, + "dashboard": { + "role": "panel", + "sizeHints": { + "preferred": "medium", + "minimum": "compact", + "aspectRatio": 1.5 + } } } }, { - "id": "shop-revenue-by-category", - "sql": "SELECT category, sum(revenue) AS revenue\nFROM shop.category_revenue\nGROUP BY category\nORDER BY revenue DESC", + "id": "shop-revenue-by-country", + "sql": "SELECT country, sum(revenue) AS revenue FROM shop.daily_sales WHERE day BETWEEN toDate({from:DateTime}) AND toDate({to:DateTime})\n/*[ AND has({country:Array(String)}, country) ]*/\nGROUP BY country ORDER BY revenue DESC", "specVersion": 1, "spec": { - "name": "Revenue by category", + "name": "Revenue by country", + "description": "", "favorite": true, - "description": "Revenue per product category from category_revenue — the mv_category_revenue view derives the category with dictGet() against the products_dict dictionary. A small breakdown → Pie.", "view": "panel", "panel": { "cfg": { - "type": "pie", + "type": "hbar", "x": 0, "y": [ 1 ], "series": null }, - "key": "category:LowCardinality(String)|revenue:Decimal(38, 2)" + "key": "country:LowCardinality(String)|revenue:Decimal(38, 2)" }, "dashboard": { "role": "panel", @@ -91,13 +115,13 @@ } }, { - "id": "shop-event-mix", - "sql": "SELECT event_type, count() AS events\nFROM shop.events_raw\nGROUP BY event_type\nORDER BY events DESC", + "id": "shop-revenue-by-category", + "sql": "SELECT category, sum(revenue) AS revenue FROM shop.category_revenue WHERE day BETWEEN toDate({from:DateTime}) AND toDate({to:DateTime})\n/*[ AND has({category:Array(String)}, category) ]*/\nGROUP BY category ORDER BY revenue DESC", "specVersion": 1, "spec": { - "name": "Event type mix", - "favorite": false, - "description": "Share of raw events by type (purchase / view / cart) straight from events_raw. A small categorical split → Pie with a legend.", + "name": "Revenue by category", + "description": "", + "favorite": true, "view": "panel", "panel": { "cfg": { @@ -108,18 +132,26 @@ ], "series": null }, - "key": "event_type:LowCardinality(String)|events:UInt64" + "key": "category:LowCardinality(String)|revenue:Decimal(38, 2)" + }, + "dashboard": { + "role": "panel", + "sizeHints": { + "preferred": "medium", + "minimum": "compact", + "aspectRatio": 1.5 + } } } }, { "id": "shop-top-products", - "sql": "SELECT p.name AS product, round(sum(e.amount)) AS revenue\nFROM shop.events_raw AS e\nINNER JOIN shop.products AS p ON p.product_id = e.product_id\nWHERE e.event_type = 'purchase'\nGROUP BY product\nORDER BY revenue DESC\nLIMIT 15", + "sql": "SELECT dictGet('shop.products_dict', 'name', toUInt64(product_id)) AS product, round(sum(amount), 2) AS revenue FROM shop.events_raw WHERE event_type = 'purchase' AND event_time BETWEEN {from:DateTime} AND {to:DateTime}\n/*[ AND country IN {country:Array(String)} ]*/\n/*[ AND has({category:Array(String)}, dictGet('shop.products_dict', 'category', toUInt64(product_id))) ]*/\nGROUP BY product ORDER BY revenue DESC LIMIT 12", "specVersion": 1, "spec": { - "name": "Top 15 products by revenue", - "favorite": false, - "description": "Joins events_raw to the products dimension table for readable names, summing purchase amounts. Horizontal Bar of the top 15.", + "name": "Top products", + "description": "", + "favorite": true, "view": "panel", "panel": { "cfg": { @@ -131,17 +163,25 @@ "series": null }, "key": "product:String|revenue:Decimal(38, 2)" + }, + "dashboard": { + "role": "panel", + "sizeHints": { + "preferred": "medium", + "minimum": "compact", + "aspectRatio": 1.5 + } } } }, { "id": "shop-daily-active-users", - "sql": "SELECT toDate(hour) AS day, uniqMerge(users) AS users\nFROM shop.hourly_active_users\nGROUP BY day\nORDER BY day", + "sql": "SELECT toDate(hour) AS day, uniqMerge(users) AS users FROM shop.hourly_active_users WHERE hour BETWEEN {from:DateTime} AND {to:DateTime}\n/*[ AND country IN {country:Array(String)} ]*/\nGROUP BY day ORDER BY day", "specVersion": 1, "spec": { "name": "Daily active users", - "favorite": false, - "description": "Distinct users per day, merged from the hourly_active_users AggregatingMergeTree with uniqMerge() — shows how an AggregateFunction state is finalized. Date axis → Line.", + "description": "", + "favorite": true, "view": "panel", "panel": { "cfg": { @@ -153,28 +193,36 @@ "series": null }, "key": "day:Date|users:UInt64" + }, + "dashboard": { + "role": "panel", + "sizeHints": { + "preferred": "medium", + "minimum": "compact", + "aspectRatio": 1.5 + } } } }, { - "id": "shop-revenue-trend-by-country", - "sql": "SELECT day, country, sum(revenue) AS revenue\nFROM shop.daily_sales\nWHERE country IN ('US', 'GB', 'DE', 'JP')\nGROUP BY day, country\nORDER BY day", + "id": "shop-traffic-by-hour", + "sql": "SELECT toHour(event_time) AS hour, count() AS events FROM shop.events_raw WHERE event_time BETWEEN {from:DateTime} AND {to:DateTime}\n/*[ AND country IN {country:Array(String)} ]*/\n/*[ AND has({category:Array(String)}, dictGet('shop.products_dict', 'category', toUInt64(product_id))) ]*/\nGROUP BY hour ORDER BY hour", "specVersion": 1, "spec": { - "name": "Revenue trend by country", + "name": "Traffic by hour", + "description": "", "favorite": true, - "description": "Daily revenue for four countries at once — the country column drives the Series, producing one Line per country with a legend.", "view": "panel", "panel": { "cfg": { - "type": "line", + "type": "bar", "x": 0, "y": [ - 2 + 1 ], - "series": 1 + "series": null }, - "key": "day:Date|country:LowCardinality(String)|revenue:Decimal(38, 2)" + "key": "hour:UInt8|events:UInt64" }, "dashboard": { "role": "panel", @@ -187,24 +235,37 @@ } }, { - "id": "shop-orders-by-hour", - "sql": "SELECT toHour(event_time) AS hour, count() AS events\nFROM shop.events_raw\nGROUP BY hour\nORDER BY hour", + "id": "shop-country-revenue-trend", + "sql": "SELECT day, country, sum(revenue) AS revenue FROM shop.daily_sales WHERE day BETWEEN toDate({from:DateTime}) AND toDate({to:DateTime}) GROUP BY day, country ORDER BY day, country", "specVersion": 1, "spec": { - "name": "Traffic by hour of day", + "name": "Country revenue trends", + "description": "Additional Library analysis retained outside the flagship grid.", "favorite": false, - "description": "Event volume by hour of day (0–23) from events_raw. A numeric ordinal X axis → vertical Column chart.", "view": "panel", "panel": { "cfg": { - "type": "bar", + "type": "line", "x": 0, "y": [ - 1 + 2 ], - "series": null + "series": 1 }, - "key": "hour:UInt8|events:UInt64" + "key": "day:Date|country:LowCardinality(String)|revenue:Decimal(38, 2)" + } + } + }, + { + "id": "shop-filter-options", + "sql": "SELECT (SELECT arraySort(groupUniqArray(country)) FROM shop.events_raw) AS country, (SELECT arraySort(groupUniqArray(category)) FROM shop.products) AS category", + "specVersion": 1, + "spec": { + "name": "Shop dimensions", + "description": "Country and product-category choices for the Shop dashboard.", + "favorite": false, + "dashboard": { + "role": "filter" } } } @@ -212,31 +273,152 @@ "dashboards": [ { "documentVersion": 1, - "id": "shop-charts", + "id": "shop-analytics", "title": "Shop analytics", - "description": "Chart examples over the sample shop dataset.", + "description": "Revenue, buyers, products, geography, and traffic over the shop-demo.sql dataset.", "revision": 1, "layout": { - "type": "flow", + "type": "grafana-grid", "version": 1, - "preset": "columns-2", "items": { + "tile-shop-kpis": { + "span": 12, + "height": 2 + }, + "tile-shop-daily-revenue": { + "span": 8, + "height": 3 + }, "tile-shop-revenue-by-country": { - "span": 1, - "height": "large" + "span": 4, + "height": 3 }, "tile-shop-revenue-by-category": { - "span": 1, - "height": "large" + "span": 4, + "height": 3 }, - "tile-shop-revenue-trend-by-country": { - "span": 1, - "height": "large" + "tile-shop-top-products": { + "span": 8, + "height": 3 + }, + "tile-shop-daily-active-users": { + "span": 8, + "height": 3 + }, + "tile-shop-traffic-by-hour": { + "span": 4, + "height": 3 + } + }, + "fallback": { + "type": "flow", + "version": 1, + "preset": "columns-2", + "items": { + "tile-shop-kpis": { + "span": 2, + "height": "compact" + }, + "tile-shop-daily-revenue": { + "span": 1, + "height": "large" + }, + "tile-shop-revenue-by-country": { + "span": 1, + "height": "large" + }, + "tile-shop-revenue-by-category": { + "span": 1, + "height": "large" + }, + "tile-shop-top-products": { + "span": 1, + "height": "large" + }, + "tile-shop-daily-active-users": { + "span": 1, + "height": "large" + }, + "tile-shop-traffic-by-hour": { + "span": 1, + "height": "large" + } } } }, - "filters": [], + "filters": [ + { + "id": "shop-from", + "parameter": "from", + "label": "From", + "defaultValue": "-90d", + "defaultActive": true, + "targets": [ + "tile-shop-kpis", + "tile-shop-daily-revenue", + "tile-shop-revenue-by-country", + "tile-shop-revenue-by-category", + "tile-shop-top-products", + "tile-shop-daily-active-users", + "tile-shop-traffic-by-hour" + ] + }, + { + "id": "shop-to", + "parameter": "to", + "label": "To", + "defaultValue": "now", + "defaultActive": true, + "targets": [ + "tile-shop-kpis", + "tile-shop-daily-revenue", + "tile-shop-revenue-by-country", + "tile-shop-revenue-by-category", + "tile-shop-top-products", + "tile-shop-daily-active-users", + "tile-shop-traffic-by-hour" + ] + }, + { + "id": "shop-country", + "parameter": "country", + "label": "Country", + "sourceQueryId": "shop-filter-options", + "defaultValue": [], + "defaultActive": false, + "targets": [ + "tile-shop-kpis", + "tile-shop-daily-revenue", + "tile-shop-revenue-by-country", + "tile-shop-top-products", + "tile-shop-daily-active-users", + "tile-shop-traffic-by-hour" + ] + }, + { + "id": "shop-category", + "parameter": "category", + "label": "Category", + "sourceQueryId": "shop-filter-options", + "defaultValue": [], + "defaultActive": false, + "targets": [ + "tile-shop-kpis", + "tile-shop-revenue-by-category", + "tile-shop-top-products", + "tile-shop-traffic-by-hour" + ] + } + ], "tiles": [ + { + "id": "tile-shop-kpis", + "queryId": "shop-kpis" + }, + { + "id": "tile-shop-daily-revenue", + "queryId": "shop-daily-revenue" + }, { "id": "tile-shop-revenue-by-country", "queryId": "shop-revenue-by-country" @@ -246,8 +428,16 @@ "queryId": "shop-revenue-by-category" }, { - "id": "tile-shop-revenue-trend-by-country", - "queryId": "shop-revenue-trend-by-country" + "id": "tile-shop-top-products", + "queryId": "shop-top-products" + }, + { + "id": "tile-shop-daily-active-users", + "queryId": "shop-daily-active-users" + }, + { + "id": "tile-shop-traffic-by-hour", + "queryId": "shop-traffic-by-hour" } ] } diff --git a/examples/shop-demo.sql b/examples/shop-demo.sql index d9df9ce3..d158a0e5 100644 --- a/examples/shop-demo.sql +++ b/examples/shop-demo.sql @@ -1,7 +1,11 @@ +-- Required setup for examples/shop-charts.json. Load this file once with: +-- clickhouse-client --multiquery < examples/shop-demo.sql +-- The portable dashboard bundle intentionally does not duplicate schema/data. +-- -- shop — a tiny demo schema that EXISTS to show the data-flow graph: -- a raw events table feeding 3 materialized views into aggregate targets, -- a dictionary sourced from a dimension table, and a view on an aggregate. --- Generated data is intentionally small (50 products, 20k events). +-- Generated data is intentionally small (50 products, 300k events / ~90 days). -- -- On a replicated antalya cluster, wrap each statement in ON CLUSTER '{cluster}' -- and use Replicated* engines so every replica has it; shown single-node here. diff --git a/examples/system-explorer-charts.json b/examples/system-explorer-charts.json deleted file mode 100644 index b492bd2a..00000000 --- a/examples/system-explorer-charts.json +++ /dev/null @@ -1,404 +0,0 @@ -{ - "$schema": "https://altinity.com/schemas/altinity-sql-browser/portable-bundle-v1.schema.json", - "format": "altinity-sql-browser/portable-bundle", - "version": 1, - "exportedAt": "2026-07-04T19:47:55.982Z", - "metadata": { - "name": "ClickHouse system explorer", - "description": "Operational views over ClickHouse system tables." - }, - "queries": [ - { - "id": "sys-1", - "sql": "SELECT query_id, user, elapsed, read_rows, formatReadableSize(memory_usage) AS memory, left(query, 80) AS query\nFROM system.processes\nORDER BY elapsed DESC\nLIMIT 20", - "specVersion": 1, - "spec": { - "name": "Currently running queries", - "favorite": false, - "description": "Live snapshot of system.processes — every query executing right now, slowest first. Empty when the cluster is idle; that's a real \"nothing running\" result, not an error." - } - }, - { - "id": "sys-2", - "sql": "SELECT database, table, elapsed, round(progress * 100, 1) AS pct_done, num_parts, is_mutation, formatReadableSize(total_size_bytes_compressed) AS size\nFROM system.merges\nORDER BY elapsed DESC\nLIMIT 20", - "specVersion": 1, - "spec": { - "name": "Merges in progress", - "favorite": false, - "description": "Live snapshot of system.merges — background merges currently running, with progress and compressed size. Usually empty between merge cycles on a small cluster." - } - }, - { - "id": "sys-3", - "sql": "SELECT database, table, mutation_id, command, parts_to_do, latest_fail_reason\nFROM system.mutations\nWHERE NOT is_done\nORDER BY create_time\nLIMIT 20", - "specVersion": 1, - "spec": { - "name": "Mutations in progress", - "favorite": false, - "description": "Unfinished ALTER UPDATE/DELETE mutations from system.mutations, with the failure reason if one is stuck retrying." - } - }, - { - "id": "sys-4", - "sql": "SELECT database, table, is_leader, is_readonly, absolute_delay, queue_size, inserts_in_queue, merges_in_queue\nFROM system.replicas\nORDER BY absolute_delay DESC\nLIMIT 20", - "specVersion": 1, - "spec": { - "name": "Replication status", - "favorite": false, - "description": "system.replicas health per table — leadership, read-only state, replication delay, and queue depth. Sorted worst-lag-first." - } - }, - { - "id": "sys-5", - "sql": "SELECT database, table, type, create_time, num_tries, last_exception\nFROM system.replication_queue\nWHERE num_tries > 0\nORDER BY num_tries DESC\nLIMIT 20", - "specVersion": 1, - "spec": { - "name": "Stuck replication queue entries", - "favorite": false, - "description": "system.replication_queue entries that have already failed and retried at least once, with the last exception — the first place to look when a replica falls behind." - } - }, - { - "id": "sys-6", - "sql": "SELECT concat(database, '.', table) AS table, sum(bytes_on_disk) AS disk_bytes\nFROM system.parts\nWHERE active\nGROUP BY database, table\nORDER BY disk_bytes DESC\nLIMIT 15", - "specVersion": 1, - "spec": { - "name": "Largest tables by disk usage", - "favorite": true, - "description": "Every active part in system.parts, summed per table, largest first. Horizontal Bar — hover any bar for the exact byte count.", - "view": "panel", - "panel": { - "cfg": { - "type": "hbar", - "x": 0, - "y": [ - 1 - ], - "series": null - }, - "key": "table:String|disk_bytes:UInt64" - }, - "dashboard": { - "role": "panel", - "sizeHints": { - "preferred": "medium", - "minimum": "compact", - "aspectRatio": 1.5 - } - } - } - }, - { - "id": "sys-7", - "sql": "SELECT concat(database, '.', table) AS table, count() AS parts\nFROM system.parts\nWHERE active\nGROUP BY database, table\nORDER BY parts DESC\nLIMIT 15", - "specVersion": 1, - "spec": { - "name": "Active parts by table", - "favorite": true, - "description": "Active part *count* per table (not size) — a table climbing here between refreshes is trending toward \"too many parts\". Horizontal Bar.", - "view": "panel", - "panel": { - "cfg": { - "type": "hbar", - "x": 0, - "y": [ - 1 - ], - "series": null - }, - "key": "table:String|parts:UInt64" - }, - "dashboard": { - "role": "panel", - "sizeHints": { - "preferred": "medium", - "minimum": "compact", - "aspectRatio": 1.5 - } - } - } - }, - { - "id": "sys-8", - "sql": "SELECT name, value AS times\nFROM system.errors\nWHERE value > 0\nORDER BY value DESC\nLIMIT 15", - "specVersion": 1, - "spec": { - "name": "Cumulative error counters", - "favorite": true, - "description": "system.errors — every error code the server has hit since last restart, most frequent first. A quick \"what's actually going wrong here\" check. Horizontal Bar.", - "view": "panel", - "panel": { - "cfg": { - "type": "hbar", - "x": 0, - "y": [ - 1 - ], - "series": null - }, - "key": "name:String|times:UInt64" - }, - "dashboard": { - "role": "panel", - "sizeHints": { - "preferred": "medium", - "minimum": "compact", - "aspectRatio": 1.5 - } - } - } - }, - { - "id": "sys-9", - "sql": "SELECT toStartOfMinute(event_time) AS t, count() AS queries\nFROM system.query_log\nWHERE event_time BETWEEN parseDateTimeBestEffort({from:String}) AND parseDateTimeBestEffort({to:String}) AND type = 'QueryFinish'\nGROUP BY t\nORDER BY t", - "specVersion": 1, - "spec": { - "name": "Queries per minute", - "favorite": true, - "description": "Finished-query volume from system.query_log, bucketed per minute, over a {from:String}/{to:String} range (shared with every other time-ranged query below — the Dashboard filter bar renders one From/To pair that drives them all). A DateTime X axis is auto-detected as a time series → Line chart.", - "view": "panel", - "panel": { - "cfg": { - "type": "line", - "x": 0, - "y": [ - 1 - ], - "series": null - }, - "key": "t:DateTime|queries:UInt64" - }, - "dashboard": { - "role": "panel", - "sizeHints": { - "preferred": "medium", - "minimum": "compact", - "aspectRatio": 1.5 - } - } - } - }, - { - "id": "sys-10", - "sql": "SELECT left(any(query), 50) AS query, avg(query_duration_ms) AS avg_duration_ms\nFROM system.query_log\nWHERE event_time BETWEEN parseDateTimeBestEffort({from:String}) AND parseDateTimeBestEffort({to:String}) AND type = 'QueryFinish'\nGROUP BY normalized_query_hash\nORDER BY avg_duration_ms DESC\nLIMIT 15", - "specVersion": 1, - "spec": { - "name": "Slowest query patterns — avg duration", - "favorite": true, - "description": "Distinct query shapes (system.query_log grouped by normalized_query_hash) ranked by average duration over a {from:String}/{to:String} range. Horizontal Bar of a non-count measure.", - "view": "panel", - "panel": { - "cfg": { - "type": "hbar", - "x": 0, - "y": [ - 1 - ], - "series": null - }, - "key": "query:String|avg_duration_ms:Float64" - }, - "dashboard": { - "role": "panel", - "sizeHints": { - "preferred": "medium", - "minimum": "compact", - "aspectRatio": 1.5 - } - } - } - }, - { - "id": "sys-11", - "sql": "SELECT toStartOfHour(event_time) AS t, errorCodeToName(exception_code) AS error, count() AS n\nFROM system.query_log\nWHERE event_time BETWEEN parseDateTimeBestEffort({from:String}) AND parseDateTimeBestEffort({to:String}) AND exception_code != 0\nGROUP BY t, error\nORDER BY t", - "specVersion": 1, - "spec": { - "name": "Query errors over time", - "favorite": true, - "description": "Failed queries from system.query_log over a {from:String}/{to:String} range, broken down by ClickHouse error name. The \"error\" column is used as the Series, producing grouped/stacked bars per error code.", - "view": "panel", - "panel": { - "cfg": { - "type": "bar", - "x": 0, - "y": [ - 2 - ], - "series": 1 - }, - "key": "t:DateTime|error:LowCardinality(String)|n:UInt64" - }, - "dashboard": { - "role": "panel", - "sizeHints": { - "preferred": "medium", - "minimum": "compact", - "aspectRatio": 1.5 - } - } - } - }, - { - "id": "sys-12", - "sql": "SELECT toStartOfHour(event_time) AS t, event_type, count() AS n\nFROM system.part_log\nWHERE event_time BETWEEN parseDateTimeBestEffort({from:String}) AND parseDateTimeBestEffort({to:String})\nGROUP BY t, event_type\nORDER BY t", - "specVersion": 1, - "spec": { - "name": "Part lifecycle events over time", - "favorite": true, - "description": "system.part_log over a {from:String}/{to:String} range — new/merged/mutated/downloaded/removed parts per hour, one query instead of five separate panels. \"event_type\" is the Series.", - "view": "panel", - "panel": { - "cfg": { - "type": "bar", - "x": 0, - "y": [ - 2 - ], - "series": 1 - }, - "key": "t:DateTime|event_type:Enum8('NewPart' = 1, 'MergeParts' = 2, 'DownloadPart' = 3, 'RemovePart' = 4, 'MutatePart' = 5, 'MovePart' = 6, 'MergePartsStart' = 7, 'MutatePartStart' = 8)|n:UInt64" - }, - "dashboard": { - "role": "panel", - "sizeHints": { - "preferred": "medium", - "minimum": "compact", - "aspectRatio": 1.5 - } - } - } - }, - { - "id": "sys-13", - "sql": "SELECT toStartOfMinute(event_time) AS t, avg(CurrentMetric_MemoryTracking) AS memory_bytes\nFROM system.metric_log\nWHERE event_time BETWEEN parseDateTimeBestEffort({from:String}) AND parseDateTimeBestEffort({to:String})\nGROUP BY t\nORDER BY t", - "specVersion": 1, - "spec": { - "name": "Memory usage over time", - "favorite": true, - "description": "Average tracked memory (system.metric_log's CurrentMetric_MemoryTracking) per minute over a {from:String}/{to:String} range. Line chart.", - "view": "panel", - "panel": { - "cfg": { - "type": "line", - "x": 0, - "y": [ - 1 - ], - "series": null - }, - "key": "t:DateTime|memory_bytes:Float64" - }, - "dashboard": { - "role": "panel", - "sizeHints": { - "preferred": "medium", - "minimum": "compact", - "aspectRatio": 1.5 - } - } - } - }, - { - "id": "sys-14", - "sql": "SELECT\n normalized_query_hash,\n left(argMax(query, query_duration_ms), 60) AS sample_query,\n count() AS executions,\n max(query_duration_ms) AS max_ms,\n avg(query_duration_ms) AS avg_ms,\n sum(read_rows) AS read_rows,\n formatReadableSize(sum(read_bytes)) AS read_bytes,\n quantile(0.99)(memory_usage) AS p99_memory\nFROM system.query_log\nWHERE event_time BETWEEN parseDateTimeBestEffort({from:String}) AND parseDateTimeBestEffort({to:String}) AND type = 'QueryFinish'\nGROUP BY normalized_query_hash\nORDER BY avg_ms DESC\nLIMIT 15", - "specVersion": 1, - "spec": { - "name": "Query cost breakdown — slowest patterns (detail)", - "favorite": false, - "description": "The deep-dive version of \"Slowest query patterns\": executions, max/avg duration, rows and bytes read, and p99 memory per query shape over a {from:String}/{to:String} range. Table view — too many columns for one chart, but the full picture behind the bar chart above." - } - } - ], - "dashboards": [ - { - "documentVersion": 1, - "id": "system-explorer", - "title": "ClickHouse system explorer", - "description": "Operational views over ClickHouse system tables.", - "revision": 1, - "layout": { - "type": "flow", - "version": 1, - "preset": "columns-2", - "items": { - "tile-sys-6": { - "span": 1, - "height": "large" - }, - "tile-sys-7": { - "span": 1, - "height": "large" - }, - "tile-sys-8": { - "span": 1, - "height": "large" - }, - "tile-sys-9": { - "span": 1, - "height": "large" - }, - "tile-sys-10": { - "span": 1, - "height": "large" - }, - "tile-sys-11": { - "span": 1, - "height": "large" - }, - "tile-sys-12": { - "span": 1, - "height": "large" - }, - "tile-sys-13": { - "span": 1, - "height": "large" - } - } - }, - "filters": [ - { - "id": "filter-from", - "parameter": "from" - }, - { - "id": "filter-to", - "parameter": "to" - } - ], - "tiles": [ - { - "id": "tile-sys-6", - "queryId": "sys-6" - }, - { - "id": "tile-sys-7", - "queryId": "sys-7" - }, - { - "id": "tile-sys-8", - "queryId": "sys-8" - }, - { - "id": "tile-sys-9", - "queryId": "sys-9" - }, - { - "id": "tile-sys-10", - "queryId": "sys-10" - }, - { - "id": "tile-sys-11", - "queryId": "sys-11" - }, - { - "id": "tile-sys-12", - "queryId": "sys-12" - }, - { - "id": "tile-sys-13", - "queryId": "sys-13" - } - ] - } - ] -} diff --git a/examples/text-log-panel.json b/examples/text-log-panel.json deleted file mode 100644 index 8ca6a37b..00000000 --- a/examples/text-log-panel.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "$schema": "https://altinity.com/schemas/altinity-sql-browser/portable-bundle-v1.schema.json", - "format": "altinity-sql-browser/portable-bundle", - "version": 1, - "exportedAt": "2026-07-13T05:01:01.000Z", - "metadata": { - "name": "Server log panel example", - "description": "Parameterized system.text_log dashboard example." - }, - "queries": [ - { - "id": "log-1", - "sql": "SELECT event_time, level, logger_name, message\nFROM system.text_log\nWHERE event_time > {from:DateTime}\n/*[ AND event_time <= {to:DateTime} ]*/\n/*[ AND level = {level:Enum8('Fatal' = 1, 'Critical' = 2, 'Error' = 3, 'Warning' = 4, 'Notice' = 5, 'Information' = 6, 'Debug' = 7, 'Trace' = 8, 'Test' = 9)} ]*/\n/*[ AND positionCaseInsensitive(logger_name, {logger:String}) > 0 ]*/\n/*[ AND positionCaseInsensitive(message, {message:String}) > 0 ]*/\nORDER BY event_time DESC\nLIMIT 500", - "specVersion": 1, - "spec": { - "name": "Server logs in a time range", - "favorite": true, - "description": "{from:DateTime} is required (drives the Dashboard's global From filter, and accepts a relative expression like -1h/now-7d). to/level/logger/message are wrapped in /*[ ... ]*/ optional blocks, so they're non-required: blank ones drop out of the query entirely, filling one in adds its predicate. level renders as a dropdown (declared Enum8 member names); logger/message use positionCaseInsensitive(...) > 0 for a plain case-insensitive substring search — no ILIKE wildcard footgun if the search term itself contains a literal % or _.", - "view": "panel", - "panel": { - "cfg": { - "type": "logs", - "time": "event_time", - "msg": "message", - "level": "level" - } - }, - "dashboard": { - "role": "panel", - "sizeHints": { - "preferred": "wide", - "minimum": "medium", - "aspectRatio": 1.6 - } - } - } - } - ], - "dashboards": [ - { - "documentVersion": 1, - "id": "text-log-panel-example", - "title": "Server log panel example", - "description": "Parameterized system.text_log dashboard example.", - "revision": 1, - "layout": { - "type": "flow", - "version": 1, - "preset": "report", - "items": { - "tile-log-1": { - "span": 1, - "height": "large" - } - } - }, - "filters": [ - { - "id": "filter-from", - "parameter": "from" - }, - { - "id": "filter-to", - "parameter": "to" - }, - { - "id": "filter-level", - "parameter": "level" - }, - { - "id": "filter-logger", - "parameter": "logger" - }, - { - "id": "filter-message", - "parameter": "message" - } - ], - "tiles": [ - { - "id": "tile-log-1", - "queryId": "log-1" - } - ] - } - ] -} diff --git a/tests/unit/spec-examples.test.js b/tests/unit/spec-examples.test.js index ee8279cb..46497422 100644 --- a/tests/unit/spec-examples.test.js +++ b/tests/unit/spec-examples.test.js @@ -8,6 +8,7 @@ import { decodePortableBundleJson } from '../../src/dashboard/model/portable-bun import { querySpecSchemaService } from '../../src/core/spec-schema.js'; import { filterExecution } from '../../src/core/filter-execution.js'; import { effectiveDashboardRole } from '../../src/core/result-choice.js'; +import { analyzeParameterizedSources } from '../../src/core/param-pipeline.js'; const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..'); @@ -27,7 +28,11 @@ describe('schema artifacts and examples', () => { it('keeps every checked-in JSON example on portable bundle v1 with explicit Dashboard v1 documents', () => { const examples = resolve(root, 'examples'); - for (const name of readdirSync(examples).filter((item) => item.endsWith('.json'))) { + const names = readdirSync(examples).filter((item) => item.endsWith('.json')).sort(); + expect(names.filter((name) => !name.startsWith('iceberg'))).toEqual([ + 'clickhouse-operations.json', 'ontime-charts.json', 'shop-charts.json', + ]); + for (const name of names) { const text = readFileSync(resolve(examples, name), 'utf8'); const bundle = decodeExample(text, name); expect(bundle.format, name).toBe('altinity-sql-browser/portable-bundle'); @@ -36,8 +41,21 @@ describe('schema artifacts and examples', () => { expect(() => assertValidExampleBundle(bundle), name).not.toThrow(); for (const dashboard of bundle.dashboards) { expect(dashboard.documentVersion, name).toBe(1); - expect(dashboard.layout.type, name).toBe('flow'); + expect(['flow', 'grafana-grid'], name).toContain(dashboard.layout.type); expect(dashboard.tiles.length, name).toBeGreaterThan(0); + const tileIds = new Set(dashboard.tiles.map((tile) => tile.id)); + const queryIds = new Set(bundle.queries.map((query) => query.id)); + for (const tile of dashboard.tiles) expect(queryIds.has(tile.queryId), `${name}:${tile.id}`).toBe(true); + for (const filter of dashboard.filters) { + if (filter.sourceQueryId) expect(queryIds.has(filter.sourceQueryId), `${name}:${filter.id}`).toBe(true); + for (const target of filter.targets || []) expect(tileIds.has(target), `${name}:${filter.id}`).toBe(true); + } + if (dashboard.layout.type === 'grafana-grid') { + expect(dashboard.layout.fallback?.type, name).toBe('flow'); + expect(dashboard.layout.fallback?.version, name).toBe(1); + expect(Object.keys(dashboard.layout.items).sort(), name).toEqual([...tileIds].sort()); + expect(Object.keys(dashboard.layout.fallback.items).sort(), name).toEqual([...tileIds].sort()); + } } } expect(() => execFileSync(process.execPath, ['examples/mjs/normalize-examples.mjs', '--check'], { @@ -45,6 +63,48 @@ describe('schema artifacts and examples', () => { })).not.toThrow(); }); + it('keeps every flagship dimensional filter on one inferred multiselect Array(T) contract', () => { + const expected = { + 'ontime-charts.json': { carrier: 'Array(String)', origin: 'Array(String)' }, + 'shop-charts.json': { country: 'Array(String)', category: 'Array(String)' }, + 'clickhouse-operations.json': { + user: 'Array(String)', query_kind: 'Array(String)', + exception_code: 'Array(Int32)', query_hash: 'Array(UInt64)', + }, + }; + for (const [name, contracts] of Object.entries(expected)) { + const bundle = decodeExample(readFileSync(resolve(root, 'examples', name), 'utf8'), name); + const dashboard = bundle.dashboards[0]; + const queryById = new Map(bundle.queries.map((query) => [query.id, query])); + for (const [parameter, type] of Object.entries(contracts)) { + const filter = dashboard.filters.find((item) => item.parameter === parameter); + expect(filter?.sourceQueryId, `${name}:${parameter}`).toBeTruthy(); + expect(filter?.selection, `${name}:${parameter}`).toBeUndefined(); + const targetIds = filter.targets || dashboard.tiles.map((tile) => tile.id); + const sources = targetIds.map((tileId) => { + const tile = dashboard.tiles.find((item) => item.id === tileId); + const query = tile && queryById.get(tile.queryId); + return query ? { id: tileId, sql: query.sql, bindPolicy: 'row-returning' } : null; + }).filter(Boolean); + const analysis = analyzeParameterizedSources(sources); + const declarations = analysis.fields[parameter]?.declarations.filter((item) => item.bound) || []; + expect(declarations.length, `${name}:${parameter}`).toBeGreaterThan(0); + expect([...new Set(declarations.map((item) => item.type))], `${name}:${parameter}`).toEqual([type]); + } + } + }); + + it('keeps authored analytical chart encodings pinned to result schema keys', () => { + for (const name of ['ontime-charts.json', 'shop-charts.json']) { + const bundle = decodeExample(readFileSync(resolve(root, 'examples', name), 'utf8'), name); + const visible = new Set(bundle.dashboards[0].tiles.map((tile) => tile.queryId)); + for (const query of bundle.queries) { + if (!visible.has(query.id) || query.spec.panel?.cfg?.type === 'kpi') continue; + expect(query.spec.panel?.key, `${name}:${query.id}`).toMatch(/^[^:]+:.+/); + } + } + }); + it('validates the generated Iceberg drilldown portable-bundle template', () => { const template = readFileSync(resolve(root, 'examples/iceberg-templates/ice_meta_drilldown.json.tmpl'), 'utf8') .replaceAll('__CATALOG__', 'demo');