From e5ceba990ae132f888b456ee5570c1963eefcb85 Mon Sep 17 00:00:00 2001 From: 0xgouda Date: Thu, 20 Nov 2025 20:00:26 +0200 Subject: [PATCH 1/6] First working version of the `stats_plans` metric. This metric uses the new `pg_stat_plans` extension to provide insights about query planning and performance of different plans for a specific query. --- metric/metrics.yaml | 50 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 metric/metrics.yaml diff --git a/metric/metrics.yaml b/metric/metrics.yaml new file mode 100644 index 0000000..5ef0f22 --- /dev/null +++ b/metric/metrics.yaml @@ -0,0 +1,50 @@ +metrics: + stat_plans: + description: > + This metric collects statistics from the `pg_stat_plans` extension. + It provides insights about query planning, including number of different plans executed for a query, most used plan, + execution times, and best plan suggestion. + This metric is useful for monitoring query performance and identifying queries that needs to be rewritten. + init_sql: CREATE EXTENSION IF NOT EXISTS pg_stat_statements; CREATE EXTENSION IF NOT EXISTS pg_stat_plans; + sqls: + 16: |- + WITH s AS ( + SELECT + queryid, + max(query::varchar(8000)) as query, + sum(calls) as query_calls + FROM pg_stat_statements + WHERE dbid = (SELECT oid FROM pg_database WHERE datname = current_database()) + GROUP BY queryid + HAVING sum(calls) > 5 AND sum(total_exec_time) > 5 + ), p AS ( + SELECT + queryid, + planid, + plan, + calls, + total_exec_time, + COUNT(planid) OVER(PARTITION BY queryid) as cnt, + ROW_NUMBER() OVER(PARTITION BY queryid ORDER BY calls DESC) as crn, + ROW_NUMBER() OVER(PARTITION BY queryid ORDER BY (total_exec_time / calls) DESC) as arn + FROM pg_stat_plans + ) + + SELECT + s.queryid, + s.query, + s.query_calls, + p1.cnt as num_of_plans, + p1.planid as most_used_plan_id, + p1.plan as most_used_plan, + p1.calls as most_used_plan_calls, + (p1.total_exec_time / p1.calls) as most_used_plan_avg_exec_time, + p1.total_exec_time as most_used_plan_total_exec_time, + (p2.total_exec_time / p2.calls) as suggested_plan_avg_time, + p2.total_exec_time as suggested_plan_total_time, + p2.planid as suggested_plan_id, + p2.plan as suggested_plan, + p2.calls as suggested_plan_calls + FROM s + JOIN p p1 ON p1.crn = 1 AND s.queryid = p1.queryid + JOIN p p2 ON p2.arn = 1 AND s.queryid = p2.queryid; \ No newline at end of file From 43f6059d72bac9df0ac5cade6c7afa7918ca47cb Mon Sep 17 00:00:00 2001 From: 0xgouda Date: Fri, 21 Nov 2025 16:56:25 +0200 Subject: [PATCH 2/6] Refactor the `stat_plans` metric query. Refactor the metric query to return all plans for top resource intensive queries which is more useful in deubgging. The query retrieves the 'top resource intensive' queries via copying parts of the `stat_statements` metric query. --- metric/metrics.yaml | 134 +++++++++++++++++++++++++++++++------------- 1 file changed, 94 insertions(+), 40 deletions(-) diff --git a/metric/metrics.yaml b/metric/metrics.yaml index 5ef0f22..279a688 100644 --- a/metric/metrics.yaml +++ b/metric/metrics.yaml @@ -2,49 +2,103 @@ metrics: stat_plans: description: > This metric collects statistics from the `pg_stat_plans` extension. - It provides insights about query planning, including number of different plans executed for a query, most used plan, - execution times, and best plan suggestion. - This metric is useful for monitoring query performance and identifying queries that needs to be rewritten. + It provides insights about different plans for the most resource intensive queries, + including plan, number of calls, and execution times. + This metric is useful for monitoring debugging query plans. init_sql: CREATE EXTENSION IF NOT EXISTS pg_stat_statements; CREATE EXTENSION IF NOT EXISTS pg_stat_plans; sqls: 16: |- - WITH s AS ( - SELECT - queryid, - max(query::varchar(8000)) as query, - sum(calls) as query_calls - FROM pg_stat_statements - WHERE dbid = (SELECT oid FROM pg_database WHERE datname = current_database()) - GROUP BY queryid - HAVING sum(calls) > 5 AND sum(total_exec_time) > 5 - ), p AS ( - SELECT + WITH p_data AS ( + SELECT + max(p.dbid)::int8 as dbid, + max(p.queryid)::int8 as queryid, + p.planid, + sum(p.calls)::int8 as calls, + round(sum(p.total_exec_time)::numeric, 3)::double precision AS total_exec_time, + max(p.plan::varchar(8000)) as plan + FROM pg_stat_plans p + WHERE + dbid = (SELECT oid FROM pg_database WHERE datname = current_database()) + GROUP BY p.planid + ), q_data AS ( + SELECT + max(s.dbid) as dbid, queryid, - planid, - plan, - calls, - total_exec_time, - COUNT(planid) OVER(PARTITION BY queryid) as cnt, - ROW_NUMBER() OVER(PARTITION BY queryid ORDER BY calls DESC) as crn, - ROW_NUMBER() OVER(PARTITION BY queryid ORDER BY (total_exec_time / calls) DESC) as arn - FROM pg_stat_plans + sum(s.calls)::int8 AS calls, + round(sum(s.total_exec_time)::numeric, 3)::double precision AS total_exec_time, + sum(shared_blks_read)::int8 AS shared_blks_read, + sum(shared_blks_written)::int8 AS shared_blks_written + FROM + pg_stat_statements s + WHERE + calls > 5 + AND total_exec_time > 5 + AND dbid = (SELECT oid FROM pg_database WHERE datname = current_database()) + AND NOT upper(s.query::varchar(50)) + LIKE ANY (ARRAY[ + 'DEALLOCATE%', + 'SET %', + 'RESET %', + 'BEGIN%', + 'BEGIN;', + 'COMMIT%', + 'END%', + 'ROLLBACK%', + 'SHOW%' + ]) + GROUP BY + queryid ) - SELECT - s.queryid, - s.query, - s.query_calls, - p1.cnt as num_of_plans, - p1.planid as most_used_plan_id, - p1.plan as most_used_plan, - p1.calls as most_used_plan_calls, - (p1.total_exec_time / p1.calls) as most_used_plan_avg_exec_time, - p1.total_exec_time as most_used_plan_total_exec_time, - (p2.total_exec_time / p2.calls) as suggested_plan_avg_time, - p2.total_exec_time as suggested_plan_total_time, - p2.planid as suggested_plan_id, - p2.plan as suggested_plan, - p2.calls as suggested_plan_calls - FROM s - JOIN p p1 ON p1.crn = 1 AND s.queryid = p1.queryid - JOIN p p2 ON p2.arn = 1 AND s.queryid = p2.queryid; \ No newline at end of file + SELECT + (EXTRACT(epoch FROM now()) * 1e9)::int8 AS epoch_ns, + queryid, + planid, + plan, + calls, + total_exec_time, + (total_exec_time / calls) as avg_exec_time + FROM + ( + ( + SELECT + p.* + FROM p_data p + JOIN q_data q ON q.queryid = p.queryid + ORDER BY q.total_exec_time DESC + LIMIT 100 + ) + + UNION + + ( + SELECT + p.* + FROM p_data p + JOIN q_data q ON q.queryid = p.queryid + ORDER BY q.calls DESC + LIMIT 100 + ) + + UNION + + ( + SELECT + p.* + FROM p_data p + JOIN q_data q ON q.queryid = p.queryid + ORDER BY q.shared_blks_read DESC + LIMIT 100 + ) + + UNION + + ( + SELECT + p.* + FROM p_data p + JOIN q_data q ON q.queryid = p.queryid + ORDER BY q.shared_blks_written DESC + LIMIT 100 + ) + ) \ No newline at end of file From 46866d39f127fb711a57a6a7a9bc64488e80693f Mon Sep 17 00:00:00 2001 From: 0xgouda Date: Mon, 24 Nov 2025 14:04:41 +0200 Subject: [PATCH 3/6] Remove returning `avg_exec_time` from query. --- metric/metrics.yaml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/metric/metrics.yaml b/metric/metrics.yaml index 279a688..ff19ed3 100644 --- a/metric/metrics.yaml +++ b/metric/metrics.yaml @@ -14,7 +14,7 @@ metrics: max(p.queryid)::int8 as queryid, p.planid, sum(p.calls)::int8 as calls, - round(sum(p.total_exec_time)::numeric, 3)::double precision AS total_exec_time, + round(sum(p.total_exec_time)::numeric, 3)::double precision AS total_plan_exec_time, max(p.plan::varchar(8000)) as plan FROM pg_stat_plans p WHERE @@ -52,12 +52,11 @@ metrics: SELECT (EXTRACT(epoch FROM now()) * 1e9)::int8 AS epoch_ns, - queryid, - planid, + queryid::int8, + planid::int8, plan, - calls, - total_exec_time, - (total_exec_time / calls) as avg_exec_time + calls::int8, + total_plan_exec_time::int8, FROM ( ( From 35555447973b09f45dad0f5d152ea2e9c6245187 Mon Sep 17 00:00:00 2001 From: 0xgouda Date: Mon, 24 Nov 2025 14:21:42 +0200 Subject: [PATCH 4/6] Add `Stat Plans Top` dashboard. The plan mimics `Stat Statements Top` but provides insights for top plans for a given query id. --- grafana/postgres/v12/stat-plans-top.json | 642 +++++++++++++++++++++++ 1 file changed, 642 insertions(+) create mode 100644 grafana/postgres/v12/stat-plans-top.json diff --git a/grafana/postgres/v12/stat-plans-top.json b/grafana/postgres/v12/stat-plans-top.json new file mode 100644 index 0000000..52827cb --- /dev/null +++ b/grafana/postgres/v12/stat-plans-top.json @@ -0,0 +1,642 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 46, + "links": [], + "panels": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "pgwatch-metrics" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "left", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "total_plan_run_time" + }, + "properties": [ + { + "id": "displayName", + "value": "Total runtime" + }, + { + "id": "custom.width", + "value": 120 + }, + { + "id": "unit", + "value": "ms" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "queryid" + }, + "properties": [ + { + "id": "displayName", + "value": "Query ID" + }, + { + "id": "links", + "value": [ + { + "title": "Opens 'Single query details' dashboard for that queryid", + "url": "/d/single-query-details?orgId=1&var-dbname=$dbname&var-queryid=${__value.raw}&from=${__from}&to=${__to}" + } + ] + }, + { + "id": "custom.width", + "value": 225 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "planid" + }, + "properties": [ + { + "id": "displayName", + "value": "Plan ID" + }, + { + "id": "custom.width", + "value": 225 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "plan" + }, + "properties": [ + { + "id": "displayName", + "value": "Plan" + } + ] + } + ] + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "pgwatch-metrics" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "select\n total_plan_run_time,\n queryid,\n planid,\n plan\nfrom (\nselect\n sum(tt - tt_lag) as total_plan_run_time,\n max(queryid) as queryid,\n planid,\n case when length(plan) > 250 then plan::varchar(250) || '...' else plan end as plan\nfrom (\n select\n data->>'queryid' as queryid,\n data->>'planid' as planid, \n data->>'plan' as plan,\n (data->>'total_plan_exec_time')::float8 as tt, \n lag((data->>'total_plan_exec_time')::float8) over w as tt_lag,\n time\n from stat_plans\n where dbname = '$dbname' and $__timeFilter(time) and (data->>'queryid')::int8 = $queryid\n window w as (partition by data->>'planid' order by time)\n) y\ngroup by planid, plan\nhaving sum(tt - tt_lag) > 0\norder by 1 desc nulls last\nlimit $top\n) z\norder by 1 desc nulls last", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Top $top plans for query by total run time", + "type": "table" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "pgwatch-metrics" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "left", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "calls" + }, + "properties": [ + { + "id": "displayName", + "value": "Calls" + }, + { + "id": "custom.width", + "value": 120 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "queryid" + }, + "properties": [ + { + "id": "displayName", + "value": "Query ID" + }, + { + "id": "links", + "value": [ + { + "title": "Opens 'Single query details' dashboard for that queryid", + "url": "/d/single-query-details?orgId=1&var-dbname=$dbname&var-queryid=${__value.raw}&from=${__from}&to=${__to}" + } + ] + }, + { + "id": "custom.width", + "value": 225 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "planid" + }, + "properties": [ + { + "id": "displayName", + "value": "Plan ID" + }, + { + "id": "custom.width", + "value": 225 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "plan" + }, + "properties": [ + { + "id": "displayName", + "value": "Plan" + } + ] + } + ] + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 4 + }, + "id": 3, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "pgwatch-metrics" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "select\n calls,\n queryid,\n planid,\n plan\nfrom (\nselect\n sum(calls - calls_lag)::int8 as calls,\n max(queryid) as queryid,\n planid,\n case when length(plan) > 250 then plan::varchar(250) || '...' else plan end as plan\nfrom (\n select\n data->>'queryid' as queryid,\n data->>'planid' as planid, \n data->>'plan' as plan,\n (data->>'calls')::float8 as calls, \n lag((data->>'calls')::float8) over w as calls_lag,\n time\n from stat_plans\n where dbname = '$dbname' and $__timeFilter(time) and (data->>'queryid')::int8 = $queryid\n window w as (partition by data->>'planid' order by time)\n) y\nwhere calls > calls_lag\ngroup by planid, plan\norder by 1 desc nulls last\nlimit $top\n) z\norder by 1 desc nulls last;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Top $top plans for query by calls", + "type": "table" + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "pgwatch-metrics" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "left", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "avg_plan_run_time" + }, + "properties": [ + { + "id": "displayName", + "value": "Avg. runtime" + }, + { + "id": "custom.width", + "value": 120 + }, + { + "id": "unit", + "value": "ms" + }, + { + "id": "decimals", + "value": 2 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "queryid" + }, + "properties": [ + { + "id": "displayName", + "value": "Query ID" + }, + { + "id": "links", + "value": [ + { + "title": "Opens 'Single query details' dashboard for that queryid", + "url": "/d/single-query-details?orgId=1&var-dbname=$dbname&var-queryid=${__value.raw}&from=${__from}&to=${__to}" + } + ] + }, + { + "id": "custom.width", + "value": 225 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "planid" + }, + "properties": [ + { + "id": "displayName", + "value": "Plan ID" + }, + { + "id": "custom.width", + "value": 225 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "plan" + }, + "properties": [ + { + "id": "displayName", + "value": "Plan" + } + ] + } + ] + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "pgwatch-metrics" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "select\n avg_plan_run_time,\n queryid,\n planid,\n plan\nfrom (\nselect\n avg((tt - tt_lag)::numeric / (calls - calls_lag)) as avg_plan_run_time,\n max(queryid) as queryid,\n planid,\n case when length(plan) > 250 then plan::varchar(250) || '...' else plan end as plan\nfrom (\n select\n data->>'queryid' as queryid,\n data->>'planid' as planid, \n data->>'plan' as plan,\n (data->>'total_plan_exec_time')::float8 as tt, \n lag((data->>'total_plan_exec_time')::float8) over w as tt_lag,\n (data->>'calls')::float8 as calls, \n lag((data->>'calls')::float8) over w as calls_lag,\n time\n from stat_plans\n where dbname = '$dbname' and $__timeFilter(time) and (data->>'queryid')::int8 = $queryid\n window w as (partition by data->>'planid' order by time)\n) y\nwhere tt > tt_lag\ngroup by planid, plan\norder by 1 desc nulls last\nlimit $top\n) z\norder by 1 desc nulls last;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Top $top plans for query by avg. runtime", + "type": "table" + }, + { + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 12, + "x": 0, + "y": 12 + }, + "id": 4, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "### Brought to you by\n\n[![Cybertec – The PostgreSQL Database Company](https://www.cybertec-postgresql.com/wp-content/uploads/2025/02/cybertec-logo-white-blue.svg)](https://www.cybertec-postgresql.com/en/)\n", + "mode": "markdown" + }, + "pluginVersion": "12.1.0", + "title": "", + "transparent": true, + "type": "text" + } + ], + "preload": false, + "schemaVersion": 41, + "tags": [ + "pgwatch" + ], + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "definition": "SELECT dbname FROM admin.all_distinct_dbname_metrics WHERE metric = 'stat_plans';", + "label": "Source Name", + "name": "dbname", + "options": [], + "query": "SELECT dbname FROM admin.all_distinct_dbname_metrics WHERE metric = 'stat_plans';", + "refresh": 1, + "regex": "", + "sort": 5, + "type": "query" + }, + { + "current": { + "text": "", + "value": "" + }, + "definition": "SELECT DISTINCT data->>'queryid' FROM stat_plans WHERE dbname = '$dbname' AND $__timeFilter(time) ORDER BY 1;", + "label": "Query ID", + "name": "queryid", + "options": [], + "query": "SELECT DISTINCT data->>'queryid' FROM stat_plans WHERE dbname = '$dbname' AND $__timeFilter(time) ORDER BY 1;", + "refresh": 2, + "regex": "", + "type": "query" + }, + { + "current": { + "text": "", + "value": "" + }, + "label": "Top", + "name": "top", + "options": [ + { + "selected": false, + "text": "1", + "value": "1" + }, + { + "selected": true, + "text": "3", + "value": "3" + }, + { + "selected": false, + "text": "5", + "value": "5" + }, + { + "selected": false, + "text": "10", + "value": "10" + }, + { + "selected": false, + "text": "15", + "value": "15" + }, + { + "selected": false, + "text": "20", + "value": "20" + }, + { + "selected": false, + "text": "30", + "value": "30" + }, + { + "selected": false, + "text": "40", + "value": "40" + }, + { + "selected": false, + "text": "50", + "value": "50" + } + ], + "query": "1,3,5,10,15,20,30,40,50", + "type": "custom" + } + ] + }, + "time": { + "from": "now-3h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Stat Plans Top", + "uid": "stat-plans-top", + "version": 12 +} \ No newline at end of file From 1ed132ae6fbcde61643f1d3482e91fb9c655da71 Mon Sep 17 00:00:00 2001 From: 0xgouda Date: Mon, 24 Nov 2025 15:05:47 +0200 Subject: [PATCH 5/6] add `/* pgwatch_generated */` to stat_plans query. --- metric/metrics.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metric/metrics.yaml b/metric/metrics.yaml index ff19ed3..87c4aae 100644 --- a/metric/metrics.yaml +++ b/metric/metrics.yaml @@ -8,7 +8,7 @@ metrics: init_sql: CREATE EXTENSION IF NOT EXISTS pg_stat_statements; CREATE EXTENSION IF NOT EXISTS pg_stat_plans; sqls: 16: |- - WITH p_data AS ( + WITH /* pgwatch_generated */ p_data AS ( SELECT max(p.dbid)::int8 as dbid, max(p.queryid)::int8 as queryid, @@ -56,7 +56,7 @@ metrics: planid::int8, plan, calls::int8, - total_plan_exec_time::int8, + total_plan_exec_time::int8 FROM ( ( From 758552d7b427bc2b262c140d11549f1a2b70357f Mon Sep 17 00:00:00 2001 From: 0xgouda Date: Tue, 25 Nov 2025 10:08:05 +0200 Subject: [PATCH 6/6] Join `Total runtime`, `Avg runtime`, and `Calls` in a single sortable panel. --- grafana/postgres/v12/stat-plans-top.json | 642 ----------------------- grafana/postgres/v12/stat-plans.json | 362 +++++++++++++ 2 files changed, 362 insertions(+), 642 deletions(-) delete mode 100644 grafana/postgres/v12/stat-plans-top.json create mode 100644 grafana/postgres/v12/stat-plans.json diff --git a/grafana/postgres/v12/stat-plans-top.json b/grafana/postgres/v12/stat-plans-top.json deleted file mode 100644 index 52827cb..0000000 --- a/grafana/postgres/v12/stat-plans-top.json +++ /dev/null @@ -1,642 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 0, - "id": 46, - "links": [], - "panels": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "pgwatch-metrics" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "left", - "cellOptions": { - "type": "auto" - }, - "inspect": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "total_plan_run_time" - }, - "properties": [ - { - "id": "displayName", - "value": "Total runtime" - }, - { - "id": "custom.width", - "value": 120 - }, - { - "id": "unit", - "value": "ms" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "queryid" - }, - "properties": [ - { - "id": "displayName", - "value": "Query ID" - }, - { - "id": "links", - "value": [ - { - "title": "Opens 'Single query details' dashboard for that queryid", - "url": "/d/single-query-details?orgId=1&var-dbname=$dbname&var-queryid=${__value.raw}&from=${__from}&to=${__to}" - } - ] - }, - { - "id": "custom.width", - "value": 225 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "planid" - }, - "properties": [ - { - "id": "displayName", - "value": "Plan ID" - }, - { - "id": "custom.width", - "value": 225 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "plan" - }, - "properties": [ - { - "id": "displayName", - "value": "Plan" - } - ] - } - ] - }, - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 1, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true, - "sortBy": [] - }, - "pluginVersion": "12.1.0", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "pgwatch-metrics" - }, - "editorMode": "code", - "format": "table", - "rawQuery": true, - "rawSql": "select\n total_plan_run_time,\n queryid,\n planid,\n plan\nfrom (\nselect\n sum(tt - tt_lag) as total_plan_run_time,\n max(queryid) as queryid,\n planid,\n case when length(plan) > 250 then plan::varchar(250) || '...' else plan end as plan\nfrom (\n select\n data->>'queryid' as queryid,\n data->>'planid' as planid, \n data->>'plan' as plan,\n (data->>'total_plan_exec_time')::float8 as tt, \n lag((data->>'total_plan_exec_time')::float8) over w as tt_lag,\n time\n from stat_plans\n where dbname = '$dbname' and $__timeFilter(time) and (data->>'queryid')::int8 = $queryid\n window w as (partition by data->>'planid' order by time)\n) y\ngroup by planid, plan\nhaving sum(tt - tt_lag) > 0\norder by 1 desc nulls last\nlimit $top\n) z\norder by 1 desc nulls last", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Top $top plans for query by total run time", - "type": "table" - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "pgwatch-metrics" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "left", - "cellOptions": { - "type": "auto" - }, - "inspect": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "calls" - }, - "properties": [ - { - "id": "displayName", - "value": "Calls" - }, - { - "id": "custom.width", - "value": 120 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "queryid" - }, - "properties": [ - { - "id": "displayName", - "value": "Query ID" - }, - { - "id": "links", - "value": [ - { - "title": "Opens 'Single query details' dashboard for that queryid", - "url": "/d/single-query-details?orgId=1&var-dbname=$dbname&var-queryid=${__value.raw}&from=${__from}&to=${__to}" - } - ] - }, - { - "id": "custom.width", - "value": 225 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "planid" - }, - "properties": [ - { - "id": "displayName", - "value": "Plan ID" - }, - { - "id": "custom.width", - "value": 225 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "plan" - }, - "properties": [ - { - "id": "displayName", - "value": "Plan" - } - ] - } - ] - }, - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 4 - }, - "id": 3, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true, - "sortBy": [] - }, - "pluginVersion": "12.1.0", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "pgwatch-metrics" - }, - "editorMode": "code", - "format": "table", - "rawQuery": true, - "rawSql": "select\n calls,\n queryid,\n planid,\n plan\nfrom (\nselect\n sum(calls - calls_lag)::int8 as calls,\n max(queryid) as queryid,\n planid,\n case when length(plan) > 250 then plan::varchar(250) || '...' else plan end as plan\nfrom (\n select\n data->>'queryid' as queryid,\n data->>'planid' as planid, \n data->>'plan' as plan,\n (data->>'calls')::float8 as calls, \n lag((data->>'calls')::float8) over w as calls_lag,\n time\n from stat_plans\n where dbname = '$dbname' and $__timeFilter(time) and (data->>'queryid')::int8 = $queryid\n window w as (partition by data->>'planid' order by time)\n) y\nwhere calls > calls_lag\ngroup by planid, plan\norder by 1 desc nulls last\nlimit $top\n) z\norder by 1 desc nulls last;", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Top $top plans for query by calls", - "type": "table" - }, - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "pgwatch-metrics" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "left", - "cellOptions": { - "type": "auto" - }, - "inspect": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "avg_plan_run_time" - }, - "properties": [ - { - "id": "displayName", - "value": "Avg. runtime" - }, - { - "id": "custom.width", - "value": 120 - }, - { - "id": "unit", - "value": "ms" - }, - { - "id": "decimals", - "value": 2 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "queryid" - }, - "properties": [ - { - "id": "displayName", - "value": "Query ID" - }, - { - "id": "links", - "value": [ - { - "title": "Opens 'Single query details' dashboard for that queryid", - "url": "/d/single-query-details?orgId=1&var-dbname=$dbname&var-queryid=${__value.raw}&from=${__from}&to=${__to}" - } - ] - }, - { - "id": "custom.width", - "value": 225 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "planid" - }, - "properties": [ - { - "id": "displayName", - "value": "Plan ID" - }, - { - "id": "custom.width", - "value": 225 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "plan" - }, - "properties": [ - { - "id": "displayName", - "value": "Plan" - } - ] - } - ] - }, - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 8 - }, - "id": 2, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true, - "sortBy": [] - }, - "pluginVersion": "12.1.0", - "targets": [ - { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "pgwatch-metrics" - }, - "editorMode": "code", - "format": "table", - "rawQuery": true, - "rawSql": "select\n avg_plan_run_time,\n queryid,\n planid,\n plan\nfrom (\nselect\n avg((tt - tt_lag)::numeric / (calls - calls_lag)) as avg_plan_run_time,\n max(queryid) as queryid,\n planid,\n case when length(plan) > 250 then plan::varchar(250) || '...' else plan end as plan\nfrom (\n select\n data->>'queryid' as queryid,\n data->>'planid' as planid, \n data->>'plan' as plan,\n (data->>'total_plan_exec_time')::float8 as tt, \n lag((data->>'total_plan_exec_time')::float8) over w as tt_lag,\n (data->>'calls')::float8 as calls, \n lag((data->>'calls')::float8) over w as calls_lag,\n time\n from stat_plans\n where dbname = '$dbname' and $__timeFilter(time) and (data->>'queryid')::int8 = $queryid\n window w as (partition by data->>'planid' order by time)\n) y\nwhere tt > tt_lag\ngroup by planid, plan\norder by 1 desc nulls last\nlimit $top\n) z\norder by 1 desc nulls last;", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Top $top plans for query by avg. runtime", - "type": "table" - }, - { - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 12, - "x": 0, - "y": 12 - }, - "id": 4, - "options": { - "code": { - "language": "plaintext", - "showLineNumbers": false, - "showMiniMap": false - }, - "content": "### Brought to you by\n\n[![Cybertec – The PostgreSQL Database Company](https://www.cybertec-postgresql.com/wp-content/uploads/2025/02/cybertec-logo-white-blue.svg)](https://www.cybertec-postgresql.com/en/)\n", - "mode": "markdown" - }, - "pluginVersion": "12.1.0", - "title": "", - "transparent": true, - "type": "text" - } - ], - "preload": false, - "schemaVersion": 41, - "tags": [ - "pgwatch" - ], - "templating": { - "list": [ - { - "allowCustomValue": true, - "current": { - "text": "", - "value": "" - }, - "definition": "SELECT dbname FROM admin.all_distinct_dbname_metrics WHERE metric = 'stat_plans';", - "label": "Source Name", - "name": "dbname", - "options": [], - "query": "SELECT dbname FROM admin.all_distinct_dbname_metrics WHERE metric = 'stat_plans';", - "refresh": 1, - "regex": "", - "sort": 5, - "type": "query" - }, - { - "current": { - "text": "", - "value": "" - }, - "definition": "SELECT DISTINCT data->>'queryid' FROM stat_plans WHERE dbname = '$dbname' AND $__timeFilter(time) ORDER BY 1;", - "label": "Query ID", - "name": "queryid", - "options": [], - "query": "SELECT DISTINCT data->>'queryid' FROM stat_plans WHERE dbname = '$dbname' AND $__timeFilter(time) ORDER BY 1;", - "refresh": 2, - "regex": "", - "type": "query" - }, - { - "current": { - "text": "", - "value": "" - }, - "label": "Top", - "name": "top", - "options": [ - { - "selected": false, - "text": "1", - "value": "1" - }, - { - "selected": true, - "text": "3", - "value": "3" - }, - { - "selected": false, - "text": "5", - "value": "5" - }, - { - "selected": false, - "text": "10", - "value": "10" - }, - { - "selected": false, - "text": "15", - "value": "15" - }, - { - "selected": false, - "text": "20", - "value": "20" - }, - { - "selected": false, - "text": "30", - "value": "30" - }, - { - "selected": false, - "text": "40", - "value": "40" - }, - { - "selected": false, - "text": "50", - "value": "50" - } - ], - "query": "1,3,5,10,15,20,30,40,50", - "type": "custom" - } - ] - }, - "time": { - "from": "now-3h", - "to": "now" - }, - "timepicker": {}, - "timezone": "browser", - "title": "Stat Plans Top", - "uid": "stat-plans-top", - "version": 12 -} \ No newline at end of file diff --git a/grafana/postgres/v12/stat-plans.json b/grafana/postgres/v12/stat-plans.json new file mode 100644 index 0000000..93da401 --- /dev/null +++ b/grafana/postgres/v12/stat-plans.json @@ -0,0 +1,362 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 4, + "links": [], + "panels": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "pgwatch-metrics" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "left", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "total_plan_exec_time" + }, + "properties": [ + { + "id": "displayName", + "value": "Total runtime" + }, + { + "id": "custom.width", + "value": 120 + }, + { + "id": "unit", + "value": "ms" + }, + { + "id": "decimals", + "value": 2 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "queryid" + }, + "properties": [ + { + "id": "displayName", + "value": "Query ID" + }, + { + "id": "links", + "value": [ + { + "title": "Opens 'Single query details' dashboard for that queryid", + "url": "/d/single-query-details?orgId=1&var-dbname=$dbname&var-queryid=${__value.raw}&from=${__from}&to=${__to}" + } + ] + }, + { + "id": "custom.width", + "value": 225 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "planid" + }, + "properties": [ + { + "id": "displayName", + "value": "Plan ID" + }, + { + "id": "custom.width", + "value": 225 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "plan" + }, + "properties": [ + { + "id": "displayName", + "value": "Plan" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "avg_plan_exec_time" + }, + "properties": [ + { + "id": "unit", + "value": "ms" + }, + { + "id": "displayName", + "value": "Avg runtime" + }, + { + "id": "custom.width", + "value": 120 + }, + { + "id": "decimals", + "value": 2 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "calls" + }, + "properties": [ + { + "id": "displayName", + "value": "Calls" + }, + { + "id": "custom.width", + "value": 120 + } + ] + } + ] + }, + "gridPos": { + "h": 12, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "pgwatch-metrics" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "select\n total_plan_exec_time,\n calls,\n (total_plan_exec_time / calls) as avg_plan_exec_time,\n queryid,\n planid,\n plan\nfrom (\nselect\n sum(tt - tt_lag) as total_plan_exec_time,\n sum(calls - calls_lag) as calls,\n max(queryid) as queryid,\n planid,\n case when length(plan) > 250 then plan::varchar(250) || '...' else plan end as plan\nfrom (\n select\n data->>'queryid' as queryid,\n data->>'planid' as planid, \n data->>'plan' as plan,\n (data->>'total_plan_exec_time')::float8 as tt, \n lag((data->>'total_plan_exec_time')::float8) over w as tt_lag,\n (data->>'calls')::int8 as calls, \n lag((data->>'calls')::int8) over w as calls_lag,\n time\n from stat_plans\n where dbname = '$dbname' and $__timeFilter(time) and (data->>'queryid')::int8 = $queryid\n window w as (partition by data->>'planid' order by time)\n) y\ngroup by planid, plan\nhaving sum(tt - tt_lag) > 0 AND sum(calls - calls_lag) > 0\norder by 1 desc, 2 desc nulls last\nlimit $top\n) z\norder by 1 desc, 2 desc nulls last", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Top $top plans for query $queryid", + "type": "table" + }, + { + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 12 + }, + "id": 4, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "### Brought to you by\n\n[![Cybertec – The PostgreSQL Database Company](https://www.cybertec-postgresql.com/wp-content/uploads/2025/02/cybertec-logo-white-blue.svg)](https://www.cybertec-postgresql.com/en/)\n", + "mode": "markdown" + }, + "pluginVersion": "12.1.0", + "title": "", + "transparent": true, + "type": "text" + } + ], + "preload": false, + "schemaVersion": 41, + "tags": [ + "pgwatch" + ], + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "definition": "SELECT dbname FROM admin.all_distinct_dbname_metrics WHERE metric = 'stat_plans';", + "label": "Source Name", + "name": "dbname", + "options": [], + "query": "SELECT dbname FROM admin.all_distinct_dbname_metrics WHERE metric = 'stat_plans';", + "refresh": 1, + "regex": "", + "sort": 5, + "type": "query" + }, + { + "current": { + "text": "", + "value": "" + }, + "definition": "SELECT DISTINCT data->>'queryid' FROM stat_plans WHERE dbname = '$dbname' AND $__timeFilter(time) ORDER BY 1;", + "label": "Query ID", + "name": "queryid", + "options": [], + "query": "SELECT DISTINCT data->>'queryid' FROM stat_plans WHERE dbname = '$dbname' AND $__timeFilter(time) ORDER BY 1;", + "refresh": 2, + "regex": "", + "type": "query" + }, + { + "current": { + "text": "", + "value": "" + }, + "label": "Top", + "name": "top", + "options": [ + { + "selected": false, + "text": "1", + "value": "1" + }, + { + "selected": false, + "text": "3", + "value": "3" + }, + { + "selected": true, + "text": "5", + "value": "5" + }, + { + "selected": false, + "text": "10", + "value": "10" + }, + { + "selected": false, + "text": "15", + "value": "15" + }, + { + "selected": false, + "text": "20", + "value": "20" + }, + { + "selected": false, + "text": "30", + "value": "30" + }, + { + "selected": false, + "text": "40", + "value": "40" + }, + { + "selected": false, + "text": "50", + "value": "50" + } + ], + "query": "1,3,5,10,15,20,30,40,50", + "type": "custom" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Stat Plans", + "uid": "stat-plans", + "version": 2 +} \ No newline at end of file