Dashboard

What goes in a Presto or Trino monitoring dashboard in Metabase?

A Presto or Trino monitoring dashboard tracks query volume and state, wall-time percentiles, how long work sits queued in each resource group, splits in flight across the workers, memory pool pressure, and the error codes queries fail with. Metabase connects to Presto and Trino natively, so every card is plain SQL over the engine's own system.runtime tables and the event listener's history table.

For: platform and data engineering teams who own the query engine. Grain: one row per query from the event listener, plus live snapshots of tasks and nodes. Source: system.runtime.queries, system.runtime.tasks, system.runtime.nodes, the JMX connector, and a QueryCompletedEvent table.

What does a Presto or Trino monitoring dashboard look like?

Here’s the layout this guide builds. Cluster-level counters sit at the top; the middle section is throughput and latency — how many queries, how long they take, how long they wait, and how much split work is in flight; the bottom section is the pressure half, where the memory pool, worker health, and error codes name the thing that is actually breaking.

Presto and Trino monitoring dashboard in Metabase showing query state, wall-time percentiles, queue time, splits in flight, memory pool, and failures.

An example Presto and Trino monitoring dashboard in Metabase, built from system.runtime and the event listener. Figures are illustrative.

Which cards belong on a Presto or Trino monitoring dashboard?

Eight cards. Four describe the workload, four describe the pressure it puts on the cluster — and it is the pairing that makes the dashboard diagnostic rather than decorative.

  • Queries by hour and final state — finished, failed, cancelled (stacked bar)
  • Query wall time, p50 and p95 by hour (line)
  • Queue time vs. execution time by resource group (line)
  • Splits in flight — queued and running across the workers (area)
  • Cluster memory pool reserved against total distributed bytes (line)
  • Failures by error code — memory limit, no nodes available, syntax (row)
  • Worker nodes and CPU time per hour (combo)
  • Top queries by CPU time, with splits and bytes scanned (table)

What data does the dashboard need?

  • system.runtime.queries — the coordinator’s live view: state, user, source, resource_group_id, queued_time_ms, analysis_time_ms, planning_time_ms, and the query text. Recent queries only.
  • system.runtime.tasks — per node and stage: queued_splits, running_splits, completed_splits, split_cpu_time_ms, raw_input_bytes, and raw_input_rows.
  • system.runtime.nodes for the worker roster — node_id, coordinator, node_version, and state (active, inactive, shutting down).
  • The JMX connector for memory: jmx.current."trino.memory:name=general,type=clustermemorypool" gives free and total distributed bytes, blocked nodes, and assigned queries (com.facebook.presto.memory on Presto).
  • An event-listener table of QueryCompletedEvent rows — wall, queued, and CPU time, peak user memory, completed splits, total bytes, and failureInfo.errorCode. This is what every trend card reads.

How do you build it?

  1. Connect Presto or Trino to Metabase with a read-only user, and make sure the catalog list includes system and jmx — they are ordinary catalogs, and without them half the cards have no source.
  2. Turn on an event listener that persists QueryCompletedEvent to a table the cluster can query, then expose that table through the same connection. Without it you have a status page, not a dashboard.
  3. Model the event table once — milliseconds to seconds, a duration bucket, a normalized query fingerprint, and the resource group parsed out of resource_group_id — so no two cards disagree about what “slow” means.
  4. Build the queue-time and memory-pool cards first. Together they tell you whether the cluster is admission-bound or execution-bound, which is the only question worth answering before you tune anything.
  5. Add filters for cluster, resource group, and date range, and alert the platform channel when reserved memory crosses its threshold or the failure rate doubles.

Example card SQL

Queries, failures, queue and wall-time percentiles by hour and resource group PostgreSQL
SELECT
date_trunc('hour', end_time)                             AS hour,
resource_group,
count(*)                                                 AS queries,
count_if(state = 'FAILED')                               AS failed,
round(approx_percentile(queued_time_ms, 0.95) / 1000.0, 2)
                                                         AS queue_p95_s,
