pax_global_header00006660000000000000000000000064145137330340014515gustar00rootroot0000000000000052 comment=251fc5ca4468196de5f1db222218a2c9c3ec1fb8 pg_show_plans-2.0.2/000077500000000000000000000000001451373303400143615ustar00rootroot00000000000000pg_show_plans-2.0.2/.editorconfig000066400000000000000000000002331451373303400170340ustar00rootroot00000000000000root = true [*] end_of_line = lf insert_final_newline = true charset = utf-8 [*.{c,h}] indent_style = tab indent_size = 4 [Makefile] indent_style = tab pg_show_plans-2.0.2/.github/000077500000000000000000000000001451373303400157215ustar00rootroot00000000000000pg_show_plans-2.0.2/.github/workflows/000077500000000000000000000000001451373303400177565ustar00rootroot00000000000000pg_show_plans-2.0.2/.github/workflows/regression.yml000066400000000000000000000016151451373303400226640ustar00rootroot00000000000000name: Build on: [push, pull_request] jobs: build: runs-on: ubuntu-latest defaults: run: shell: sh strategy: matrix: pgversion: - 16 - 15 - 14 - 13 - 12 env: PGVERSION: ${{ matrix.pgversion }} steps: - name: checkout uses: actions/checkout@v3 - name: install pg run: | sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -v $PGVERSION -p -i sudo -u postgres createuser -s "$USER" - name: build run: | make PROFILE="-Werror" sudo -E make install - name: test run: | sudo pg_conftool set shared_preload_libraries pg_show_plans sudo pg_ctlcluster $PGVERSION main restart make installcheck - name: show regression diffs if: ${{ failure() }} run: | cat regression.diffs pg_show_plans-2.0.2/.gitignore000066400000000000000000000001761451373303400163550ustar00rootroot00000000000000# Clangd .cache/ .clangd compile_commands.json # Build Output *.bc *.o *.so # Regression Tests Output results/ regression.* pg_show_plans-2.0.2/LICENSE000066400000000000000000000020771451373303400153740ustar00rootroot00000000000000The PostgreSQL License Copyright (c) 2019-2023, CYBERTEC PostgreSQL International GmbH Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. IN NO EVENT SHALL CYBERTEC PostgreSQL International GmbH BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF CYBERTEC PostgreSQL International GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. CYBERTEC PostgreSQL International GmbH SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND CYBERTEC PostgreSQL International GmbH HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. pg_show_plans-2.0.2/Makefile000066400000000000000000000007611451373303400160250ustar00rootroot00000000000000MODULE_big = pg_show_plans OBJS = pg_show_plans.o EXTENSION = pg_show_plans DATA = pg_show_plans--1.0--1.1.sql \ pg_show_plans--1.1--2.0.sql \ pg_show_plans--2.0.sql REGRESS = pg_show_plans formats DOCS = pg_show_plans.md USE_PGXS = 1 ifdef USE_PGXS PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) else subdir = contrib/pg_show_plans top_builddir = ../.. include $(top_builddir)/src/Makefile.global include $(top_srcdir)/contrib/contrib-global.mk endif pg_show_plans-2.0.2/README.md000066400000000000000000000116101451373303400156370ustar00rootroot00000000000000# pg_show_plans PostgreSQL extension that shows query plans of all the currently running SQL statements. Query plans can be shown in several formats, like `JSON`. *This extension creates a hash table within shared memory. The hash table is not resizable, thus, no new plans can be added once it has been filled up.* # INSTALL Either use PGXS infrastructure (recommended), or compile within the source tree. PostgreSQL versions 12 and newer are supported. ## PGXS Install PostgreSQL before proceeding. Make sure to have `pg_config` binary, these are typically included in `-dev` and `-devel` packages. ```bash git clone https://github.com/cybertec-postgresql/pg_show_plans.git cd pg_show_plans make make install ``` ## Within Source Tree ```bash PG_VER='15.3' # Set the required PostgreSQL version. curl -O "https://download.postgresql.org/pub/source/v${PG_VER}/postgresql-${PG_VER}.tar.bz2" tar xvfj "postgresql-${PG_VER}.tar.bz2" cd postgresql-${PG_VER} ./configure cd contrib git clone https://github.com/cybertec-postgresql/pg_show_plans.git cd pg_show_plans make USE_PGXS= make USE_PGXS= install ``` ## Configure Add `pg_show_plans` to `shared_preload_libraries` within `postgresql.conf`: ``` shared_preload_libraries = 'pg_show_plans' ``` Restart the server, and invoke `CREATE EXTENSION pg_show_plans;`: ``` postgresql=# CREATE EXTENSION pg_show_plans; CREATE EXTENSION postgresql=# ``` # USAGE To see the query plans: ``` testdb=# SELECT * FROM pg_show_plans; pid | level | userid | dbid | plan -------+-------+--------+-------+----------------------------------------------------------------------- 11473 | 0 | 10 | 16384 | Function Scan on pg_show_plans (cost=0.00..10.00 rows=1000 width=56) 11504 | 0 | 10 | 16384 | Function Scan on print_item (cost=0.25..10.25 rows=1000 width=524) 11504 | 1 | 10 | 16384 | Result (cost=0.00..0.01 rows=1 width=4) (3 rows) ``` To get query plans and see the corresponding query expression: ``` testdb=# \x Expanded display is on. testdb=# SELECT p.pid, p.level, p.plan, a.query FROM pg_show_plans p LEFT JOIN pg_stat_activity a ON p.pid = a.pid AND p.level = 0 ORDER BY p.pid, p.level; -[ RECORD 1 ]----------------------------------------------------------------------------------------- pid | 11473 level | 0 plan | Sort (cost=72.08..74.58 rows=1000 width=80) + | Sort Key: pg_show_plans.pid, pg_show_plans.level + | -> Hash Left Join (cost=2.25..22.25 rows=1000 width=80) + | Hash Cond: (pg_show_plans.pid = s.pid) + | Join Filter: (pg_show_plans.level = 0) + | -> Function Scan on pg_show_plans (cost=0.00..10.00 rows=1000 width=48) + | -> Hash (cost=1.00..1.00 rows=100 width=44) + | -> Function Scan on pg_stat_get_activity s (cost=0.00..1.00 rows=100 width=44) query | SELECT p.pid, p.level, p.plan, a.query FROM pg_show_plans p + | LEFT JOIN pg_stat_activity a + | ON p.pid = a.pid AND p.level = 0 ORDER BY p.pid, p.level; -[ RECORD 2 ]----------------------------------------------------------------------------------------- pid | 11517 level | 0 plan | Function Scan on print_item (cost=0.25..10.25 rows=1000 width=524) query | SELECT * FROM print_item(1,20); -[ RECORD 3 ]----------------------------------------------------------------------------------------- pid | 11517 level | 1 plan | Result (cost=0.00..0.01 rows=1 width=4) query | ``` # REFERENCE ## GUC Variables * `pg_show_plans.plan_format = text`: query plans output format, either of `text`, `json`, `yaml`, and `xml`. * `pg_show_plans.max_plan_length = 16384`: query plan maximal length in bytes. This value affects the amount of shared memory the extension asks for, the server may not start if the value is too high. * `pg_show_plans.is_enabled = true`: enable or disable the extension by assigning to this variable. *Default values are shown after '=' sign.* ## Views * `pg_show_plans`: defined as `SELECT * FROM pg_show_plans();`. ## Functions * `pg_show_plans()`: show query plans: - `pid`: server process ID that runs the query. - `level`: query nest level. Top level is 0. For example, if you execute a simple select query, the level of this query's plan is 0. If you execute a function that invokes a select query, level 0 is the plan of the function and level 1 is the plan of the select query invoked by the function. - `userid`: user ID who runs the query. - `dbid`: database ID the query runs in. - `plan`: query plan. pg_show_plans-2.0.2/debian/000077500000000000000000000000001451373303400156035ustar00rootroot00000000000000pg_show_plans-2.0.2/debian/changelog000066400000000000000000000005001451373303400174500ustar00rootroot00000000000000pg-show-plans (1.1.5-1) unstable; urgency=medium * New upstream version. * Upload for PostgreSQL 16. -- Christoph Berg Sun, 17 Sep 2023 20:46:10 +0200 pg-show-plans (1.1.2-1) unstable; urgency=medium * Initial release. -- Christoph Berg Fri, 31 Mar 2023 16:25:36 +0200 pg_show_plans-2.0.2/debian/control000066400000000000000000000014731451373303400172130ustar00rootroot00000000000000Source: pg-show-plans Section: database Priority: optional Maintainer: CYBERTEC PostgreSQL Uploaders: Christoph Berg , Build-Depends: debhelper-compat (= 13), postgresql-all (>= 217~), Standards-Version: 4.6.2 Rules-Requires-Root: no Homepage: https://github.com/cybertec-postgresql/pg_show_plans Vcs-Browser: https://github.com/cybertec-postgresql/pg_show_plans Vcs-Git: https://github.com/cybertec-postgresql/pg_show_plans.git Package: postgresql-16-show-plans Architecture: any Depends: ${misc:Depends}, ${postgresql:Depends}, ${shlibs:Depends}, Description: Show query plans of currently running PostgreSQL statements This PostgreSQL extension shows the query plans of all currently running SQL statements. Plan output format can be plain text (default), JSON, YAML, or XML. pg_show_plans-2.0.2/debian/control.in000066400000000000000000000015021451373303400176110ustar00rootroot00000000000000Source: pg-show-plans Section: database Priority: optional Maintainer: CYBERTEC PostgreSQL Uploaders: Christoph Berg , Build-Depends: debhelper-compat (= 13), postgresql-all (>= 217~), Standards-Version: 4.6.2 Rules-Requires-Root: no Homepage: https://github.com/cybertec-postgresql/pg_show_plans Vcs-Browser: https://github.com/cybertec-postgresql/pg_show_plans Vcs-Git: https://github.com/cybertec-postgresql/pg_show_plans.git Package: postgresql-PGVERSION-show-plans Architecture: any Depends: ${misc:Depends}, ${postgresql:Depends}, ${shlibs:Depends}, Description: Show query plans of currently running PostgreSQL statements This PostgreSQL extension shows the query plans of all currently running SQL statements. Plan output format can be plain text (default), JSON, YAML, or XML. pg_show_plans-2.0.2/debian/copyright000066400000000000000000000026551451373303400175460ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: pg_show_plans Source: https://github.com/cybertec-postgresql/pg_show_plans Files: * Copyright: Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group Portions Copyright (c) 1994, The Regents of the University of California Copyright (c) 2019-2023, CYBERTEC PostgreSQL International GmbH License: PostgreSQL-Cybertec Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. . IN NO EVENT SHALL CYBERTEC PostgreSQL International GmbH BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF CYBERTEC PostgreSQL International GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. . CYBERTEC PostgreSQL International GmbH SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND CYBERTEC PostgreSQL International GmbH HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. pg_show_plans-2.0.2/debian/gitlab-ci.yml000066400000000000000000000001371451373303400201620ustar00rootroot00000000000000include: https://salsa.debian.org/postgresql/postgresql-common/raw/master/gitlab/gitlab-ci.yml pg_show_plans-2.0.2/debian/pgversions000066400000000000000000000000041451373303400177170ustar00rootroot0000000000000012+ pg_show_plans-2.0.2/debian/rules000077500000000000000000000003461451373303400166660ustar00rootroot00000000000000#!/usr/bin/make -f %: dh $@ --with pgxs override_dh_installdocs: dh_installdocs --all README.* override_dh_pgxs_test: +LANG=C pg_buildext -o 'shared_preload_libraries=pg_show_plans' installcheck . . postgresql-%v-show-plans pg_show_plans-2.0.2/debian/source/000077500000000000000000000000001451373303400171035ustar00rootroot00000000000000pg_show_plans-2.0.2/debian/source/format000066400000000000000000000000141451373303400203110ustar00rootroot000000000000003.0 (quilt) pg_show_plans-2.0.2/debian/tests/000077500000000000000000000000001451373303400167455ustar00rootroot00000000000000pg_show_plans-2.0.2/debian/tests/control000066400000000000000000000001031451373303400203420ustar00rootroot00000000000000Depends: make, @, Tests: installcheck Restrictions: allow-stderr pg_show_plans-2.0.2/debian/tests/installcheck000077500000000000000000000001201451373303400213300ustar00rootroot00000000000000#!/bin/sh pg_buildext -o 'shared_preload_libraries=pg_show_plans' installcheck pg_show_plans-2.0.2/debian/watch000066400000000000000000000001241451373303400166310ustar00rootroot00000000000000version=4 https://github.com/cybertec-postgresql/pg_show_plans/tags .*/v(.*).tar.gz pg_show_plans-2.0.2/expected/000077500000000000000000000000001451373303400161625ustar00rootroot00000000000000pg_show_plans-2.0.2/expected/formats.out000066400000000000000000000116331451373303400203720ustar00rootroot00000000000000-- explain output on PG12/13 is missing "Async Capable" select setting::int < 140000 as pg12_13 from pg_settings where name = 'server_version_num'; pg12_13 --------- f (1 row) -- json output set pg_show_plans.plan_format = 'json'; show pg_show_plans.plan_format; pg_show_plans.plan_format --------------------------- json (1 row) select * from nest(); level | plan -------+--------------------------------------- 0 | [ + | "Plan": { + | "Node Type": "Function Scan", + | "Parallel Aware": false, + | "Async Capable": false, + | "Function Name": "nest", + | "Alias": "nest", + | "Startup Cost": 0.25, + | "Total Cost": 10.25, + | "Plan Rows": 1000, + | "Plan Width": 36 + | } + | ] 1 | [ + | "Plan": { + | "Node Type": "Function Scan", + | "Parallel Aware": false, + | "Async Capable": false, + | "Function Name": "pg_show_plans",+ | "Alias": "pg_show_plans", + | "Startup Cost": 0.00, + | "Total Cost": 12.50, + | "Plan Rows": 333, + | "Plan Width": 36, + | "Filter": "(level >= 0)" + | } + | ] (2 rows) -- yaml output set pg_show_plans.plan_format = 'yaml'; show pg_show_plans.plan_format; pg_show_plans.plan_format --------------------------- yaml (1 row) select * from nest(); level | plan -------+---------------------------------- 0 | Plan: + | Node Type: "Function Scan" + | Parallel Aware: false + | Async Capable: false + | Function Name: "nest" + | Alias: "nest" + | Startup Cost: 0.25 + | Total Cost: 10.25 + | Plan Rows: 1000 + | Plan Width: 36 1 | Plan: + | Node Type: "Function Scan" + | Parallel Aware: false + | Async Capable: false + | Function Name: "pg_show_plans"+ | Alias: "pg_show_plans" + | Startup Cost: 0.00 + | Total Cost: 12.50 + | Plan Rows: 333 + | Plan Width: 36 + | Filter: "(level >= 0)" (2 rows) -- xml output set pg_show_plans.plan_format = 'xml'; show pg_show_plans.plan_format; pg_show_plans.plan_format --------------------------- xml (1 row) select * from nest(); level | plan -------+---------------------------------------------------------- 0 | + | + | Function Scan + | false + | false + | nest + | nest + | 0.25 + | 10.25 + | 1000 + | 36 + | + | 1 | + | + | Function Scan + | false + | false + | pg_show_plans + | pg_show_plans + | 0.00 + | 12.50 + | 333 + | 36 + | (level >= 0) + | + | (2 rows) -- check plan format after reconnect \c show pg_show_plans.plan_format; pg_show_plans.plan_format --------------------------- xml (1 row) pg_show_plans-2.0.2/expected/formats_1.out000066400000000000000000000111371451373303400206110ustar00rootroot00000000000000-- explain output on PG12/13 is missing "Async Capable" select setting::int < 140000 as pg12_13 from pg_settings where name = 'server_version_num'; pg12_13 --------- t (1 row) -- json output set pg_show_plans.plan_format = 'json'; show pg_show_plans.plan_format; pg_show_plans.plan_format --------------------------- json (1 row) select * from nest(); level | plan -------+--------------------------------------- 0 | [ + | "Plan": { + | "Node Type": "Function Scan", + | "Parallel Aware": false, + | "Function Name": "nest", + | "Alias": "nest", + | "Startup Cost": 0.25, + | "Total Cost": 10.25, + | "Plan Rows": 1000, + | "Plan Width": 36 + | } + | ] 1 | [ + | "Plan": { + | "Node Type": "Function Scan", + | "Parallel Aware": false, + | "Function Name": "pg_show_plans",+ | "Alias": "pg_show_plans", + | "Startup Cost": 0.00, + | "Total Cost": 12.50, + | "Plan Rows": 333, + | "Plan Width": 36, + | "Filter": "(level >= 0)" + | } + | ] (2 rows) -- yaml output set pg_show_plans.plan_format = 'yaml'; show pg_show_plans.plan_format; pg_show_plans.plan_format --------------------------- yaml (1 row) select * from nest(); level | plan -------+---------------------------------- 0 | Plan: + | Node Type: "Function Scan" + | Parallel Aware: false + | Function Name: "nest" + | Alias: "nest" + | Startup Cost: 0.25 + | Total Cost: 10.25 + | Plan Rows: 1000 + | Plan Width: 36 1 | Plan: + | Node Type: "Function Scan" + | Parallel Aware: false + | Function Name: "pg_show_plans"+ | Alias: "pg_show_plans" + | Startup Cost: 0.00 + | Total Cost: 12.50 + | Plan Rows: 333 + | Plan Width: 36 + | Filter: "(level >= 0)" (2 rows) -- xml output set pg_show_plans.plan_format = 'xml'; show pg_show_plans.plan_format; pg_show_plans.plan_format --------------------------- xml (1 row) select * from nest(); level | plan -------+---------------------------------------------------------- 0 | + | + | Function Scan + | false + | nest + | nest + | 0.25 + | 10.25 + | 1000 + | 36 + | + | 1 | + | + | Function Scan + | false + | pg_show_plans + | pg_show_plans + | 0.00 + | 12.50 + | 333 + | 36 + | (level >= 0) + | + | (2 rows) -- check plan format after reconnect \c show pg_show_plans.plan_format; pg_show_plans.plan_format --------------------------- xml (1 row) pg_show_plans-2.0.2/expected/pg_show_plans.out000066400000000000000000000024211451373303400215550ustar00rootroot00000000000000create extension pg_show_plans; show pg_show_plans.is_enabled; pg_show_plans.is_enabled -------------------------- on (1 row) show pg_show_plans.max_plan_length; pg_show_plans.max_plan_length ------------------------------- 16384 (1 row) create function nest() returns table (level int, plan text) language plpgsql as $$ begin return query select pg_show_plans.level, pg_show_plans.plan from pg_show_plans where pg_show_plans.level >= 0; end; $$; -- text output set pg_show_plans.plan_format = 'text'; show pg_show_plans.plan_format; pg_show_plans.plan_format --------------------------- text (1 row) select level, plan from pg_show_plans; level | plan -------+----------------------------------------------------------------------- 0 | Function Scan on pg_show_plans (cost=0.00..10.00 rows=1000 width=36) (1 row) select * from nest(); level | plan -------+---------------------------------------------------------------------- 0 | Function Scan on nest (cost=0.25..10.25 rows=1000 width=36) 1 | Function Scan on pg_show_plans (cost=0.00..12.50 rows=333 width=36)+ | Filter: (level >= 0) (2 rows) pg_show_plans-2.0.2/pg_show_plans--1.0--1.1.sql000066400000000000000000000014131451373303400206670ustar00rootroot00000000000000-- pid/level changed from int8 to int DROP VIEW pg_show_plans; DROP FUNCTION pg_show_plans; CREATE FUNCTION pg_show_plans( OUT pid int, OUT level int, OUT userid oid, OUT dbid oid, OUT plan text ) RETURNS SETOF record AS 'MODULE_PATHNAME' LANGUAGE C; -- Register a view on the function for ease of use. CREATE VIEW pg_show_plans AS SELECT * FROM pg_show_plans(); GRANT SELECT ON pg_show_plans TO PUBLIC; -- Some 1.0 versions already contained yaml/xml, use "or replace" here CREATE OR REPLACE FUNCTION pgsp_format_yaml() RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C; CREATE OR REPLACE FUNCTION pgsp_format_xml() RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C; REVOKE ALL ON FUNCTION pgsp_format_yaml() FROM PUBLIC; REVOKE ALL ON FUNCTION pgsp_format_xml() FROM PUBLIC; pg_show_plans-2.0.2/pg_show_plans--1.0.sql000066400000000000000000000022221451373303400203140ustar00rootroot00000000000000/* pg_show_plans/pg_show_plans--1.0.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pg_show_plans" to load this file. \quit -- Register functions. CREATE FUNCTION pg_show_plans_enable() RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C; CREATE FUNCTION pg_show_plans_disable() RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C; CREATE FUNCTION pgsp_format_json() RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C; CREATE FUNCTION pgsp_format_text() RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C; CREATE FUNCTION pg_show_plans( OUT pid int8, OUT level int8, OUT userid oid, OUT dbid oid, OUT plan text ) RETURNS SETOF record AS 'MODULE_PATHNAME' LANGUAGE C; -- Register a view on the function for ease of use. CREATE VIEW pg_show_plans AS SELECT * FROM pg_show_plans(); GRANT SELECT ON pg_show_plans TO PUBLIC; -- Don't want this to be available to non-superusers. REVOKE ALL ON FUNCTION pg_show_plans_enable() FROM PUBLIC; REVOKE ALL ON FUNCTION pg_show_plans_disable() FROM PUBLIC; REVOKE ALL ON FUNCTION pgsp_format_json() FROM PUBLIC; REVOKE ALL ON FUNCTION pgsp_format_text() FROM PUBLIC; pg_show_plans-2.0.2/pg_show_plans--1.1--2.0.sql000066400000000000000000000003041451373303400206660ustar00rootroot00000000000000DROP FUNCTION gsp_format_text; DROP FUNCTION gsp_format_json; DROP FUNCTION gsp_format_yaml; DROP FUNCTION gsp_format_xml; DROP FUNCTION pg_show_plans_enable; DROP FUNCTION pg_show_plans_disable; pg_show_plans-2.0.2/pg_show_plans--1.1.sql000066400000000000000000000026201451373303400203170ustar00rootroot00000000000000/* pg_show_plans/pg_show_plans--1.0.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pg_show_plans" to load this file. \quit -- Register functions. CREATE FUNCTION pg_show_plans_enable() RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C; CREATE FUNCTION pg_show_plans_disable() RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C; CREATE FUNCTION pgsp_format_text() RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C; CREATE FUNCTION pgsp_format_json() RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C; CREATE FUNCTION pgsp_format_yaml() RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C; CREATE FUNCTION pgsp_format_xml() RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C; CREATE FUNCTION pg_show_plans( OUT pid int, OUT level int, OUT userid oid, OUT dbid oid, OUT plan text ) RETURNS SETOF record AS 'MODULE_PATHNAME' LANGUAGE C; -- Register a view on the function for ease of use. CREATE VIEW pg_show_plans AS SELECT * FROM pg_show_plans(); GRANT SELECT ON pg_show_plans TO PUBLIC; -- Don't want this to be available to non-superusers. REVOKE ALL ON FUNCTION pg_show_plans_enable() FROM PUBLIC; REVOKE ALL ON FUNCTION pg_show_plans_disable() FROM PUBLIC; REVOKE ALL ON FUNCTION pgsp_format_text() FROM PUBLIC; REVOKE ALL ON FUNCTION pgsp_format_json() FROM PUBLIC; REVOKE ALL ON FUNCTION pgsp_format_yaml() FROM PUBLIC; REVOKE ALL ON FUNCTION pgsp_format_xml() FROM PUBLIC; pg_show_plans-2.0.2/pg_show_plans--2.0.sql000066400000000000000000000007741451373303400203270ustar00rootroot00000000000000/* pg_show_plans/pg_show_plans--2.0.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pg_show_plans" to load this file. \quit CREATE FUNCTION pg_show_plans( OUT pid int, OUT level int, OUT userid oid, OUT dbid oid, OUT plan text ) RETURNS SETOF record AS 'MODULE_PATHNAME' LANGUAGE C; -- Register a view on the function for ease of use. CREATE VIEW pg_show_plans AS SELECT * FROM pg_show_plans(); GRANT SELECT ON pg_show_plans TO PUBLIC; pg_show_plans-2.0.2/pg_show_plans.c000066400000000000000000000442731451373303400174020ustar00rootroot00000000000000/* * ------------------------------------------------------------------------- * * pg_show_plans.c * Show query plans of all currently running SQL statements * * Copyright (c) 2008-2022, PostgreSQL Global Development Group * Copyright (c) 2019-2023, CYBERTEC PostgreSQL International GmbH * * ------------------------------------------------------------------------- */ /* Includes */ #include "postgres.h" #include "catalog/pg_authid.h" #include "commands/explain.h" #include "fmgr.h" #include "funcapi.h" #include "lib/stringinfo.h" #include "miscadmin.h" #include "storage/ipc.h" #include "storage/lwlock.h" #include "storage/shmem.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/guc.h" /* Constants and Macros */ PG_MODULE_MAGIC; #if PG_VERSION_NUM < 120000 #error "Unsupported PostgreSQL Version" #endif #define MAX_NEST_LEVEL 10 /* Typedefs */ typedef struct pgspHashKey /* Hash entry key. */ { pid_t pid; } pgspHashKey; typedef struct pgspEntry /* Hash table entry. */ { pgspHashKey hash_key; /* Entry hash key, must be first. */ slock_t mutex; /* Protects the entry. */ Oid user_id; /* User OID. */ Oid db_id; /* Database OID. */ int plan_len[MAX_NEST_LEVEL]; /* Query plan length in bytes. */ int n_plans; /* Query plan count. */ char plan[]; /* Query plan string. */ } pgspEntry; typedef struct pgspSharedState /* Shared state of the extension. */ { LWLock *lock; /* Protects shared hash table. */ bool is_enabled; /* Enables or disables the extension. */ int plan_format; } pgspSharedState; typedef struct pgspCtx { /* Used as `funcctx->user_fctx` in pg_show_plans(). */ HASH_SEQ_STATUS *hash_seq; pgspEntry *pgvp_tmp_entry; /* PGVP entry currently processing. */ int curr_nest; /* Current nest level porcessing. */ bool is_done; /* Done processing current PGVP entry? */ } pgspCtx; /* Function Prototypes */ void _PG_init(void); /* Returns shared hash entry size. */ static Size hash_entry_size(void); /* Calculates shared memory size required for the extension. */ static Size shmem_required(void); /* Generates a hash entry key. */ static uint32 gen_hash_key(const void *key, Size keysize); /* Hash entry comparison function. */ static int compare_hash_key(const void *key1, const void *key2, Size keysize); /* Caches the process' hash entry (if not already). Returns 1 on success. */ static int ensure_cached(void); /* Grabs an exclusive lock, and creates a new hash table entry. */ static pgspEntry *create_hash_entry(const pgspHashKey *key); /* Add a new query plan to the shared hash entry. */ static void append_query_plan(ExplainState *es); /* on_shmem_exit() callback to delete hash entry on client disconnect. */ static void cleanup(int code, Datum arg); /* Set extension state, either enable or disable. */ static void set_state(bool state, void *extra); static const char *show_state(void); /* Set query plan output format: text, json, ... */ static void set_format(const int format); /* Propagate GUC variable value to shared memory (assign hook). */ static void prop_format_to_shmem(int newval, void *extra); static const char *show_format(void); /* Check the extension has been properly loaded. */ static inline void shmem_safety_check(void); /* Check whether the user has required privileges. */ static bool is_allowed_role(void); /* Hook functions. */ /* Ask for shared memory. */ #if PG_VERSION_NUM >= 150000 static void pgvp_shmem_request(void); #endif static void pgsp_shmem_startup(void); /* Saves query plans to the shared hash table. */ static void pgsp_ExecutorStart(QueryDesc *queryDesc, int eflags); /* Keeps track of the nest level. */ static void pgsp_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, uint64 count, bool execute_once); /* Show query plans of all the currently running statements. */ Datum pg_show_plans(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(pg_show_plans); /* Global Variables */ /* Shared extension state. */ static pgspSharedState *pgsp = NULL; /* Current process' hash entry. */ static pgspEntry *pgsp_cache = NULL; /* Shared hash table with query plans. */ static HTAB *pgsp_hash = NULL; /* Current query plan nested level. */ static unsigned int nest_level = 0; /* To save old hook values. */ #if PG_VERSION_NUM >= 150000 static shmem_request_hook_type prev_shmem_request_hook = NULL; #endif static shmem_startup_hook_type prev_shmem_startup_hook = NULL; static ExecutorStart_hook_type prev_ExecutorStart = NULL; static ExecutorRun_hook_type prev_ExecutorRun = NULL; /* GUC variables. */ /* Maximal query plan length. */ static int max_plan_length; /* Start extension enabled or not?. */ static bool start_enabled; /* pg_show_plans() query plan output format. */ static int plan_format; /* Available query plan formats. */ static const struct config_enum_entry plan_formats[] = { {"text", EXPLAIN_FORMAT_TEXT, false}, {"json", EXPLAIN_FORMAT_JSON, false}, {"yaml", EXPLAIN_FORMAT_YAML, false}, {"xml", EXPLAIN_FORMAT_XML, false}, {NULL, 0, false} }; void _PG_init(void) { /* Must be in shared_preload_libraries="...". */ if (!process_shared_preload_libraries_in_progress) return; DefineCustomBoolVariable("pg_show_plans.is_enabled", "Start with the extension enabled?", NULL, &start_enabled, true, PGC_USERSET, 0, NULL, set_state, show_state); DefineCustomIntVariable("pg_show_plans.max_plan_length", gettext_noop("Set the maximum plan length. " "Note that this module allocates (max_plan_length*max_connections) " "bytes on the shared memory."), gettext_noop("A hash entry whose length is max_plan_length stores the plans of " "all nested levels, so this value should be set enough size. " "However, if it is too large, the server may not be able to start " "because of the shortage of memory due to the huge shared memory size."), &max_plan_length, 16 * 1024, 1024, 100 * 1024, PGC_POSTMASTER, 0, NULL, NULL, NULL); DefineCustomEnumVariable("pg_show_plans.plan_format", "Set the output format of query plans.", NULL, &plan_format, EXPLAIN_FORMAT_TEXT, plan_formats, PGC_USERSET, 0, NULL, prop_format_to_shmem, show_format); /* Save old hooks, and install new ones. */ #if PG_VERSION_NUM >= 150000 prev_shmem_request_hook = shmem_request_hook; shmem_request_hook = pgvp_shmem_request; #else RequestAddinShmemSpace(shmem_required()); RequestNamedLWLockTranche("pg_show_plans", 1); #endif prev_shmem_startup_hook = shmem_startup_hook; shmem_startup_hook = pgsp_shmem_startup; prev_ExecutorStart = ExecutorStart_hook; ExecutorStart_hook = pgsp_ExecutorStart; /* Store new plans. */ prev_ExecutorRun = ExecutorRun_hook; ExecutorRun_hook = pgsp_ExecutorRun; /* Track nest level. */ } static Size hash_entry_size(void) { /* Structure size & variable array maximal length. */ return offsetof(pgspEntry, plan) + max_plan_length; } static Size shmem_required(void) { Size s; s = MAXALIGN(sizeof(pgspSharedState)); s = add_size(s, hash_estimate_size(MaxConnections, hash_entry_size()) ); return s; } static uint32 gen_hash_key(const void *key, Size keysize) { const pgspHashKey *k = (const pgspHashKey *) key; return (uint32) k->pid; } static int compare_hash_key(const void *key1, const void *key2, Size keysize) { const pgspHashKey *k1 = (const pgspHashKey *) key1; const pgspHashKey *k2 = (const pgspHashKey *) key2; return (k1->pid == k2->pid) ? 0 : 1; } static int ensure_cached(void) { pgspHashKey pgvp_hash_hey; if (pgsp_cache) return 1; pgvp_hash_hey.pid = MyProcPid; pgsp_cache = create_hash_entry(&pgvp_hash_hey); if (!pgsp_cache) return 0; /* Ran out of memory. */ pgsp_cache->user_id = GetUserId(); pgsp_cache->plan[0] = '\0'; pgsp_cache->n_plans = 0; on_shmem_exit(cleanup, (Datum)NULL); return 1; } /* Returns NULL if the hash table is full. */ static pgspEntry * create_hash_entry(const pgspHashKey *key) { pgspEntry *entry; LWLockAcquire(pgsp->lock, LW_EXCLUSIVE); entry = (pgspEntry *)hash_search(pgsp_hash, key, HASH_ENTER_NULL, NULL); LWLockRelease(pgsp->lock); return entry; } static void append_query_plan(ExplainState *es) { const StringInfo new_plan = es->str; int i; int space_left; /* Space left within a shared hash map entry. */ int offset; /* Beginning of free space within an entry. */ offset = 0; for (i = 0; i < nest_level; i++) offset += pgsp_cache->plan_len[i] + 1; space_left = max_plan_length - offset; if (pgsp->plan_format == EXPLAIN_FORMAT_TEXT) new_plan->len--; /* Discard '\n'. */ if (new_plan->len < space_left) { /* Enough space for a new plan. */ memcpy(pgsp_cache->plan + offset, new_plan->data, new_plan->len); pgsp_cache->plan[offset + new_plan->len] = '\0'; pgsp_cache->plan_len[nest_level] = new_plan->len; } else { /* No space left to hold a new plan, snip it. */ memcpy(pgsp_cache->plan + offset, new_plan->data, space_left); pgsp_cache->plan[max_plan_length-1] = '\0'; pgsp_cache->plan[max_plan_length-2] = '|'; /* Snip indicator. */ pgsp_cache->plan_len[nest_level] = space_left; } pgsp_cache->db_id = MyDatabaseId; pgsp_cache->n_plans = nest_level+1; } static void cleanup(int code, Datum arg) { pgspHashKey key; key.pid = pgsp_cache->hash_key.pid; LWLockAcquire(pgsp->lock, LW_EXCLUSIVE); hash_search(pgsp_hash, &key, HASH_REMOVE, NULL); LWLockRelease(pgsp->lock); } static void set_state(bool state, void *extra) { /* Shared memory may not be fully available at server start, so we do not * check for pgsp_hash availability here. That is why the following line is * commented out. */ /* shmem_safety_check(); */ if (pgsp != NULL && is_allowed_role()) pgsp->is_enabled = state; } /* since we can't update start_enabled in running backends, provide a show hook * that reads the value from shared memory */ static const char * show_state() { if (pgsp->is_enabled) return "on"; else return "off"; } static void set_format(const int format) { /* Shared memory may not be fully available at server start, so we do not * check for pgsp_hash availability here. That is why the following line is * commented out. */ /* shmem_safety_check(); */ if (pgsp != NULL && is_allowed_role()) pgsp->plan_format = format; } static void prop_format_to_shmem(int newval, void *extra) { set_format(newval); } static const char * show_format() { if (pgsp->plan_format == EXPLAIN_FORMAT_TEXT) return "text"; else if (pgsp->plan_format == EXPLAIN_FORMAT_JSON) return "json"; else if (pgsp->plan_format == EXPLAIN_FORMAT_YAML) return "yaml"; else if (pgsp->plan_format == EXPLAIN_FORMAT_XML) return "xml"; else elog(ERROR, "unexpected plan_format value: %d", pgsp->plan_format); } static inline void shmem_safety_check(void) { if (pgsp && pgsp_hash) return; ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pg_show_plans must be loaded" " via shared_preload_libraries"))); } static bool is_allowed_role(void) { bool is_allowed_role = false; #if PG_VERSION_NUM >= 140000 is_allowed_role = is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS); #else is_allowed_role = is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_ALL_STATS); #endif return is_allowed_role; } #if PG_VERSION_NUM >= 150000 static void pgvp_shmem_request(void) { if (prev_shmem_request_hook) prev_shmem_request_hook(); RequestAddinShmemSpace(shmem_required()); RequestNamedLWLockTranche("pg_show_plans", 1); } #endif static void pgsp_shmem_startup(void) { bool found; HASHCTL info; if (prev_shmem_startup_hook) prev_shmem_startup_hook(); /* Reset in case this is a restart within the postmaster. */ pgsp = NULL; pgsp_hash = NULL; /* Create or attach to the shared memory state, including hash table. */ LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); pgsp = ShmemInitStruct("pg_show_plans", sizeof(pgspSharedState), &found); if (!found) /* First time. */ { pgsp->lock = &(GetNamedLWLockTranche("pg_show_plans"))->lock; pgsp->is_enabled = start_enabled; pgsp->plan_format = plan_format; } memset(&info, 0, sizeof(info)); info.keysize = sizeof(pgspHashKey); info.entrysize = hash_entry_size(); info.hash = gen_hash_key; info.match = compare_hash_key; pgsp_hash = ShmemInitHash("pg_show_plans hash", MaxConnections, MaxConnections, &info, HASH_ELEM|HASH_FUNCTION|HASH_COMPARE); LWLockRelease(AddinShmemInitLock); } static void pgsp_ExecutorStart(QueryDesc *queryDesc, int eflags) { ExplainState *es; if (prev_ExecutorStart) prev_ExecutorStart(queryDesc, eflags); else standard_ExecutorStart(queryDesc, eflags); if (!ensure_cached()) { ereport(WARNING, errcode(ERRCODE_OUT_OF_MEMORY), errmsg("not enough memory to store new query plans")); return; } if (!pgsp->is_enabled) return; es = NewExplainState(); es->format = pgsp->plan_format; ExplainBeginOutput(es); ExplainPrintPlan(es, queryDesc); ExplainEndOutput(es); append_query_plan(es); pfree(es->str->data); } static void pgsp_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, uint64 count, bool execute_once) { nest_level++; PG_TRY(); { /* These functions return *after* the nested quries do. */ if (prev_ExecutorRun) prev_ExecutorRun(queryDesc, direction, count, execute_once); else standard_ExecutorRun(queryDesc, direction, count, execute_once); nest_level--; /* Wait for reading to complete, then delete. */ if (nest_level < 1) { /* Mark hash entry as empty. */ SpinLockAcquire(&pgsp_cache->mutex); pgsp_cache->n_plans = 0; SpinLockRelease(&pgsp_cache->mutex); } } PG_CATCH(); /* Since 13 PG_FINALLY() is available. */ { nest_level--; /* Wait for reading to complete, then delete. */ if (nest_level < 1) { /* Mark hash entry as empty. */ SpinLockAcquire(&pgsp_cache->mutex); pgsp_cache->n_plans = 0; SpinLockRelease(&pgsp_cache->mutex); } PG_RE_THROW(); } PG_END_TRY(); } Datum pg_show_plans(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; TupleDesc tupdesc; int call_cntr; int max_calls; int offset; int i; pgspCtx *pgvp_ctx; HASH_SEQ_STATUS *hash_seq; pgspEntry *pgvp_tmp_entry; int curr_nest; bool is_done; shmem_safety_check(); if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); LWLockAcquire(pgsp->lock, LW_SHARED); pgvp_ctx = (pgspCtx *)palloc(sizeof(pgspCtx)); pgvp_ctx->is_done = true; pgvp_ctx->curr_nest = 0; pgvp_ctx->hash_seq = (HASH_SEQ_STATUS *)palloc(sizeof(HASH_SEQ_STATUS)); hash_seq_init(pgvp_ctx->hash_seq, pgsp_hash); funcctx->user_fctx = (void *)pgvp_ctx; funcctx->max_calls = hash_get_num_entries(pgsp_hash); if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record") )); funcctx->tuple_desc = BlessTupleDesc(tupdesc); MemoryContextSwitchTo(oldcontext); } funcctx = SRF_PERCALL_SETUP(); /* Restore context. */ pgvp_ctx = (pgspCtx *)funcctx->user_fctx; hash_seq = pgvp_ctx->hash_seq; is_done = pgvp_ctx->is_done; pgvp_tmp_entry = pgvp_ctx->pgvp_tmp_entry; curr_nest = pgvp_ctx->curr_nest; /* Pull other stuff from `funcctx`. */ call_cntr = funcctx->call_cntr; max_calls = funcctx->max_calls; if (call_cntr < max_calls) { Datum values[5]; bool nulls[5]; HeapTuple htup; if (is_done) /* Done processing a hash entry? */ { /* Grab a new one. */ pgvp_tmp_entry = hash_seq_search(hash_seq); /* Skip empty entries and the ones the user is not * allowed to see. */ for (;;) { if (pgvp_tmp_entry->n_plans >= 1) { if (is_allowed_role()) break; else if (pgvp_tmp_entry->user_id == GetUserId()) break; } if (call_cntr == max_calls-1) { /* No more entries. */ hash_seq_term(hash_seq); LWLockRelease(pgsp->lock); SRF_RETURN_DONE(funcctx); } pgvp_tmp_entry = hash_seq_search(hash_seq); call_cntr++; } SpinLockAcquire(&pgvp_tmp_entry->mutex); } /* A single hash entry may store multiple (nested) plans, so * count offset to get the desired plan. */ offset = 0; for (i = 0; i < curr_nest; i++) offset += pgvp_tmp_entry->plan_len[i] + 1; MemSet(nulls, 0, sizeof(nulls)); values[0] = Int32GetDatum(pgvp_tmp_entry->hash_key.pid); values[1] = Int32GetDatum(curr_nest); values[2] = ObjectIdGetDatum(pgvp_tmp_entry->user_id); values[3] = ObjectIdGetDatum(pgvp_tmp_entry->db_id); values[4] = CStringGetTextDatum(pgvp_tmp_entry->plan + offset); htup = heap_form_tuple(funcctx->tuple_desc, values, nulls); if (curr_nest < pgvp_tmp_entry->n_plans-1) { /* Still have nested plans. */ curr_nest++; call_cntr--; /* May not be legal, but it works. */ is_done = false; } else { /* No more nested plans, get a new entry. */ curr_nest = 0; is_done = true; SpinLockRelease(&pgvp_tmp_entry->mutex); } /* Save values back to the context. */ pgvp_ctx->is_done = is_done; pgvp_ctx->curr_nest = curr_nest; pgvp_ctx->pgvp_tmp_entry = pgvp_tmp_entry; funcctx->call_cntr = call_cntr; SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(htup)); } else { hash_seq_term(hash_seq); LWLockRelease(pgsp->lock); SRF_RETURN_DONE(funcctx); } } pg_show_plans-2.0.2/pg_show_plans.control000066400000000000000000000002641451373303400206300ustar00rootroot00000000000000# pg_show_plans extension comment = 'show query plans of all currently running SQL statements' default_version = '2.0' module_pathname = '$libdir/pg_show_plans' relocatable = true pg_show_plans-2.0.2/pg_show_plans.md000077700000000000000000000000001451373303400210202README.mdustar00rootroot00000000000000pg_show_plans-2.0.2/sql/000077500000000000000000000000001451373303400151605ustar00rootroot00000000000000pg_show_plans-2.0.2/sql/formats.sql000066400000000000000000000010451451373303400173540ustar00rootroot00000000000000-- explain output on PG12/13 is missing "Async Capable" select setting::int < 140000 as pg12_13 from pg_settings where name = 'server_version_num'; -- json output set pg_show_plans.plan_format = 'json'; show pg_show_plans.plan_format; select * from nest(); -- yaml output set pg_show_plans.plan_format = 'yaml'; show pg_show_plans.plan_format; select * from nest(); -- xml output set pg_show_plans.plan_format = 'xml'; show pg_show_plans.plan_format; select * from nest(); -- check plan format after reconnect \c show pg_show_plans.plan_format; pg_show_plans-2.0.2/sql/pg_show_plans.sql000066400000000000000000000007451451373303400205520ustar00rootroot00000000000000create extension pg_show_plans; show pg_show_plans.is_enabled; show pg_show_plans.max_plan_length; create function nest() returns table (level int, plan text) language plpgsql as $$ begin return query select pg_show_plans.level, pg_show_plans.plan from pg_show_plans where pg_show_plans.level >= 0; end; $$; -- text output set pg_show_plans.plan_format = 'text'; show pg_show_plans.plan_format; select level, plan from pg_show_plans; select * from nest();