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 diff --git a/metric/metrics.yaml b/metric/metrics.yaml new file mode 100644 index 0000000..87c4aae --- /dev/null +++ b/metric/metrics.yaml @@ -0,0 +1,103 @@ +metrics: + stat_plans: + description: > + This metric collects statistics from the `pg_stat_plans` extension. + 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 /* pgwatch_generated */ 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_plan_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, + 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 + (EXTRACT(epoch FROM now()) * 1e9)::int8 AS epoch_ns, + queryid::int8, + planid::int8, + plan, + calls::int8, + total_plan_exec_time::int8 + 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