round(approx_percentile(wall_time_ms, 0.50) / 1000.0, 2) AS wall_p50_s,
round(approx_percentile(wall_time_ms, 0.95) / 1000.0, 2) AS wall_p95_s,
round(sum(cpu_time_ms) / 3600000.0, 1)                   AS cpu_hours,
sum(completed_splits)                                    AS splits,
round(sum(total_bytes) / power(1024, 4), 2)              AS tb_scanned
FROM metrics.query_events
WHERE end_time >= current_timestamp - interval '14' day
GROUP BY 1, 2
ORDER BY 1, 2;

Metrics

Integrations

Dashboards

FAQ

What is a Presto or Trino monitoring dashboard?
A Presto or Trino monitoring dashboard tracks what the cluster is doing and where it is running out of room — queries by state, wall-time percentiles, time spent queued in each resource group, splits in flight across the workers, memory pool pressure, and the error codes queries die with. Every number is already inside the engine: the system.runtime tables expose the coordinator's live view, and the event listener writes one durable row per finished query. Metabase connects to Presto and Trino natively, so the dashboard is ordinary SQL against the same cluster you are watching.
Does the dashboard change between Presto and Trino?
Barely. Trino forked from PrestoSQL in 2020, and the observability surface is nearly identical: system.runtime.queries, system.runtime.tasks, and system.runtime.nodes exist in both, with the same columns for state, queued time, splits, and input bytes. The differences are naming — the JMX beans live under trino.memory on one and com.facebook.presto.memory on the other, the session and config properties drifted apart, and each project has added error codes the other does not have. Point the cards at whichever bean and error-code list your cluster reports and the layout is unchanged. Managed distributions are the real exception: on Starburst you get the same tables plus an insights schema, while Athena is serverless and gives you CloudWatch and the query-execution API instead of system.runtime.
Why is system.runtime not enough on its own?
Because it is a live view, not a history. system.runtime.queries is materialized from the coordinator's in-memory state, which keeps only the most recent queries — governed by query.max-history (100 by default) and query.min-expire-age (15 minutes) — and it empties entirely when the coordinator restarts. That is perfect for the "what is happening right now" cards and useless for trends. The fix is the event listener: implement or install one, have it write a QueryCompletedEvent row per query into a table the cluster can also read, and point every trend card at that table. The live tables then only power the running/queued counters.
What does queue time tell me that wall time doesn't?
Which lever to pull. Wall time is queue time plus planning plus execution, so a rising p95 says nothing about the cause. Split them per resource group and the answer is immediate: long queue time with steady execution means the group's hardConcurrencyLimit or softMemoryLimit is admitting too few queries at once, and the fix is resource-group configuration or more workers. Steady queue time with rising execution means the queries themselves got heavier — more data, worse join order, a missing partition filter — and no amount of scheduling helps. Resource groups are also the only place a dashboard can see who is starving whom, since a single greedy group can hold the whole cluster's memory.
What do splits tell me that query count doesn't?
How much work each query actually is. Presto and Trino break a query into stages, stages into tasks, and tasks into splits — a split is roughly one chunk of one file or one range of one table, and the workers execute them from a queue. Ten queries scanning a well-partitioned table might be a few hundred splits; one unpartitioned scan of the same table can be a hundred thousand. system.runtime.tasks gives you queued_splits, running_splits, and completed_splits per node, so a chart of splits in flight is the cluster's true load curve, and a persistently deep split queue with idle CPU usually means small files, not too few workers.
How do I read the memory pool cards?
The cluster memory pool is a shared pot: each worker contributes its query.max-memory-per-node, and the coordinator refuses to admit a query when the general pool cannot cover it. Chart reserved bytes against total distributed bytes, and watch two things — the percentage reserved, and the count of blocked nodes. Sustained above roughly 85% and you will see EXCEEDED_GLOBAL_MEMORY_LIMIT failures and long queue times together, which is the signature of a memory-bound cluster rather than a CPU-bound one. The three real fixes are lowering per-query limits so one query cannot eat the pool, enabling spill-to-disk for the heavy joins and aggregations, or adding workers.
How is this different from a data lake dashboard?
Layer. A data lake dashboard looks at the storage and the tables — file counts, partition health, freshness, and what the lake costs. This one looks at the engine that reads them, and answers "why did that query queue for two minutes and then fail". They are complementary, and the join between them is usually small files: the lake dashboard shows the partition with 40,000 objects, this one shows the split explosion it causes. Teams running more than one engine often keep this beside the BigQuery or Redshift version.