├── LICENCE.txt ├── Makefile ├── README.md ├── pg_stat_sql_plans--0.2.sql ├── pg_stat_sql_plans.c └── pg_stat_sql_plans.control /LICENCE.txt: -------------------------------------------------------------------------------- 1 | PG_STAT_SQL_PLANS 2 | Copyright (c) 2018, legrand_legrand 3 | 4 | Permission to use, copy, modify, and distribute this software and its 5 | documentation for any purpose, without fee, and without a written agreement 6 | is hereby granted, provided that the above copyright notice and this 7 | paragraph and the following two paragraphs appear in all copies. 8 | 9 | IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR DIRECT, 10 | INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST 11 | PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 12 | THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | 14 | THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT 15 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 16 | PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND 17 | THE COPYRIGHT HOLDER HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, 18 | UPDATES, ENHANCEMENTS, OR MODIFICATIONS. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # contrib/pg_stat_sql_plans/Makefile 2 | 3 | MODULE_big = pg_stat_sql_plans 4 | OBJS = pg_stat_sql_plans.o $(WIN32RES) 5 | 6 | EXTENSION = pg_stat_sql_plans 7 | DATA = pg_stat_sql_plans--0.2.sql 8 | PGFILEDESC = "pg_stat_sql_plans - execution statistics of SQL statements" 9 | 10 | LDFLAGS_SL += $(filter -lm, $(LIBS)) 11 | 12 | REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/pg_stat_sql_plans/pg_stat_sql_plans.conf 13 | REGRESS = pg_stat_sql_plans 14 | # Disabled because these tests require "shared_preload_libraries=pg_stat_sql_plans", 15 | # which typical installcheck users do not have (e.g. buildfarm clients). 16 | NO_INSTALLCHECK = 1 17 | 18 | ifdef USE_PGXS 19 | PG_CONFIG = pg_config 20 | PGXS := $(shell $(PG_CONFIG) --pgxs) 21 | include $(PGXS) 22 | else 23 | subdir = contrib/pg_stat_sql_plans 24 | top_builddir = ../.. 25 | include $(top_builddir)/src/Makefile.global 26 | include $(top_srcdir)/contrib/contrib-global.mk 27 | endif 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pg_stat_sql_plans 2 | pg_stat_sql_plans is a PostgreSQL extension created from pg_stat_statements adding a planid column 3 | generated from the hash value of the explain text. 4 | 5 | Alpha version, DO NOT USE IN PRODUCTION 6 | 7 | # Content: 8 | 9 | Customized version of pg_stat_statements, implementing many additionnal features: 10 | - queryid is based on normalized sql text (not parse tree jumbling), 11 | - stored query text is not normalized (but a SQL function is provided to do so), 12 | - planid is based on normalized explain text, 13 | - includes a specific 'minimal' explain module, for performances, 14 | - planid is build at planning time, making it reusable by cached plans, 15 | - explain text is saved in logs, 16 | - first_call, last_call informations are kept for each entry, 17 | - contains duration of queries that failed (timeout, error, cancelled, ...), 18 | - contains duration of planning, 19 | - expose current queryid, planid per pid in pg_stat_activity, 20 | - includes specific wait events for planning and extension activities, 21 | - ... 22 | 23 | Some ideas where found in other postgres extensions like pg_store_plans, pg_stat_plans 24 | auto_explain, pg_show_plans ... and patches from pgsql-hackers mailing list. 25 | 26 | 27 | # Prerequisites: 28 | Postgres version >= 14 (see other branch for pg11-12 and pg13 compatiblility) 29 | should be declared in postgresql.conf with shared_preload_libraries='pg_stat_sql_plans' 30 | and compute_query_id = off (to bypass core query_id computation) 31 | 32 | 33 | # View pg_stat_sql_plans definition: 34 | 35 | userid 36 | see pg_stat_statements 37 | 38 | dbid 39 | see pg_stat_statements 40 | 41 | qpid 42 | query plan id, combining (queryid,planid) values 43 | 44 | query 45 | text (not normalized, with constant values) of the first query for a qpid 46 | (queryid, planid). This text can be reused to generate explain plans or 47 | normalized with function pgssp_normalize_query(text) 48 | 49 | queryid 50 | hash value of normalized query text (using pgssp_normalize_query(text)) 51 | queryid is stable across different databases / environments, and doen't 52 | change after object recreation (after drop create or dump/restore) 53 | 54 | planid 55 | hash value of normalized plan text (using pgssp_normalize_query(text)) 56 | obtained with EXPLAIN 57 | costs OFF (for performances reasons), 58 | verbose OFF (may be changed in verbose ON to display objects schemas) 59 | Default values: 60 | 0 for utility statement (Optimisable one's like "CREATE TABLE AS" 61 | have a planid calculated and an explain plan like any other query) 62 | 1 when plan_type = 'none' 63 | 765585858645765476 when plan_type = 'standard' 64 | 65 | 66 | plans 67 | see pg_stat_statements 68 | 69 | calls 70 | see pg_stat_statements 71 | ... 72 | 73 | plan_time 74 | planning time (milli seconds) 75 | exec_time 76 | execution time as found in pg_statements extension. 77 | extn_time 78 | time spent by extension in pgssp_store function (including planid calculation) 79 | ... 80 | 81 | first_call 82 | first occurence date of the line 83 | last_call 84 | latest occurence date of the line 85 | 86 | # View pg_stat_sql_plans_agg definition: 87 | 88 | userid 89 | 90 | dbid 91 | 92 | queryid 93 | 94 | distinct_planid 95 | number of distinct planid for this query 96 | 97 | planids 98 | list of the planid for this query 99 | 100 | query 101 | 102 | plans 103 | number of planning executions 104 | 105 | calls 106 | number of executions 107 | 108 | total_time 109 | total time of planning + execution + extension 110 | 111 | plan_time 112 | exec_time 113 | extn_time 114 | 115 | average_time 116 | total_time / calls 117 | 118 | plan_avg_time 119 | plan_time / plans 120 | 121 | exec_avg_time 122 | exec_time / calls 123 | 124 | extn_avg_time 125 | extn_time / calls 126 | 127 | rows 128 | first_call 129 | last_call 130 | 131 | # Parameters (GUC): 132 | (*) means default value: 133 | 134 | pg_stat_sql_plans.explain true, false (*) 135 | write the plan in log file (as auto_explain) for each new (queryid,planid) 136 | 137 | pg_stat_sql_plans.max 5000 (*) 138 | 139 | pg_stat_sql_plans.plan_type none, standard (*) 140 | none: plan is not considered, planid=1 141 | standard: use native explain with costs OFF, verbose OFF, can be slow 142 | 143 | pg_stat_sql_plans.save true (*), false 144 | 145 | pg_stat_sql_plans.track top (*), all, none 146 | 147 | pg_stat_sql_plans.track_errors true (*), false 148 | include duration of failed queries (timeout, error, cancelled, ...) 149 | 150 | pg_stat_sql_plans.track_pid true (*), false 151 | enable, disable the result of pgssp_backend_qpid(pid), can only by changed at the db level 152 | using pg_relaod_conf(). 153 | 154 | pg_stat_sql_plans.track_utility true (*), false 155 | 156 | 157 | # Entries Eviction: 158 | based on oldest last_call date (to be sure to keep lastest recently used entries) 159 | a message is written in log file at each eviction pass like: 160 | "2018-11-13 22:16:36.421 CET [5904] LOG: pg_stat_sql_plans evicting 250 entries" 161 | 162 | 163 | # Additional Functions: 164 | - pg_stat_sql_plans_reset() 165 | to reset all entries. 166 | 167 | - pgssp_normalize_query(text) 168 | replace litérals and numerics per ? 169 | 170 | - pgssp_backend_qpid(pid) 171 | Returns last (nested) query plan id executing/executed by backend. 172 | It returns queryid value during planning, and planid calculation. 173 | This value does not reflect statements with syntax error (that are not parsed). 174 | Usefull to identify the query plan of a never ending query (without cancelling it) 175 | Also usefull for sampling wait events per queryid/planid, can be used to join 176 | pg_stat_activity with pg_stat_sql_plans (see exemple) 177 | 178 | returns 0 if no qpid found, -1 when trackin is disabled (pg_stat_sql_plans.track_pid = false) 179 | 180 | 181 | # Wait events: 182 | - extension event 183 | event_type-event_name "Extension"-"Extension" is displayed when 184 | spending time in pgssp_store function including: 185 | - time to store entry, 186 | - planid calculation (that can be long on table with many columns), 187 | - old entries eviction 188 | 189 | - planning event 190 | During planning event_type-event_name "Activity"-"unknown wait event" 191 | are displayed. 192 | 193 | 194 | # Examples 195 | - join pg_stat_activity with pg_stat_sql_plans on (dbid,userid, qpid) 196 | 197 | SELECT 198 | pgsa.datname, 199 | pgsa.pid, 200 | pgsa.usename, 201 | pgsa.application_name, 202 | pgsa.state, 203 | pgsa.query, 204 | coalesce(queryid,pgsa.qpid) queryid, 205 | pgssp.planid, 206 | pgssp_normalize_query(pgssp.query), 207 | pgssp.calls 208 | FROM 209 | (SELECT *, pgssp_backend_qpid(pid) qpid FROM pg_stat_activity) pgsa 210 | LEFT OUTER JOIN pg_stat_sql_plans pgssp 211 | ON pgsa.qpid = pgssp.qpid 212 | AND pgsa.datid = pgssp.dbid 213 | AND pgsa.usesysid = pgssp.userid 214 | WHERE pgsa.backend_type='client backend' 215 | AND pgsa.pid != pg_backend_pid() 216 | ; 217 | 218 | 219 | - sampling wait events pg_stat_activity per query plan id 220 | 221 | CREATE UNLOGGED TABLE mon AS 222 | SELECT pid,wait_event_type,wait_event,pgssp_backend_qpid(pid) AS qpid 223 | FROM pg_stat_activity 224 | WHERE 0=1 225 | ; 226 | 227 | DO $$ 228 | BEGIN 229 | LOOP 230 | INSERT INTO mon SELECT pid,wait_event_type,wait_event,pgssp_backend_qpid(pid) 231 | FROM pg_stat_activity 232 | WHERE state ='active' and pid != pg_backend_pid(); 233 | PERFORM pg_sleep(0.01); 234 | COMMIT; 235 | END LOOP; 236 | END; 237 | $$ 238 | ; 239 | -------------------------------------------------------------------------------- /pg_stat_sql_plans--0.2.sql: -------------------------------------------------------------------------------- 1 | /* contrib/pg_stat_sql_plans/pg_stat_sql_plans--0.2.sql */ 2 | 3 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 4 | \echo Use "CREATE EXTENSION pg_stat_sql_plans" to load this file. \quit 5 | 6 | -- Register functions. 7 | CREATE FUNCTION pg_stat_sql_plans_reset() 8 | RETURNS void 9 | AS 'MODULE_PATHNAME' 10 | LANGUAGE C PARALLEL SAFE; 11 | 12 | CREATE FUNCTION pgssp_normalize_query(text) 13 | RETURNS text 14 | AS 'MODULE_PATHNAME' 15 | LANGUAGE C 16 | RETURNS NULL ON NULL INPUT; 17 | 18 | CREATE FUNCTION pg_stat_sql_plans(IN showtext boolean, 19 | OUT userid oid, 20 | OUT dbid oid, 21 | OUT qpid int8, 22 | OUT query text, 23 | OUT queryid int8, 24 | OUT planid int8, 25 | OUT plans int8, 26 | OUT calls int8, 27 | OUT total_time float8, 28 | OUT min_time float8, 29 | OUT max_time float8, 30 | OUT mean_time float8, 31 | OUT stddev_time float8, 32 | OUT plan_time float8, 33 | OUT exec_time float8, 34 | OUT extn_time float8, 35 | OUT rows int8, 36 | OUT shared_blks_hit int8, 37 | OUT shared_blks_read int8, 38 | OUT shared_blks_dirtied int8, 39 | OUT shared_blks_written int8, 40 | OUT local_blks_hit int8, 41 | OUT local_blks_read int8, 42 | OUT local_blks_dirtied int8, 43 | OUT local_blks_written int8, 44 | OUT temp_blks_read int8, 45 | OUT temp_blks_written int8, 46 | OUT blk_read_time float8, 47 | OUT blk_write_time float8, 48 | OUT first_call timestamptz, 49 | OUT last_call timestamptz 50 | ) 51 | RETURNS SETOF record 52 | AS 'MODULE_PATHNAME', 'pg_stat_sql_plans_1_3' 53 | LANGUAGE C STRICT VOLATILE PARALLEL SAFE; 54 | 55 | -- Register a view on the function for ease of use. 56 | CREATE VIEW pg_stat_sql_plans AS 57 | SELECT 58 | * 59 | FROM pg_stat_sql_plans(true) 60 | ; 61 | 62 | create view public.pg_stat_sql_plans_agg as 63 | SELECT 64 | pgssp.userid, 65 | pgssp.dbid, 66 | pgssp.queryid, 67 | count(case when pgssp.planid != 0 then pgssp.planid end ) as distinct_planid, 68 | STRING_AGG(case when pgssp.planid != 0 then pgssp.planid::text end , ',') as planids, 69 | max(pgssp.query) query, 70 | sum(pgssp.plans) as plans, 71 | sum(pgssp.calls) as calls, 72 | sum(total_time) as total_time, 73 | sum(pgssp.plan_time) as plan_time, 74 | sum(pgssp.exec_time) as exec_time, 75 | sum(pgssp.extn_time) as extn_time, 76 | sum(total_time)/sum(pgssp.calls) as average_time, 77 | sum(pgssp.plan_time)/sum(case when pgssp.plans = 0 then 1 else pgssp.plans end ) as plan_avg_time, 78 | sum(pgssp.exec_time)/sum(pgssp.calls) as exec_avg_time, 79 | sum(pgssp.extn_time)/sum(pgssp.calls) as extn_avg_time, 80 | sum(pgssp.rows) as rows, 81 | min(pgssp.first_call) as first_call, 82 | max(pgssp.last_call) as last_call 83 | FROM 84 | public.pg_stat_sql_plans(true) pgssp 85 | WHERE 86 | pgssp.calls !=0 87 | GROUP BY 88 | pgssp.userid, 89 | pgssp.dbid, 90 | pgssp.queryid; 91 | 92 | GRANT SELECT ON pg_stat_sql_plans,pg_stat_sql_plans_agg TO PUBLIC; 93 | 94 | -- Don't want this to be available to non-superusers. 95 | REVOKE ALL ON FUNCTION pg_stat_sql_plans_reset() FROM PUBLIC; 96 | 97 | CREATE FUNCTION pgssp_backend_qpid(int) 98 | RETURNS int8 99 | AS 'MODULE_PATHNAME' 100 | LANGUAGE C 101 | RETURNS NULL ON NULL INPUT; 102 | -------------------------------------------------------------------------------- /pg_stat_sql_plans.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * pg_stat_sql_plans.c 4 | * Track statement execution times across a whole database cluster. 5 | * 6 | * Execution costs are totalled for each distinct source query, and kept in 7 | * a shared hashtable. (We track only as many distinct queries as will fit 8 | * in the designated amount of shared memory.) 9 | * 10 | * As of Postgres 9.2, this module normalizes query entries. Normalization 11 | * is a process whereby similar queries, typically differing only in their 12 | * constants (though the exact rules are somewhat more subtle than that) are 13 | * recognized as equivalent, and are tracked as a single entry. This is 14 | * particularly useful for non-prepared queries. 15 | * 16 | * To save on shared memory, and to avoid having to truncate oversized query 17 | * strings, we store these strings in a temporary external query-texts file. 18 | * Offsets into this file are kept in shared memory. 19 | * 20 | * Note about locking issues: to create or delete an entry in the shared 21 | * hashtable, one must hold pgssp->lock exclusively. Modifying any field 22 | * in an entry except the counters requires the same. To look up an entry, 23 | * one must hold the lock shared. To read or update the counters within 24 | * an entry, one must hold the lock shared or exclusive (so the entry doesn't 25 | * disappear!) and also take the entry's mutex spinlock. 26 | * The shared state variable pgssp->extent (the next free spot in the external 27 | * query-text file) should be accessed only while holding either the 28 | * pgssp->mutex spinlock, or exclusive lock on pgssp->lock. We use the mutex to 29 | * allow reserving file space while holding only shared lock on pgssp->lock. 30 | * Rewriting the entire external query-text file, eg for garbage collection, 31 | * requires holding pgssp->lock exclusively; this allows individual entries 32 | * in the file to be read or written while holding only shared lock. 33 | * 34 | * 35 | * Copyright (c) 2008-2018, PostgreSQL Global Development Group 36 | * 37 | * IDENTIFICATION 38 | * contrib/pg_stat_sql_plans/pg_stat_sql_plans.c 39 | * 40 | *------------------------------------------------------------------------- 41 | */ 42 | #include "postgres.h" 43 | 44 | #include 45 | #include 46 | #include 47 | 48 | #include "access/hash.h" 49 | #include "access/twophase.h" 50 | #include "catalog/pg_authid.h" 51 | #include "commands/explain.h" 52 | #include "executor/instrument.h" 53 | #include "funcapi.h" 54 | #include "mb/pg_wchar.h" 55 | #include "miscadmin.h" 56 | #include "optimizer/planner.h" 57 | #include "parser/analyze.h" 58 | #include "parser/parsetree.h" 59 | #include "parser/scanner.h" 60 | #include "parser/scansup.h" 61 | #include "parser/gram.h" 62 | #include "pgstat.h" 63 | #include "storage/fd.h" 64 | #include "storage/ipc.h" 65 | #include "storage/spin.h" 66 | #include "storage/proc.h" 67 | #include "tcop/utility.h" 68 | #include "utils/acl.h" 69 | #include "utils/builtins.h" 70 | #include "utils/guc.h" 71 | #include "utils/memutils.h" 72 | #include "utils/timestamp.h" 73 | 74 | 75 | PG_MODULE_MAGIC; 76 | 77 | /* Location of permanent stats file (valid when database is shut down) */ 78 | #define pgssp_DUMP_FILE PGSTAT_STAT_PERMANENT_DIRECTORY "/pg_stat_sql_plans.stat" 79 | 80 | /* 81 | * Location of external query text file. We don't keep it in the core 82 | * system's stats_temp_directory. The core system can safely use that GUC 83 | * setting, because the statistics collector temp file paths are set only once 84 | * as part of changing the GUC, but pg_stat_sql_plans has no way of avoiding 85 | * race conditions. Besides, we only expect modest, infrequent I/O for query 86 | * strings, so placing the file on a faster filesystem is not compelling. 87 | */ 88 | #define pgssp_TEXT_FILE PG_STAT_TMP_DIR "/pgssp_query_texts.stat" 89 | 90 | /* Magic number identifying the stats file format */ 91 | static const uint32 pgssp_FILE_HEADER = 0x20171004; 92 | 93 | /* PostgreSQL major version number, changes in which invalidate all entries */ 94 | static const uint32 pgssp_PG_MAJOR_VERSION = PG_VERSION_NUM / 100; 95 | 96 | /* XXX: Should USAGE_EXEC reflect execution time and/or buffer usage? */ 97 | //#define USAGE_EXEC(duration) (1.0) 98 | //#define USAGE_INIT (1.0) /* including initial planning */ 99 | #define ASSUMED_MEDIAN_INIT (10.0) /* initial assumed median usage */ 100 | #define ASSUMED_LENGTH_INIT 1024 /* initial assumed mean query length */ 101 | //#define USAGE_DECREASE_FACTOR (0.99) /* decreased every entry_dealloc */ 102 | //#define STICKY_DECREASE_FACTOR (0.50) /* factor for sticky entries */ 103 | #define USAGE_DEALLOC_PERCENT 5 /* free this % of entries at once */ 104 | 105 | /* 106 | * Extension version number, for supporting older extension versions' objects 107 | */ 108 | typedef enum pgsspVersion 109 | { 110 | pgssp_V1_0 = 0, 111 | pgssp_V1_1, 112 | pgssp_V1_2, 113 | pgssp_V1_3 114 | } pgsspVersion; 115 | 116 | /* 117 | * Hashtable key that defines the identity of a hashtable entry. We separate 118 | * queries by user and by database even if they are otherwise identical. 119 | * 120 | * Right now, this structure contains no padding. If you add any, make sure 121 | * to teach pgssp_store() to zero the padding bytes. Otherwise, things will 122 | * break, because pgssp_hash is created using HASH_BLOBS, and thus tag_hash 123 | * is used to hash this. 124 | */ 125 | typedef struct pgsspHashKey 126 | { 127 | Oid userid; /* user OID */ 128 | Oid dbid; /* database OID */ 129 | uint64 qpid; /* extension identifier, combination of queryid and planid */ 130 | } pgsspHashKey; 131 | 132 | /* 133 | * The actual stats counters kept within pgsspEntry. 134 | */ 135 | typedef struct Counters 136 | { 137 | uint64 queryid; /* query identifier */ 138 | uint64 planid; /* plan identifier */ 139 | int64 plans; /* # of times planned */ 140 | int64 calls; /* # of times executed */ 141 | double total_time; /* total execution time, in msec */ 142 | double min_time; /* minimum execution time in msec */ 143 | double max_time; /* maximum execution time in msec */ 144 | double mean_time; /* mean execution time in msec */ 145 | double sum_var_time; /* sum of variances in execution time in msec */ 146 | double plan_time; /* total planing time, in msec */ 147 | double exec_time; /* total execution time, in msec */ 148 | double extn_time; /* total pgssp time, in msec */ 149 | int64 rows; /* total # of retrieved or affected rows */ 150 | int64 shared_blks_hit; /* # of shared buffer hits */ 151 | int64 shared_blks_read; /* # of shared disk blocks read */ 152 | int64 shared_blks_dirtied; /* # of shared disk blocks dirtied */ 153 | int64 shared_blks_written; /* # of shared disk blocks written */ 154 | int64 local_blks_hit; /* # of local buffer hits */ 155 | int64 local_blks_read; /* # of local disk blocks read */ 156 | int64 local_blks_dirtied; /* # of local disk blocks dirtied */ 157 | int64 local_blks_written; /* # of local disk blocks written */ 158 | int64 temp_blks_read; /* # of temp blocks read */ 159 | int64 temp_blks_written; /* # of temp blocks written */ 160 | double blk_read_time; /* time spent reading, in msec */ 161 | double blk_write_time; /* time spent writing, in msec */ 162 | TimestampTz first_call; /* timestamp of first call */ 163 | TimestampTz last_call; /* timestamp of last call */ 164 | } Counters; 165 | 166 | /* 167 | * Statistics per statement 168 | * 169 | * Note: in event of a failure in garbage collection of the query text file, 170 | * we reset query_offset to zero and query_len to -1. This will be seen as 171 | * an invalid state by qtext_fetch(). 172 | */ 173 | typedef struct pgsspEntry 174 | { 175 | pgsspHashKey key; /* hash key of entry - MUST BE FIRST */ 176 | Counters counters; /* the statistics for this query */ 177 | Size query_offset; /* query text offset in external file */ 178 | int query_len; /* # of valid bytes in query string, or -1 */ 179 | int encoding; /* query text encoding */ 180 | slock_t mutex; /* protects the counters only */ 181 | } pgsspEntry; 182 | 183 | /* 184 | * Global shared state 185 | */ 186 | typedef struct pgsspSharedState 187 | { 188 | LWLock *lock; /* protects hashtable search/modification */ 189 | double cur_median_usage; /* current median usage in hashtable */ 190 | Size mean_query_len; /* current mean entry text length */ 191 | slock_t mutex; /* protects following fields only: */ 192 | Size extent; /* current extent of query file */ 193 | int n_writers; /* number of active writers to query file */ 194 | int gc_count; /* query file garbage collection cycle count */ 195 | } pgsspSharedState; 196 | 197 | /* 198 | * Struct for tracking locations/lengths of constants during normalization 199 | */ 200 | typedef struct pgsspLocationLen 201 | { 202 | int location; /* start offset in query text */ 203 | int length; /* length in bytes, or -1 to ignore */ 204 | } pgsspLocationLen; 205 | 206 | /* get max procs */ 207 | static int get_max_procs_count(void); 208 | 209 | /* Proc entry */ 210 | typedef struct procEntry 211 | { 212 | uint64 qpid; 213 | } procEntry; 214 | /*---- Local variables ----*/ 215 | 216 | /* Current nesting depth of ExecutorRun+ProcessUtility calls */ 217 | static int nested_level = 0; 218 | 219 | /* Saved hook values in case of unload */ 220 | static shmem_startup_hook_type prev_shmem_startup_hook = NULL; 221 | static planner_hook_type prev_planner_hook = NULL; 222 | static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL; 223 | static ExecutorStart_hook_type prev_ExecutorStart = NULL; 224 | static ExecutorRun_hook_type prev_ExecutorRun = NULL; 225 | static ExecutorFinish_hook_type prev_ExecutorFinish = NULL; 226 | static ExecutorEnd_hook_type prev_ExecutorEnd = NULL; 227 | static ProcessUtility_hook_type prev_ProcessUtility = NULL; 228 | 229 | /* Links to shared memory state */ 230 | static pgsspSharedState *pgssp = NULL; 231 | static HTAB *pgssp_hash = NULL; 232 | 233 | /*---- GUC variables ----*/ 234 | 235 | typedef enum 236 | { 237 | pgssp_TRACK_NONE, /* track no statements */ 238 | pgssp_TRACK_TOP, /* only top level statements */ 239 | pgssp_TRACK_ALL /* all statements, including nested ones */ 240 | } pgsspTrackLevel; 241 | 242 | static const struct config_enum_entry track_options[] = 243 | { 244 | {"none", pgssp_TRACK_NONE, false}, 245 | {"top", pgssp_TRACK_TOP, false}, 246 | {"all", pgssp_TRACK_ALL, false}, 247 | {NULL, 0, false} 248 | }; 249 | 250 | typedef enum 251 | { 252 | pgssp_PLAN_NONE, /* track no plan*/ 253 | pgssp_PLAN_STD /* track explain plans (costs off) */ 254 | } pgsspPlanType; 255 | 256 | static const struct config_enum_entry plan_type_options[] = 257 | { 258 | {"none", pgssp_PLAN_NONE, false}, 259 | {"standard", pgssp_PLAN_STD, false}, 260 | {NULL, 0, false} 261 | }; 262 | 263 | static int pgssp_max; /* max # statements to track */ 264 | static int pgssp_track; /* tracking level */ 265 | static bool pgssp_track_utility; /* whether to track utility commands */ 266 | static bool pgssp_track_errors; /* whether to track statements in error */ 267 | static int pgssp_plan_type; /* how to track plan id */ 268 | static bool pgssp_explain; /* whether to explain query */ 269 | static bool pgssp_track_pid; /* whether to track collected data per pid */ 270 | static bool pgssp_save; /* whether to save stats across shutdown */ 271 | 272 | 273 | #define pgssp_enabled() \ 274 | (pgssp_track == pgssp_TRACK_ALL || \ 275 | (pgssp_track == pgssp_TRACK_TOP && nested_level == 0)) 276 | 277 | #define record_gc_qtexts() \ 278 | do { \ 279 | volatile pgsspSharedState *s = (volatile pgsspSharedState *) pgssp; \ 280 | SpinLockAcquire(&s->mutex); \ 281 | s->gc_count++; \ 282 | SpinLockRelease(&s->mutex); \ 283 | } while(0) 284 | 285 | /*---- Function declarations ----*/ 286 | 287 | void _PG_init(void); 288 | void _PG_fini(void); 289 | 290 | PG_FUNCTION_INFO_V1(pg_stat_sql_plans_reset); 291 | PG_FUNCTION_INFO_V1(pg_stat_sql_plans_1_2); 292 | PG_FUNCTION_INFO_V1(pg_stat_sql_plans_1_3); 293 | PG_FUNCTION_INFO_V1(pg_stat_sql_plans); 294 | PG_FUNCTION_INFO_V1(pgssp_normalize_query); 295 | PG_FUNCTION_INFO_V1(pgssp_backend_qpid); 296 | 297 | 298 | static void pgssp_shmem_startup(void); 299 | static void pgssp_shmem_shutdown(int code, Datum arg); 300 | static PlannedStmt *pgssp_planner(Query *parse, 301 | const char *query_string, 302 | int cursorOptions, 303 | ParamListInfo boundParams); 304 | static void pgssp_post_parse_analyze(ParseState *pstate, Query *query, 305 | JumbleState *jstate); 306 | static void pgssp_ExecutorStart(QueryDesc *queryDesc, int eflags); 307 | static void pgssp_ExecutorRun(QueryDesc *queryDesc, 308 | ScanDirection direction, 309 | uint64 count, bool execute_once); 310 | static void pgssp_ExecutorFinish(QueryDesc *queryDesc); 311 | static void pgssp_ExecutorEnd(QueryDesc *queryDesc); 312 | static void pgssp_ProcessUtility(PlannedStmt *pstmt, const char *queryString, 313 | bool readOnlyTree, 314 | ProcessUtilityContext context, ParamListInfo params, 315 | QueryEnvironment *queryEnv, 316 | DestReceiver *dest, QueryCompletion *qc); 317 | static uint64 pgssp_hash_string(const char *str, int len); 318 | static void pgssp_store(const char *query, uint64 queryId, 319 | PlannedStmt *plan, ParamListInfo params, 320 | int query_location, int query_len, 321 | double total_time, uint64 rows, 322 | const BufferUsage *bufusage); 323 | static void pg_stat_sql_plans_internal(FunctionCallInfo fcinfo, 324 | pgsspVersion api_version, 325 | bool showtext); 326 | static Size pgssp_memsize(void); 327 | static pgsspEntry *entry_alloc(pgsspHashKey *key, Size query_offset, int query_len, 328 | int encoding); 329 | static void entry_dealloc(void); 330 | static bool qtext_store(const char *query, int query_len, 331 | Size *query_offset, int *gc_count); 332 | static char *qtext_load_file(Size *buffer_size); 333 | static char *qtext_fetch(Size query_offset, int query_len, 334 | char *buffer, Size buffer_size); 335 | static bool need_gc_qtexts(void); 336 | static void gc_qtexts(void); 337 | static void entry_reset(void); 338 | void normalize_expr(char *expr, bool preserve_space); 339 | static uint64 hash_query(const char* query); 340 | static procEntry *ProcEntryArray = NULL; 341 | 342 | 343 | 344 | /* 345 | * Module load callback 346 | */ 347 | void 348 | _PG_init(void) 349 | { 350 | /* 351 | * In order to create our shared memory area, we have to be loaded via 352 | * shared_preload_libraries. If not, fall out without hooking into any of 353 | * the main system. (We don't throw error here because it seems useful to 354 | * allow the pg_stat_sql_plans functions to be created even when the 355 | * module isn't active. The functions must protect themselves against 356 | * being called then, however.) 357 | */ 358 | if (!process_shared_preload_libraries_in_progress) 359 | return; 360 | 361 | /* 362 | * Define (or redefine) custom GUC variables. 363 | */ 364 | DefineCustomIntVariable("pg_stat_sql_plans.max", 365 | "Sets the maximum number of statements tracked by pg_stat_sql_plans.", 366 | NULL, 367 | &pgssp_max, 368 | 5000, 369 | 100, 370 | INT_MAX, 371 | PGC_POSTMASTER, 372 | 0, 373 | NULL, 374 | NULL, 375 | NULL); 376 | 377 | DefineCustomEnumVariable("pg_stat_sql_plans.track", 378 | "Selects which statements are tracked by pg_stat_sql_plans.", 379 | NULL, 380 | &pgssp_track, 381 | pgssp_TRACK_TOP, 382 | track_options, 383 | PGC_SUSET, 384 | 0, 385 | NULL, 386 | NULL, 387 | NULL); 388 | 389 | DefineCustomBoolVariable("pg_stat_sql_plans.track_utility", 390 | "Selects whether utility commands are tracked by pg_stat_sql_plans.", 391 | NULL, 392 | &pgssp_track_utility, 393 | true, 394 | PGC_SUSET, 395 | 0, 396 | NULL, 397 | NULL, 398 | NULL); 399 | 400 | DefineCustomBoolVariable("pg_stat_sql_plans.track_errors", 401 | "Selects whether statements in error are tracked by pg_stat_sql_plans.", 402 | NULL, 403 | &pgssp_track_errors, 404 | true, 405 | PGC_SUSET, 406 | 0, 407 | NULL, 408 | NULL, 409 | NULL); 410 | 411 | DefineCustomEnumVariable("pg_stat_sql_plans.plan_type", 412 | "Selects which type of explain plan is used by pg_stat_sql_plans.", 413 | NULL, 414 | &pgssp_plan_type, 415 | pgssp_PLAN_STD, 416 | plan_type_options, 417 | PGC_SUSET, 418 | 0, 419 | NULL, 420 | NULL, 421 | NULL); 422 | 423 | DefineCustomBoolVariable("pg_stat_sql_plans.explain", 424 | "Selects whether explain query by pg_stat_sql_plans.", 425 | NULL, 426 | &pgssp_explain, 427 | false, 428 | PGC_SUSET, 429 | 0, 430 | NULL, 431 | NULL, 432 | NULL); 433 | 434 | DefineCustomBoolVariable("pg_stat_sql_plans.track_pid", 435 | "Selects whether data by pid are collected by pg_stat_sql_plans.", 436 | NULL, 437 | &pgssp_track_pid, 438 | true, 439 | PGC_SIGHUP, 440 | 0, 441 | NULL, 442 | NULL, 443 | NULL); 444 | 445 | DefineCustomBoolVariable("pg_stat_sql_plans.save", 446 | "Save pg_stat_sql_plans statistics across server shutdowns.", 447 | NULL, 448 | &pgssp_save, 449 | true, 450 | PGC_SIGHUP, 451 | 0, 452 | NULL, 453 | NULL, 454 | NULL); 455 | 456 | EmitWarningsOnPlaceholders("pg_stat_sql_plans"); 457 | 458 | /* 459 | * Request additional shared resources. (These are no-ops if we're not in 460 | * the postmaster process.) We'll allocate or attach to the shared 461 | * resources in pgssp_shmem_startup(). 462 | */ 463 | RequestAddinShmemSpace(pgssp_memsize()); 464 | RequestNamedLWLockTranche("pg_stat_sql_plans", 1); 465 | 466 | /* 467 | * Install hooks. 468 | */ 469 | prev_shmem_startup_hook = shmem_startup_hook; 470 | shmem_startup_hook = pgssp_shmem_startup; 471 | prev_planner_hook = planner_hook; 472 | planner_hook = pgssp_planner; 473 | prev_post_parse_analyze_hook = post_parse_analyze_hook; 474 | post_parse_analyze_hook = pgssp_post_parse_analyze; 475 | prev_ExecutorStart = ExecutorStart_hook; 476 | ExecutorStart_hook = pgssp_ExecutorStart; 477 | prev_ExecutorRun = ExecutorRun_hook; 478 | ExecutorRun_hook = pgssp_ExecutorRun; 479 | prev_ExecutorFinish = ExecutorFinish_hook; 480 | ExecutorFinish_hook = pgssp_ExecutorFinish; 481 | prev_ExecutorEnd = ExecutorEnd_hook; 482 | ExecutorEnd_hook = pgssp_ExecutorEnd; 483 | prev_ProcessUtility = ProcessUtility_hook; 484 | ProcessUtility_hook = pgssp_ProcessUtility; 485 | } 486 | 487 | /* 488 | * Module unload callback 489 | */ 490 | void 491 | _PG_fini(void) 492 | { 493 | /* Uninstall hooks. */ 494 | shmem_startup_hook = prev_shmem_startup_hook; 495 | planner_hook = prev_planner_hook; 496 | post_parse_analyze_hook = prev_post_parse_analyze_hook; 497 | ExecutorStart_hook = prev_ExecutorStart; 498 | ExecutorRun_hook = prev_ExecutorRun; 499 | ExecutorFinish_hook = prev_ExecutorFinish; 500 | ExecutorEnd_hook = prev_ExecutorEnd; 501 | ProcessUtility_hook = prev_ProcessUtility; 502 | } 503 | 504 | /* 505 | * shmem_startup hook: allocate or attach to shared memory, 506 | * then load any pre-existing statistics from file. 507 | * Also create and load the query-texts file, which is expected to exist 508 | * (even if empty) while the module is enabled. 509 | */ 510 | static void 511 | pgssp_shmem_startup(void) 512 | { 513 | bool found; 514 | HASHCTL info; 515 | FILE *file = NULL; 516 | FILE *qfile = NULL; 517 | uint32 header; 518 | int32 num; 519 | int32 pgver; 520 | int32 i; 521 | int buffer_size; 522 | char *buffer = NULL; 523 | int size; 524 | 525 | if (prev_shmem_startup_hook) 526 | prev_shmem_startup_hook(); 527 | 528 | /* reset in case this is a restart within the postmaster */ 529 | pgssp = NULL; 530 | pgssp_hash = NULL; 531 | 532 | /* 533 | * Create or attach to the shared memory state, including hash table 534 | */ 535 | LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); 536 | 537 | /* spécific for ProcEntryArray */ 538 | size = mul_size(sizeof(procEntry), get_max_procs_count()); 539 | ProcEntryArray = (procEntry *) ShmemInitStruct("Proc Entry Array", size, &found); 540 | if (!found) 541 | { 542 | MemSet(ProcEntryArray, 0, size); 543 | } 544 | 545 | pgssp = ShmemInitStruct("pg_stat_sql_plans", 546 | sizeof(pgsspSharedState), 547 | &found); 548 | 549 | if (!found) 550 | { 551 | /* First time through ... */ 552 | pgssp->lock = &(GetNamedLWLockTranche("pg_stat_sql_plans"))->lock; 553 | pgssp->cur_median_usage = ASSUMED_MEDIAN_INIT; 554 | pgssp->mean_query_len = ASSUMED_LENGTH_INIT; 555 | SpinLockInit(&pgssp->mutex); 556 | pgssp->extent = 0; 557 | pgssp->n_writers = 0; 558 | pgssp->gc_count = 0; 559 | } 560 | 561 | memset(&info, 0, sizeof(info)); 562 | info.keysize = sizeof(pgsspHashKey); 563 | info.entrysize = sizeof(pgsspEntry); 564 | pgssp_hash = ShmemInitHash("pg_stat_sql_plans hash", 565 | pgssp_max, pgssp_max, 566 | &info, 567 | HASH_ELEM | HASH_BLOBS); 568 | 569 | LWLockRelease(AddinShmemInitLock); 570 | 571 | /* 572 | * If we're in the postmaster (or a standalone backend...), set up a shmem 573 | * exit hook to dump the statistics to disk. 574 | */ 575 | if (!IsUnderPostmaster) 576 | on_shmem_exit(pgssp_shmem_shutdown, (Datum) 0); 577 | 578 | /* 579 | * Done if some other process already completed our initialization. 580 | */ 581 | if (found) 582 | return; 583 | 584 | /* 585 | * Note: we don't bother with locks here, because there should be no other 586 | * processes running when this code is reached. 587 | */ 588 | 589 | /* Unlink query text file possibly left over from crash */ 590 | unlink(pgssp_TEXT_FILE); 591 | 592 | /* Allocate new query text temp file */ 593 | qfile = AllocateFile(pgssp_TEXT_FILE, PG_BINARY_W); 594 | if (qfile == NULL) 595 | goto write_error; 596 | 597 | /* 598 | * If we were told not to load old statistics, we're done. (Note we do 599 | * not try to unlink any old dump file in this case. This seems a bit 600 | * questionable but it's the historical behavior.) 601 | */ 602 | if (!pgssp_save) 603 | { 604 | FreeFile(qfile); 605 | return; 606 | } 607 | 608 | /* 609 | * Attempt to load old statistics from the dump file. 610 | */ 611 | file = AllocateFile(pgssp_DUMP_FILE, PG_BINARY_R); 612 | if (file == NULL) 613 | { 614 | if (errno != ENOENT) 615 | goto read_error; 616 | /* No existing persisted stats file, so we're done */ 617 | FreeFile(qfile); 618 | return; 619 | } 620 | 621 | buffer_size = 2048; 622 | buffer = (char *) palloc(buffer_size); 623 | 624 | if (fread(&header, sizeof(uint32), 1, file) != 1 || 625 | fread(&pgver, sizeof(uint32), 1, file) != 1 || 626 | fread(&num, sizeof(int32), 1, file) != 1) 627 | goto read_error; 628 | 629 | if (header != pgssp_FILE_HEADER || 630 | pgver != pgssp_PG_MAJOR_VERSION) 631 | goto data_error; 632 | 633 | for (i = 0; i < num; i++) 634 | { 635 | pgsspEntry temp; 636 | pgsspEntry *entry; 637 | Size query_offset; 638 | 639 | if (fread(&temp, sizeof(pgsspEntry), 1, file) != 1) 640 | goto read_error; 641 | 642 | /* Encoding is the only field we can easily sanity-check */ 643 | if (!PG_VALID_BE_ENCODING(temp.encoding)) 644 | goto data_error; 645 | 646 | /* Resize buffer as needed */ 647 | if (temp.query_len >= buffer_size) 648 | { 649 | buffer_size = Max(buffer_size * 2, temp.query_len + 1); 650 | buffer = repalloc(buffer, buffer_size); 651 | } 652 | 653 | if (fread(buffer, 1, temp.query_len + 1, file) != temp.query_len + 1) 654 | goto read_error; 655 | 656 | /* Should have a trailing null, but let's make sure */ 657 | buffer[temp.query_len] = '\0'; 658 | 659 | /* Skip loading "sticky" entries */ 660 | if (temp.counters.calls == 0) 661 | continue; 662 | 663 | /* Store the query text */ 664 | query_offset = pgssp->extent; 665 | if (fwrite(buffer, 1, temp.query_len + 1, qfile) != temp.query_len + 1) 666 | goto write_error; 667 | pgssp->extent += temp.query_len + 1; 668 | 669 | /* make the hashtable entry (discards old entries if too many) */ 670 | entry = entry_alloc(&temp.key, query_offset, temp.query_len, 671 | temp.encoding); 672 | 673 | /* copy in the actual stats */ 674 | entry->counters = temp.counters; 675 | } 676 | 677 | pfree(buffer); 678 | FreeFile(file); 679 | FreeFile(qfile); 680 | 681 | /* 682 | * Remove the persisted stats file so it's not included in 683 | * backups/replication slaves, etc. A new file will be written on next 684 | * shutdown. 685 | * 686 | * Note: it's okay if the pgssp_TEXT_FILE is included in a basebackup, 687 | * because we remove that file on startup; it acts inversely to 688 | * pgssp_DUMP_FILE, in that it is only supposed to be around when the 689 | * server is running, whereas pgssp_DUMP_FILE is only supposed to be around 690 | * when the server is not running. Leaving the file creates no danger of 691 | * a newly restored database having a spurious record of execution costs, 692 | * which is what we're really concerned about here. 693 | */ 694 | unlink(pgssp_DUMP_FILE); 695 | 696 | return; 697 | 698 | read_error: 699 | ereport(LOG, 700 | (errcode_for_file_access(), 701 | errmsg("could not read pg_stat_statement file \"%s\": %m", 702 | pgssp_DUMP_FILE))); 703 | goto fail; 704 | data_error: 705 | ereport(LOG, 706 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 707 | errmsg("ignoring invalid data in pg_stat_statement file \"%s\"", 708 | pgssp_DUMP_FILE))); 709 | goto fail; 710 | write_error: 711 | ereport(LOG, 712 | (errcode_for_file_access(), 713 | errmsg("could not write pg_stat_statement file \"%s\": %m", 714 | pgssp_TEXT_FILE))); 715 | fail: 716 | if (buffer) 717 | pfree(buffer); 718 | if (file) 719 | FreeFile(file); 720 | if (qfile) 721 | FreeFile(qfile); 722 | /* If possible, throw away the bogus file; ignore any error */ 723 | unlink(pgssp_DUMP_FILE); 724 | 725 | /* 726 | * Don't unlink pgssp_TEXT_FILE here; it should always be around while the 727 | * server is running with pg_stat_sql_plans enabled 728 | */ 729 | } 730 | 731 | /* 732 | * shmem_shutdown hook: Dump statistics into file. 733 | * 734 | * Note: we don't bother with acquiring lock, because there should be no 735 | * other processes running when this is called. 736 | */ 737 | static void 738 | pgssp_shmem_shutdown(int code, Datum arg) 739 | { 740 | FILE *file; 741 | char *qbuffer = NULL; 742 | Size qbuffer_size = 0; 743 | HASH_SEQ_STATUS hash_seq; 744 | int32 num_entries; 745 | pgsspEntry *entry; 746 | 747 | /* Don't try to dump during a crash. */ 748 | if (code) 749 | return; 750 | 751 | /* Safety check ... shouldn't get here unless shmem is set up. */ 752 | if (!pgssp || !pgssp_hash) 753 | return; 754 | 755 | /* Don't dump if told not to. */ 756 | if (!pgssp_save) 757 | return; 758 | 759 | file = AllocateFile(pgssp_DUMP_FILE ".tmp", PG_BINARY_W); 760 | if (file == NULL) 761 | goto error; 762 | 763 | if (fwrite(&pgssp_FILE_HEADER, sizeof(uint32), 1, file) != 1) 764 | goto error; 765 | if (fwrite(&pgssp_PG_MAJOR_VERSION, sizeof(uint32), 1, file) != 1) 766 | goto error; 767 | num_entries = hash_get_num_entries(pgssp_hash); 768 | if (fwrite(&num_entries, sizeof(int32), 1, file) != 1) 769 | goto error; 770 | 771 | qbuffer = qtext_load_file(&qbuffer_size); 772 | if (qbuffer == NULL) 773 | goto error; 774 | 775 | /* 776 | * When serializing to disk, we store query texts immediately after their 777 | * entry data. Any orphaned query texts are thereby excluded. 778 | */ 779 | hash_seq_init(&hash_seq, pgssp_hash); 780 | while ((entry = hash_seq_search(&hash_seq)) != NULL) 781 | { 782 | int len = entry->query_len; 783 | char *qstr = qtext_fetch(entry->query_offset, len, 784 | qbuffer, qbuffer_size); 785 | 786 | if (qstr == NULL) 787 | qstr = ""; 788 | 789 | 790 | if (fwrite(entry, sizeof(pgsspEntry), 1, file) != 1 || 791 | fwrite(qstr, 1, len + 1, file) != len + 1) 792 | { 793 | /* note: we assume hash_seq_term won't change errno */ 794 | hash_seq_term(&hash_seq); 795 | goto error; 796 | } 797 | } 798 | 799 | free(qbuffer); 800 | qbuffer = NULL; 801 | 802 | if (FreeFile(file)) 803 | { 804 | file = NULL; 805 | goto error; 806 | } 807 | 808 | /* 809 | * Rename file into place, so we atomically replace any old one. 810 | */ 811 | (void) durable_rename(pgssp_DUMP_FILE ".tmp", pgssp_DUMP_FILE, LOG); 812 | 813 | /* Unlink query-texts file; it's not needed while shutdown */ 814 | unlink(pgssp_TEXT_FILE); 815 | 816 | return; 817 | 818 | error: 819 | ereport(LOG, 820 | (errcode_for_file_access(), 821 | errmsg("could not write pg_stat_statement file \"%s\": %m", 822 | pgssp_DUMP_FILE ".tmp"))); 823 | if (qbuffer) 824 | free(qbuffer); 825 | if (file) 826 | FreeFile(file); 827 | unlink(pgssp_DUMP_FILE ".tmp"); 828 | unlink(pgssp_TEXT_FILE); 829 | } 830 | 831 | /* 832 | * Calculate max processes count. 833 | */ 834 | static int 835 | get_max_procs_count(void) 836 | { 837 | int count = 0; 838 | 839 | /* MyProcs, including autovacuum workers and launcher */ 840 | count += MaxBackends; 841 | /* AuxiliaryProcs */ 842 | count += NUM_AUXILIARY_PROCS; 843 | /* Prepared xacts */ 844 | count += max_prepared_xacts; 845 | 846 | return count; 847 | } 848 | 849 | /* 850 | * Post-parse-analysis hook: mark query with a queryId 851 | */ 852 | static void 853 | pgssp_post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate) 854 | { 855 | if (prev_post_parse_analyze_hook) 856 | prev_post_parse_analyze_hook(pstate, query, jstate); 857 | 858 | /* Assert we didn't do this already */ 859 | Assert(query->queryId == UINT64CONST(0)); 860 | 861 | /* Safety check... */ 862 | if (!pgssp || !pgssp_hash || !pgssp_enabled() ) 863 | return; 864 | 865 | /* Update memory structure dedicated for pgssp_backend_qpid function */ 866 | if (MyProc && pgssp_track_pid ) 867 | { 868 | int i = MyProc - ProcGlobal->allProcs; 869 | const char *querytext = pstate->p_sourcetext; 870 | int query_len; 871 | int query_location = query->stmt_location; 872 | query_len = query->stmt_len; 873 | 874 | if (query_location >= 0) 875 | { 876 | Assert(query_location <= strlen(querytext)); 877 | querytext += query_location; 878 | /* Length of 0 (or -1) means "rest of string" */ 879 | if (query_len <= 0) 880 | query_len = strlen(querytext); 881 | else 882 | Assert(query_len <= strlen(querytext)); 883 | } 884 | else 885 | { 886 | /* If query location is unknown, distrust query_len as well */ 887 | query_location = 0; 888 | query_len = strlen(querytext); 889 | } 890 | 891 | /* 892 | * Discard leading and trailing whitespace, too. Use scanner_isspace() 893 | * not libc's isspace(), because we want to match the lexer's behavior. 894 | */ 895 | while (query_len > 0 && scanner_isspace(querytext[0])) 896 | querytext++, query_location++, query_len--; 897 | while (query_len > 0 && scanner_isspace(querytext[query_len - 1])) 898 | query_len--; 899 | 900 | /* store queryid based on utility statement text 901 | * planned statements are updated during executor start hook 902 | * after planid calculation in planner hook 903 | */ 904 | if (query->utilityStmt) { 905 | ProcEntryArray[i].qpid = hash_combine64(pgssp_hash_string(querytext, query_len),UINT64CONST(0)); 906 | } else { 907 | /* init with queryId value for planning phase */ 908 | ProcEntryArray[i].qpid = hash_query(pstate->p_sourcetext); 909 | } 910 | } 911 | 912 | /* 913 | * Utility statements get queryId zero. We do this even in cases where 914 | * the statement contains an optimizable statement for which a queryId 915 | * could be derived (such as EXPLAIN or DECLARE CURSOR). For such cases, 916 | * runtime control will first go through ProcessUtility and then the 917 | * executor, and we don't want the executor hooks to do anything, since we 918 | * are already measuring the statement's costs at the utility level. 919 | */ 920 | if (query->utilityStmt) 921 | { 922 | query->queryId = UINT64CONST(0); 923 | return; 924 | } 925 | 926 | /* Compute query ID and mark the Query node with it */ 927 | query->queryId = hash_query(pstate->p_sourcetext); 928 | 929 | /* 930 | * If we are unlucky enough to get a hash of zero, use 1 instead, to 931 | * prevent confusion with the utility-statement case. 932 | */ 933 | if (query->queryId == UINT64CONST(0)) 934 | query->queryId = UINT64CONST(1); 935 | 936 | } 937 | 938 | /* 939 | * planner hook 940 | */ 941 | 942 | static PlannedStmt * 943 | pgssp_planner(Query *parse, 944 | const char *query_string, 945 | int cursorOptions, 946 | ParamListInfo boundParams) 947 | { 948 | PlannedStmt *result; 949 | 950 | 951 | if (pgssp_enabled()) 952 | { 953 | instr_time start; 954 | instr_time duration; 955 | BufferUsage bufusage; 956 | 957 | // pgstat_report_wait_start(0x0B010000U); // gives ???-unknown wait event 958 | pgstat_report_wait_start(0x050E0000U); // gives Activity-unknown wait event 959 | INSTR_TIME_SET_CURRENT(start); 960 | 961 | nested_level++; 962 | PG_TRY(); 963 | { 964 | if (prev_planner_hook) 965 | result = prev_planner_hook(parse, query_string, cursorOptions, boundParams); 966 | else 967 | result = standard_planner(parse, query_string, cursorOptions, boundParams); 968 | nested_level--; 969 | } 970 | PG_CATCH(); 971 | { 972 | nested_level--; 973 | PG_RE_THROW(); 974 | } 975 | PG_END_TRY(); 976 | 977 | INSTR_TIME_SET_CURRENT(duration); 978 | INSTR_TIME_SUBTRACT(duration, start); 979 | 980 | bufusage.shared_blks_hit = 0; 981 | bufusage.shared_blks_read = 0; 982 | bufusage.shared_blks_dirtied = 0; 983 | bufusage.shared_blks_written = 0; 984 | bufusage.local_blks_hit = 0; 985 | bufusage.local_blks_read = 0; 986 | bufusage.local_blks_dirtied = 0; 987 | bufusage.local_blks_written = 0; 988 | bufusage.temp_blks_read = 0; 989 | bufusage.temp_blks_written = 0; 990 | //TODO 991 | // INSTR_TIME_SUBTRACT(bufusage.blk_read_time, bufusage.blk_read_time); 992 | // INSTR_TIME_SUBTRACT(bufusage.blk_write_time, bufusage.blk_write_time); 993 | 994 | 995 | pgssp_store( query_string, 996 | result->queryId, 997 | result, 998 | boundParams, 999 | result->stmt_location, 1000 | result->stmt_len, 1001 | INSTR_TIME_GET_MILLISEC(duration), 1002 | 0, /* rows */ 1003 | &bufusage); 1004 | 1005 | pgstat_report_wait_end(); 1006 | } 1007 | else 1008 | { 1009 | if (prev_planner_hook) 1010 | result = prev_planner_hook(parse, query_string, cursorOptions, boundParams); 1011 | else 1012 | result = standard_planner(parse, query_string, cursorOptions, boundParams); 1013 | } 1014 | 1015 | return result; 1016 | } 1017 | 1018 | /* 1019 | * ExecutorStart hook: start up tracking if needed 1020 | */ 1021 | static void 1022 | pgssp_ExecutorStart(QueryDesc *queryDesc, int eflags) 1023 | { 1024 | if (prev_ExecutorStart) 1025 | prev_ExecutorStart(queryDesc, eflags); 1026 | else 1027 | standard_ExecutorStart(queryDesc, eflags); 1028 | 1029 | if (MyProc && pgssp_enabled() && pgssp_track_pid && queryDesc->plannedstmt->queryId != UINT64CONST(0)) 1030 | { 1031 | int i = MyProc - ProcGlobal->allProcs; 1032 | /* 1033 | * !!! this may be executed before planner hook end 1034 | * when explain is slow (and plan cache not used) 1035 | * queryId is then the value initialized in post parse 1036 | * when it should have been the value combined with planId !!! 1037 | */ 1038 | ProcEntryArray[i].qpid = queryDesc->plannedstmt->queryId; 1039 | } 1040 | 1041 | /* 1042 | * If query has queryId zero, don't track it. This prevents double 1043 | * counting of optimizable statements that are directly contained in 1044 | * utility statements. 1045 | */ 1046 | if (pgssp_enabled() && queryDesc->plannedstmt->queryId != UINT64CONST(0)) 1047 | { 1048 | /* 1049 | * Set up to track total elapsed time in ExecutorRun. Make sure the 1050 | * space is allocated in the per-query context so it will go away at 1051 | * ExecutorEnd. 1052 | */ 1053 | if (queryDesc->totaltime == NULL) 1054 | { 1055 | MemoryContext oldcxt; 1056 | 1057 | oldcxt = MemoryContextSwitchTo(queryDesc->estate->es_query_cxt); 1058 | queryDesc->totaltime = InstrAlloc(1, INSTRUMENT_ALL, false); 1059 | MemoryContextSwitchTo(oldcxt); 1060 | } 1061 | } 1062 | } 1063 | 1064 | /* 1065 | * ExecutorRun hook: all we need do is track nesting depth 1066 | */ 1067 | static void 1068 | pgssp_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, uint64 count, 1069 | bool execute_once) 1070 | { 1071 | nested_level++; 1072 | PG_TRY(); 1073 | { 1074 | if (prev_ExecutorRun) 1075 | prev_ExecutorRun(queryDesc, direction, count, execute_once); 1076 | else 1077 | standard_ExecutorRun(queryDesc, direction, count, execute_once); 1078 | nested_level--; 1079 | } 1080 | PG_CATCH(); 1081 | { 1082 | if (queryDesc->totaltime && pgssp_enabled() && pgssp_track_errors) 1083 | { 1084 | /* Part to get counters on errors */ 1085 | EState *estate; 1086 | estate = queryDesc->estate; 1087 | InstrStopNode(queryDesc->totaltime, estate->es_processed); 1088 | InstrEndLoop(queryDesc->totaltime); 1089 | 1090 | pgssp_store(queryDesc->sourceText, 1091 | queryDesc->plannedstmt->queryId, 1092 | NULL, 1093 | NULL, 1094 | queryDesc->plannedstmt->stmt_location, 1095 | queryDesc->plannedstmt->stmt_len, 1096 | queryDesc->totaltime->total * 1000.0, /* convert to msec */ 1097 | queryDesc->estate->es_processed, 1098 | &queryDesc->totaltime->bufusage); 1099 | } 1100 | nested_level--; 1101 | PG_RE_THROW(); 1102 | } 1103 | PG_END_TRY(); 1104 | } 1105 | 1106 | /* 1107 | * ExecutorFinish hook: all we need do is track nesting depth 1108 | */ 1109 | static void 1110 | pgssp_ExecutorFinish(QueryDesc *queryDesc) 1111 | { 1112 | nested_level++; 1113 | PG_TRY(); 1114 | { 1115 | if (prev_ExecutorFinish) 1116 | prev_ExecutorFinish(queryDesc); 1117 | else 1118 | standard_ExecutorFinish(queryDesc); 1119 | nested_level--; 1120 | } 1121 | PG_CATCH(); 1122 | { 1123 | nested_level--; 1124 | PG_RE_THROW(); 1125 | } 1126 | PG_END_TRY(); 1127 | } 1128 | 1129 | /* 1130 | * ExecutorEnd hook: store results if needed 1131 | */ 1132 | static void 1133 | pgssp_ExecutorEnd(QueryDesc *queryDesc) 1134 | { 1135 | uint64 queryId = queryDesc->plannedstmt->queryId; 1136 | 1137 | if (queryId != UINT64CONST(0) && queryDesc->totaltime && pgssp_enabled() 1138 | /* this is a workaround to prevent error of recursion when explainoneplan 1139 | is launched from pgssp_planner hook 1140 | */ 1141 | && queryDesc->already_executed ) 1142 | { 1143 | /* 1144 | * Make sure stats accumulation is done. (Note: it's okay if several 1145 | * levels of hook all do this.) 1146 | */ 1147 | 1148 | InstrEndLoop(queryDesc->totaltime); 1149 | 1150 | pgssp_store(queryDesc->sourceText, 1151 | queryId, 1152 | NULL, 1153 | NULL, 1154 | queryDesc->plannedstmt->stmt_location, 1155 | queryDesc->plannedstmt->stmt_len, 1156 | queryDesc->totaltime->total * 1000.0, /* convert to msec */ 1157 | queryDesc->estate->es_processed, 1158 | &queryDesc->totaltime->bufusage); 1159 | } 1160 | 1161 | if (prev_ExecutorEnd) 1162 | prev_ExecutorEnd(queryDesc); 1163 | else 1164 | standard_ExecutorEnd(queryDesc); 1165 | } 1166 | 1167 | /* 1168 | * ProcessUtility hook 1169 | */ 1170 | static void 1171 | pgssp_ProcessUtility(PlannedStmt *pstmt, const char *queryString, 1172 | bool readOnlyTree, 1173 | ProcessUtilityContext context, 1174 | ParamListInfo params, QueryEnvironment *queryEnv, 1175 | DestReceiver *dest, QueryCompletion *qc) 1176 | { 1177 | Node *parsetree = pstmt->utilityStmt; 1178 | 1179 | /* 1180 | * If it's an EXECUTE statement, we don't track it and don't increment the 1181 | * nesting level. This allows the cycles to be charged to the underlying 1182 | * PREPARE instead (by the Executor hooks), which is much more useful. 1183 | * 1184 | * We also don't track execution of PREPARE. If we did, we would get one 1185 | * hash table entry for the PREPARE (with hash calculated from the query 1186 | * string), and then a different one with the same query string (but hash 1187 | * calculated from the query tree) would be used to accumulate costs of 1188 | * ensuing EXECUTEs. This would be confusing, and inconsistent with other 1189 | * cases where planning time is not included at all. 1190 | * 1191 | * Likewise, we don't track execution of DEALLOCATE. 1192 | */ 1193 | if (pgssp_track_utility && pgssp_enabled() && 1194 | !IsA(parsetree, ExecuteStmt) && 1195 | !IsA(parsetree, PrepareStmt) && 1196 | !IsA(parsetree, DeallocateStmt) && 1197 | // TODO exclude optimisable utility statemenents that appears 2 times otherwise 1198 | !IsA(parsetree, CreateTableAsStmt) 1199 | // extend this to : 1200 | // create materialized view, 1201 | // RefreshMatViewStmt, 1202 | // explain, 1203 | // ... 1204 | ) 1205 | { 1206 | instr_time start; 1207 | instr_time duration; 1208 | uint64 rows; 1209 | BufferUsage bufusage_start, 1210 | bufusage; 1211 | 1212 | bufusage_start = pgBufferUsage; 1213 | INSTR_TIME_SET_CURRENT(start); 1214 | 1215 | nested_level++; 1216 | PG_TRY(); 1217 | { 1218 | if (prev_ProcessUtility) 1219 | prev_ProcessUtility(pstmt, queryString, readOnlyTree, 1220 | context, params, queryEnv, 1221 | dest, qc); 1222 | else 1223 | standard_ProcessUtility(pstmt, queryString, readOnlyTree, 1224 | context, params, queryEnv, 1225 | dest, qc); 1226 | nested_level--; 1227 | } 1228 | PG_CATCH(); 1229 | { 1230 | if(pgssp_track_errors) 1231 | { 1232 | /* Part to get counters on errors */ 1233 | INSTR_TIME_SET_CURRENT(duration); 1234 | INSTR_TIME_SUBTRACT(duration, start); 1235 | 1236 | /* calc differences of buffer counters. */ 1237 | bufusage.shared_blks_hit = 1238 | pgBufferUsage.shared_blks_hit - bufusage_start.shared_blks_hit; 1239 | bufusage.shared_blks_read = 1240 | pgBufferUsage.shared_blks_read - bufusage_start.shared_blks_read; 1241 | bufusage.shared_blks_dirtied = 1242 | pgBufferUsage.shared_blks_dirtied - bufusage_start.shared_blks_dirtied; 1243 | bufusage.shared_blks_written = 1244 | pgBufferUsage.shared_blks_written - bufusage_start.shared_blks_written; 1245 | bufusage.local_blks_hit = 1246 | pgBufferUsage.local_blks_hit - bufusage_start.local_blks_hit; 1247 | bufusage.local_blks_read = 1248 | pgBufferUsage.local_blks_read - bufusage_start.local_blks_read; 1249 | bufusage.local_blks_dirtied = 1250 | pgBufferUsage.local_blks_dirtied - bufusage_start.local_blks_dirtied; 1251 | bufusage.local_blks_written = 1252 | pgBufferUsage.local_blks_written - bufusage_start.local_blks_written; 1253 | bufusage.temp_blks_read = 1254 | pgBufferUsage.temp_blks_read - bufusage_start.temp_blks_read; 1255 | bufusage.temp_blks_written = 1256 | pgBufferUsage.temp_blks_written - bufusage_start.temp_blks_written; 1257 | bufusage.blk_read_time = pgBufferUsage.blk_read_time; 1258 | INSTR_TIME_SUBTRACT(bufusage.blk_read_time, bufusage_start.blk_read_time); 1259 | bufusage.blk_write_time = pgBufferUsage.blk_write_time; 1260 | INSTR_TIME_SUBTRACT(bufusage.blk_write_time, bufusage_start.blk_write_time); 1261 | 1262 | pgssp_store(queryString, 1263 | 0, /* signal that it's a utility stmt */ 1264 | NULL, 1265 | NULL, 1266 | pstmt->stmt_location, 1267 | pstmt->stmt_len, 1268 | INSTR_TIME_GET_MILLISEC(duration), 1269 | 0, /* rows */ 1270 | &bufusage); 1271 | }; 1272 | nested_level--; 1273 | PG_RE_THROW(); 1274 | } 1275 | PG_END_TRY(); 1276 | 1277 | INSTR_TIME_SET_CURRENT(duration); 1278 | INSTR_TIME_SUBTRACT(duration, start); 1279 | 1280 | /* 1281 | * Track the total number of rows retrieved or affected by 1282 | * the utility statements of COPY, FETCH, CREATE TABLE AS, 1283 | * CREATE MATERIALIZED VIEW and SELECT INTO. 1284 | */ 1285 | rows = (qc && (qc->commandTag == CMDTAG_COPY || 1286 | qc->commandTag == CMDTAG_FETCH || 1287 | qc->commandTag == CMDTAG_SELECT)) ? 1288 | qc->nprocessed : 0; 1289 | 1290 | /* calc differences of buffer counters. */ 1291 | bufusage.shared_blks_hit = 1292 | pgBufferUsage.shared_blks_hit - bufusage_start.shared_blks_hit; 1293 | bufusage.shared_blks_read = 1294 | pgBufferUsage.shared_blks_read - bufusage_start.shared_blks_read; 1295 | bufusage.shared_blks_dirtied = 1296 | pgBufferUsage.shared_blks_dirtied - bufusage_start.shared_blks_dirtied; 1297 | bufusage.shared_blks_written = 1298 | pgBufferUsage.shared_blks_written - bufusage_start.shared_blks_written; 1299 | bufusage.local_blks_hit = 1300 | pgBufferUsage.local_blks_hit - bufusage_start.local_blks_hit; 1301 | bufusage.local_blks_read = 1302 | pgBufferUsage.local_blks_read - bufusage_start.local_blks_read; 1303 | bufusage.local_blks_dirtied = 1304 | pgBufferUsage.local_blks_dirtied - bufusage_start.local_blks_dirtied; 1305 | bufusage.local_blks_written = 1306 | pgBufferUsage.local_blks_written - bufusage_start.local_blks_written; 1307 | bufusage.temp_blks_read = 1308 | pgBufferUsage.temp_blks_read - bufusage_start.temp_blks_read; 1309 | bufusage.temp_blks_written = 1310 | pgBufferUsage.temp_blks_written - bufusage_start.temp_blks_written; 1311 | bufusage.blk_read_time = pgBufferUsage.blk_read_time; 1312 | INSTR_TIME_SUBTRACT(bufusage.blk_read_time, bufusage_start.blk_read_time); 1313 | bufusage.blk_write_time = pgBufferUsage.blk_write_time; 1314 | INSTR_TIME_SUBTRACT(bufusage.blk_write_time, bufusage_start.blk_write_time); 1315 | 1316 | pgssp_store(queryString, 1317 | 0, /* signal that it's a utility stmt */ 1318 | NULL, 1319 | NULL, 1320 | pstmt->stmt_location, 1321 | pstmt->stmt_len, 1322 | INSTR_TIME_GET_MILLISEC(duration), 1323 | rows, 1324 | &bufusage); 1325 | } 1326 | else 1327 | { 1328 | if (prev_ProcessUtility) 1329 | prev_ProcessUtility(pstmt, queryString, readOnlyTree, 1330 | context, params, queryEnv, 1331 | dest, qc); 1332 | else 1333 | standard_ProcessUtility(pstmt, queryString, readOnlyTree, 1334 | context, params, queryEnv, 1335 | dest, qc); 1336 | } 1337 | } 1338 | 1339 | /* 1340 | * Given an arbitrarily long query string, produce a hash for the purposes of 1341 | * identifying the query, without normalizing constants. Used when hashing 1342 | * utility statements. 1343 | */ 1344 | static uint64 1345 | pgssp_hash_string(const char *str, int len) 1346 | { 1347 | return DatumGetUInt64(hash_any_extended((const unsigned char *) str, 1348 | len, 0)); 1349 | } 1350 | 1351 | /* 1352 | * Store some statistics for a statement. 1353 | * 1354 | * If queryId is 0 then this is a utility statement and we should compute 1355 | * a suitable queryId internally. 1356 | * 1357 | */ 1358 | static void 1359 | pgssp_store(const char *query, uint64 queryId, 1360 | PlannedStmt *plan, ParamListInfo params, 1361 | int query_location, int query_len, 1362 | double total_time, uint64 rows, 1363 | const BufferUsage *bufusage) 1364 | { 1365 | pgsspHashKey key; 1366 | pgsspEntry *entry; 1367 | int encoding = GetDatabaseEncoding(); 1368 | instr_time start; 1369 | instr_time duration; 1370 | ExplainState *es = NewExplainState(); 1371 | uint64 qpId ; 1372 | uint64 planId = UINT64CONST(-1); 1373 | pgstat_report_wait_start(PG_WAIT_EXTENSION); 1374 | INSTR_TIME_SET_CURRENT(start); 1375 | 1376 | Assert(query != NULL); 1377 | 1378 | /* Safety check... */ 1379 | if (!pgssp || !pgssp_hash) 1380 | return; 1381 | 1382 | /* 1383 | * Confine our attention to the relevant part of the string, if the query 1384 | * is a portion of a multi-statement source string. 1385 | * 1386 | * First apply starting offset, unless it's -1 (unknown). 1387 | */ 1388 | if (query_location >= 0) 1389 | { 1390 | Assert(query_location <= strlen(query)); 1391 | query += query_location; 1392 | /* Length of 0 (or -1) means "rest of string" */ 1393 | if (query_len <= 0) 1394 | query_len = strlen(query); 1395 | else 1396 | Assert(query_len <= strlen(query)); 1397 | } 1398 | else 1399 | { 1400 | /* If query location is unknown, distrust query_len as well */ 1401 | query_location = 0; 1402 | query_len = strlen(query); 1403 | } 1404 | 1405 | /* 1406 | * Discard leading and trailing whitespace, too. Use scanner_isspace() 1407 | * not libc's isspace(), because we want to match the lexer's behavior. 1408 | */ 1409 | while (query_len > 0 && scanner_isspace(query[0])) 1410 | query++, query_location++, query_len--; 1411 | while (query_len > 0 && scanner_isspace(query[query_len - 1])) 1412 | query_len--; 1413 | 1414 | /* 1415 | * For utility statements, we just hash the query string to get an ID. 1416 | */ 1417 | if (queryId == UINT64CONST(0) && !plan) 1418 | { 1419 | queryId = pgssp_hash_string(query, query_len); 1420 | planId = UINT64CONST(0); 1421 | qpId = hash_combine64(queryId,planId); 1422 | } 1423 | else if (plan) 1424 | { 1425 | /* specific for optimisable utility statements like CTAS ... */ 1426 | if (queryId == UINT64CONST(0)) 1427 | { 1428 | /* query text can be normalized here */ 1429 | queryId = hash_query(query); 1430 | } 1431 | 1432 | /* Build planid */ 1433 | if (pgssp_plan_type == pgssp_PLAN_NONE) 1434 | { 1435 | planId = UINT64CONST(1); 1436 | qpId = hash_combine64(queryId,planId); 1437 | plan->queryId = qpId; 1438 | } 1439 | else 1440 | { 1441 | /* this part comes from auto_explain 1442 | * ExplainPrintPlan has been replaced by 1443 | * ExplainOnePlan to be able to be calculated 1444 | * from planner hook. 1445 | * 1446 | * es->verbose = true; 1447 | * could help on databases with multiple schemas 1448 | * having tables with the same names. 1449 | * 1450 | * es->settings = true; 1451 | * may help when gucs are modified per session. 1452 | */ 1453 | 1454 | 1455 | /* COSTS off: to make overhead smaller */ 1456 | es->costs = false; 1457 | es->format = EXPLAIN_FORMAT_TEXT; 1458 | 1459 | ExplainBeginOutput(es); 1460 | 1461 | // ExplainQueryText(es, queryDesc); 1462 | if (pgssp_plan_type == pgssp_PLAN_STD ) 1463 | ExplainOnePlan(plan, NULL, es, query, params, 1464 | NULL, NULL, NULL); 1465 | 1466 | // if (es->analyze && auto_explain_log_triggers) 1467 | // ExplainPrintTriggers(es, queryDesc); 1468 | ExplainEndOutput(es); 1469 | 1470 | /* Remove last line break */ 1471 | if (es->str->len > 0 && es->str->data[es->str->len - 1] == '\n') 1472 | es->str->data[--es->str->len] = '\0'; 1473 | 1474 | if (pgssp_plan_type == pgssp_PLAN_STD ) 1475 | planId = hash_query(es->str->data); 1476 | 1477 | /* alter queryid value to reflect planid */ 1478 | qpId = hash_combine64(queryId,planId); 1479 | plan->queryId = qpId; 1480 | } 1481 | } 1482 | else 1483 | /* execution of a planned query */ 1484 | qpId = queryId; 1485 | 1486 | 1487 | /* Set up key for hashtable search */ 1488 | key.userid = GetUserId(); 1489 | key.dbid = MyDatabaseId; 1490 | key.qpid = qpId; 1491 | 1492 | /* Lookup the hash table entry with shared lock. */ 1493 | LWLockAcquire(pgssp->lock, LW_SHARED); 1494 | 1495 | entry = (pgsspEntry *) hash_search(pgssp_hash, &key, HASH_FIND, NULL); 1496 | 1497 | /* Create new entry, at the end of execution*/ 1498 | if (!entry ) 1499 | { 1500 | Size query_offset; 1501 | int gc_count; 1502 | bool stored; 1503 | bool do_gc; 1504 | 1505 | 1506 | /* Append new query text to file with only shared lock held */ 1507 | stored = qtext_store( query, query_len, 1508 | &query_offset, &gc_count); 1509 | 1510 | /* 1511 | * Determine whether we need to garbage collect external query texts 1512 | * while the shared lock is still held. This micro-optimization 1513 | * avoids taking the time to decide this while holding exclusive lock. 1514 | */ 1515 | do_gc = need_gc_qtexts(); 1516 | 1517 | /* Need exclusive lock to make a new hashtable entry - promote */ 1518 | LWLockRelease(pgssp->lock); 1519 | LWLockAcquire(pgssp->lock, LW_EXCLUSIVE); 1520 | 1521 | /* 1522 | * A garbage collection may have occurred while we weren't holding the 1523 | * lock. In the unlikely event that this happens, the query text we 1524 | * stored above will have been garbage collected, so write it again. 1525 | * This should be infrequent enough that doing it while holding 1526 | * exclusive lock isn't a performance problem. 1527 | */ 1528 | if (!stored || pgssp->gc_count != gc_count) 1529 | stored = qtext_store( query, query_len, 1530 | &query_offset, NULL); 1531 | 1532 | /* If we failed to write to the text file, give up */ 1533 | if (!stored) 1534 | goto done; 1535 | 1536 | /* OK to create a new hashtable entry */ 1537 | entry = entry_alloc(&key, query_offset, query_len, encoding); 1538 | 1539 | /* If needed, perform garbage collection while exclusive lock held */ 1540 | if (do_gc) 1541 | gc_qtexts(); 1542 | 1543 | if (planId != UINT64CONST(0) && planId != UINT64CONST(1) && planId != UINT64CONST(-1) && pgssp_explain) 1544 | { 1545 | /* 1546 | * Plan is only logged one time for each queryid / planId 1547 | */ 1548 | ereport(LOG, 1549 | (errmsg("qpid: %lld queryid: %lld planid: %lld plan:\n%s", 1550 | (long long)qpId, (long long)queryId, (long long)planId, es->str->data), 1551 | errhidecontext(true), errhidestmt(true))); 1552 | } 1553 | } 1554 | 1555 | 1556 | /* Increment the counts */ 1557 | if (true) 1558 | { 1559 | /* 1560 | * Grab the spinlock while updating the counters (see comment about 1561 | * locking rules at the head of the file) 1562 | */ 1563 | volatile pgsspEntry *e = (volatile pgsspEntry *) entry; 1564 | 1565 | SpinLockAcquire(&e->mutex); 1566 | 1567 | /* 1568 | * queryid and planid are initialized during planning or utility only, 1569 | * not touched during execution 1570 | */ 1571 | if (plan || planId == UINT64CONST(0)) 1572 | { 1573 | e->counters.queryid = queryId; 1574 | e->counters.planid = planId; 1575 | } 1576 | 1577 | /* "Unstick" entry if it was previously sticky */ 1578 | if ( e->counters.plans == 0 && e->counters.calls == 0) 1579 | { 1580 | e->counters.first_call = GetCurrentTimestamp(); 1581 | } 1582 | 1583 | 1584 | /* add pgssp_store function duration to total_time */ 1585 | // should by outside SpinLockAcquire 1586 | INSTR_TIME_SET_CURRENT(duration); 1587 | INSTR_TIME_SUBTRACT(duration, start); 1588 | 1589 | if(plan) 1590 | { 1591 | e->counters.plans += 1; 1592 | e->counters.plan_time += total_time; 1593 | } 1594 | else 1595 | { 1596 | e->counters.calls += 1; 1597 | e->counters.exec_time += total_time; 1598 | } 1599 | 1600 | 1601 | e->counters.extn_time += INSTR_TIME_GET_MILLISEC(duration); 1602 | total_time += INSTR_TIME_GET_MILLISEC(duration); 1603 | 1604 | e->counters.total_time += total_time ; 1605 | //TODO if (e->counters.calls == 1) 1606 | // { 1607 | // e->counters.min_time = total_time; 1608 | // e->counters.max_time = total_time; 1609 | // e->counters.mean_time = total_time; 1610 | // } 1611 | // else 1612 | // { 1613 | // /* 1614 | // * Welford's method for accurately computing variance. See 1615 | // * 1616 | // */ 1617 | // double old_mean = e->counters.mean_time; 1618 | // 1619 | // e->counters.mean_time += 1620 | // (total_time - old_mean) / e->counters.calls; 1621 | // e->counters.sum_var_time += 1622 | // (total_time - old_mean) * (total_time - e->counters.mean_time); 1623 | // 1624 | // /* calculate min and max time */ 1625 | // if (e->counters.min_time > total_time) 1626 | // e->counters.min_time = total_time; 1627 | // if (e->counters.max_time < total_time) 1628 | // e->counters.max_time = total_time; 1629 | // } 1630 | e->counters.rows += rows; 1631 | e->counters.shared_blks_hit += bufusage->shared_blks_hit; 1632 | e->counters.shared_blks_read += bufusage->shared_blks_read; 1633 | e->counters.shared_blks_dirtied += bufusage->shared_blks_dirtied; 1634 | e->counters.shared_blks_written += bufusage->shared_blks_written; 1635 | e->counters.local_blks_hit += bufusage->local_blks_hit; 1636 | e->counters.local_blks_read += bufusage->local_blks_read; 1637 | e->counters.local_blks_dirtied += bufusage->local_blks_dirtied; 1638 | e->counters.local_blks_written += bufusage->local_blks_written; 1639 | e->counters.temp_blks_read += bufusage->temp_blks_read; 1640 | e->counters.temp_blks_written += bufusage->temp_blks_written; 1641 | e->counters.blk_read_time += INSTR_TIME_GET_MILLISEC(bufusage->blk_read_time); 1642 | e->counters.blk_write_time += INSTR_TIME_GET_MILLISEC(bufusage->blk_write_time); 1643 | e->counters.last_call = GetCurrentTimestamp(); 1644 | 1645 | SpinLockRelease(&e->mutex); 1646 | } 1647 | 1648 | done: 1649 | LWLockRelease(pgssp->lock); 1650 | 1651 | pgstat_report_wait_end(); 1652 | } 1653 | 1654 | /* 1655 | * Reset all statement statistics. 1656 | */ 1657 | Datum 1658 | pg_stat_sql_plans_reset(PG_FUNCTION_ARGS) 1659 | { 1660 | if (!pgssp || !pgssp_hash) 1661 | ereport(ERROR, 1662 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), 1663 | errmsg("pg_stat_sql_plans must be loaded via shared_preload_libraries"))); 1664 | entry_reset(); 1665 | PG_RETURN_VOID(); 1666 | } 1667 | 1668 | /* Number of output arguments (columns) for various API versions */ 1669 | #define pg_stat_sql_plans_COLS_V1_0 14 1670 | #define pg_stat_sql_plans_COLS_V1_1 18 1671 | #define pg_stat_sql_plans_COLS_V1_2 19 1672 | //#define pg_stat_sql_plans_COLS_V1_3 29 1673 | #define pg_stat_sql_plans_COLS_V1_3 31 1674 | #define pg_stat_sql_plans_COLS 31 /* maximum of above */ 1675 | 1676 | /* 1677 | * Retrieve statement statistics. 1678 | * 1679 | * The SQL API of this function has changed multiple times, and will likely 1680 | * do so again in future. To support the case where a newer version of this 1681 | * loadable module is being used with an old SQL declaration of the function, 1682 | * we continue to support the older API versions. For 1.2 and later, the 1683 | * expected API version is identified by embedding it in the C name of the 1684 | * function. Unfortunately we weren't bright enough to do that for 1.1. 1685 | */ 1686 | Datum 1687 | pg_stat_sql_plans_1_3(PG_FUNCTION_ARGS) 1688 | { 1689 | bool showtext = PG_GETARG_BOOL(0); 1690 | 1691 | pg_stat_sql_plans_internal(fcinfo, pgssp_V1_3, showtext); 1692 | 1693 | return (Datum) 0; 1694 | } 1695 | 1696 | Datum 1697 | pg_stat_sql_plans_1_2(PG_FUNCTION_ARGS) 1698 | { 1699 | bool showtext = PG_GETARG_BOOL(0); 1700 | 1701 | pg_stat_sql_plans_internal(fcinfo, pgssp_V1_2, showtext); 1702 | 1703 | return (Datum) 0; 1704 | } 1705 | 1706 | /* 1707 | * Legacy entry point for pg_stat_sql_plans() API versions 1.0 and 1.1. 1708 | * This can be removed someday, perhaps. 1709 | */ 1710 | Datum 1711 | pg_stat_sql_plans(PG_FUNCTION_ARGS) 1712 | { 1713 | /* If it's really API 1.1, we'll figure that out below */ 1714 | pg_stat_sql_plans_internal(fcinfo, pgssp_V1_0, true); 1715 | 1716 | return (Datum) 0; 1717 | } 1718 | 1719 | /* Common code for all versions of pg_stat_sql_plans() */ 1720 | static void 1721 | pg_stat_sql_plans_internal(FunctionCallInfo fcinfo, 1722 | pgsspVersion api_version, 1723 | bool showtext) 1724 | { 1725 | ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; 1726 | TupleDesc tupdesc; 1727 | Tuplestorestate *tupstore; 1728 | MemoryContext per_query_ctx; 1729 | MemoryContext oldcontext; 1730 | Oid userid = GetUserId(); 1731 | bool is_allowed_role = false; 1732 | char *qbuffer = NULL; 1733 | Size qbuffer_size = 0; 1734 | Size extent = 0; 1735 | int gc_count = 0; 1736 | HASH_SEQ_STATUS hash_seq; 1737 | pgsspEntry *entry; 1738 | 1739 | /* Superusers or members of pg_read_all_stats members are allowed */ 1740 | is_allowed_role = is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS); 1741 | 1742 | /* hash table must exist already */ 1743 | if (!pgssp || !pgssp_hash) 1744 | ereport(ERROR, 1745 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), 1746 | errmsg("pg_stat_sql_plans must be loaded via shared_preload_libraries"))); 1747 | 1748 | /* check to see if caller supports us returning a tuplestore */ 1749 | if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) 1750 | ereport(ERROR, 1751 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 1752 | errmsg("set-valued function called in context that cannot accept a set"))); 1753 | if (!(rsinfo->allowedModes & SFRM_Materialize)) 1754 | ereport(ERROR, 1755 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 1756 | errmsg("materialize mode required, but it is not " \ 1757 | "allowed in this context"))); 1758 | 1759 | /* Switch into long-lived context to construct returned data structures */ 1760 | per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; 1761 | oldcontext = MemoryContextSwitchTo(per_query_ctx); 1762 | 1763 | /* Build a tuple descriptor for our result type */ 1764 | if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) 1765 | elog(ERROR, "return type must be a row type"); 1766 | 1767 | /* 1768 | * Check we have the expected number of output arguments. Aside from 1769 | * being a good safety check, we need a kluge here to detect API version 1770 | * 1.1, which was wedged into the code in an ill-considered way. 1771 | */ 1772 | switch (tupdesc->natts) 1773 | { 1774 | case pg_stat_sql_plans_COLS_V1_0: 1775 | if (api_version != pgssp_V1_0) 1776 | elog(ERROR, "incorrect number of output arguments"); 1777 | break; 1778 | case pg_stat_sql_plans_COLS_V1_1: 1779 | /* pg_stat_sql_plans() should have told us 1.0 */ 1780 | if (api_version != pgssp_V1_0) 1781 | elog(ERROR, "incorrect number of output arguments"); 1782 | api_version = pgssp_V1_1; 1783 | break; 1784 | case pg_stat_sql_plans_COLS_V1_2: 1785 | if (api_version != pgssp_V1_2) 1786 | elog(ERROR, "incorrect number of output arguments"); 1787 | break; 1788 | case pg_stat_sql_plans_COLS_V1_3: 1789 | if (api_version != pgssp_V1_3) 1790 | elog(ERROR, "incorrect number of output arguments"); 1791 | break; 1792 | default: 1793 | elog(ERROR, "incorrect number of output arguments"); 1794 | } 1795 | 1796 | tupstore = tuplestore_begin_heap(true, false, work_mem); 1797 | rsinfo->returnMode = SFRM_Materialize; 1798 | rsinfo->setResult = tupstore; 1799 | rsinfo->setDesc = tupdesc; 1800 | 1801 | MemoryContextSwitchTo(oldcontext); 1802 | 1803 | /* 1804 | * We'd like to load the query text file (if needed) while not holding any 1805 | * lock on pgssp->lock. In the worst case we'll have to do this again 1806 | * after we have the lock, but it's unlikely enough to make this a win 1807 | * despite occasional duplicated work. We need to reload if anybody 1808 | * writes to the file (either a retail qtext_store(), or a garbage 1809 | * collection) between this point and where we've gotten shared lock. If 1810 | * a qtext_store is actually in progress when we look, we might as well 1811 | * skip the speculative load entirely. 1812 | */ 1813 | if (showtext) 1814 | { 1815 | int n_writers; 1816 | 1817 | /* Take the mutex so we can examine variables */ 1818 | { 1819 | volatile pgsspSharedState *s = (volatile pgsspSharedState *) pgssp; 1820 | 1821 | SpinLockAcquire(&s->mutex); 1822 | extent = s->extent; 1823 | n_writers = s->n_writers; 1824 | gc_count = s->gc_count; 1825 | SpinLockRelease(&s->mutex); 1826 | } 1827 | 1828 | /* No point in loading file now if there are active writers */ 1829 | if (n_writers == 0) 1830 | qbuffer = qtext_load_file(&qbuffer_size); 1831 | } 1832 | 1833 | /* 1834 | * Get shared lock, load or reload the query text file if we must, and 1835 | * iterate over the hashtable entries. 1836 | * 1837 | * With a large hash table, we might be holding the lock rather longer 1838 | * than one could wish. However, this only blocks creation of new hash 1839 | * table entries, and the larger the hash table the less likely that is to 1840 | * be needed. So we can hope this is okay. Perhaps someday we'll decide 1841 | * we need to partition the hash table to limit the time spent holding any 1842 | * one lock. 1843 | */ 1844 | LWLockAcquire(pgssp->lock, LW_SHARED); 1845 | 1846 | if (showtext) 1847 | { 1848 | /* 1849 | * Here it is safe to examine extent and gc_count without taking the 1850 | * mutex. Note that although other processes might change 1851 | * pgssp->extent just after we look at it, the strings they then write 1852 | * into the file cannot yet be referenced in the hashtable, so we 1853 | * don't care whether we see them or not. 1854 | * 1855 | * If qtext_load_file fails, we just press on; we'll return NULL for 1856 | * every query text. 1857 | */ 1858 | if (qbuffer == NULL || 1859 | pgssp->extent != extent || 1860 | pgssp->gc_count != gc_count) 1861 | { 1862 | if (qbuffer) 1863 | free(qbuffer); 1864 | qbuffer = qtext_load_file(&qbuffer_size); 1865 | } 1866 | } 1867 | 1868 | hash_seq_init(&hash_seq, pgssp_hash); 1869 | while ((entry = hash_seq_search(&hash_seq)) != NULL) 1870 | { 1871 | Datum values[pg_stat_sql_plans_COLS]; 1872 | bool nulls[pg_stat_sql_plans_COLS]; 1873 | int i = 0; 1874 | Counters tmp; 1875 | double stddev; 1876 | /* qpid replaces combined key (queryid,planid) */ 1877 | int64 qpid = entry->key.qpid; 1878 | 1879 | memset(values, 0, sizeof(values)); 1880 | memset(nulls, 0, sizeof(nulls)); 1881 | 1882 | values[i++] = ObjectIdGetDatum(entry->key.userid); 1883 | values[i++] = ObjectIdGetDatum(entry->key.dbid); 1884 | 1885 | if (is_allowed_role || entry->key.userid == userid) 1886 | { 1887 | if (api_version >= pgssp_V1_3) 1888 | values[i++] = Int64GetDatumFast(qpid); 1889 | 1890 | if (showtext) 1891 | { 1892 | char *qstr = qtext_fetch(entry->query_offset, 1893 | entry->query_len, 1894 | qbuffer, 1895 | qbuffer_size); 1896 | 1897 | if (qstr) 1898 | { 1899 | char *enc; 1900 | 1901 | enc = pg_any_to_server(qstr, 1902 | entry->query_len, 1903 | entry->encoding); 1904 | 1905 | values[i++] = CStringGetTextDatum(enc); 1906 | 1907 | if (enc != qstr) 1908 | pfree(enc); 1909 | } 1910 | else 1911 | { 1912 | /* Just return a null if we fail to find the text */ 1913 | nulls[i++] = true; 1914 | } 1915 | } 1916 | else 1917 | { 1918 | /* Query text not requested */ 1919 | nulls[i++] = true; 1920 | } 1921 | } 1922 | else 1923 | { 1924 | /* Don't show queryid */ 1925 | if (api_version >= pgssp_V1_2) 1926 | nulls[i++] = true; 1927 | 1928 | /* 1929 | * Don't show query text, but hint as to the reason for not doing 1930 | * so if it was requested 1931 | */ 1932 | if (showtext) 1933 | values[i++] = CStringGetTextDatum(""); 1934 | else 1935 | nulls[i++] = true; 1936 | } 1937 | 1938 | /* copy counters to a local variable to keep locking time short */ 1939 | { 1940 | volatile pgsspEntry *e = (volatile pgsspEntry *) entry; 1941 | 1942 | SpinLockAcquire(&e->mutex); 1943 | tmp = e->counters; 1944 | SpinLockRelease(&e->mutex); 1945 | } 1946 | 1947 | /* Skip entry if unexecuted (ie, it's a pending "sticky" entry) */ 1948 | if (tmp.plans == 0 && tmp.calls == 0 ) 1949 | continue; 1950 | 1951 | if (api_version >= pgssp_V1_3) 1952 | { 1953 | values[i++] = Int64GetDatumFast(tmp.queryid); 1954 | values[i++] = Int64GetDatumFast(tmp.planid); 1955 | values[i++] = Int64GetDatumFast(tmp.plans); 1956 | } 1957 | values[i++] = Int64GetDatumFast(tmp.calls); 1958 | values[i++] = Float8GetDatumFast(tmp.total_time); 1959 | if (api_version >= pgssp_V1_3) 1960 | { 1961 | values[i++] = Float8GetDatumFast(tmp.min_time); 1962 | values[i++] = Float8GetDatumFast(tmp.max_time); 1963 | values[i++] = Float8GetDatumFast(tmp.mean_time); 1964 | 1965 | /* 1966 | * Note we are calculating the population variance here, not the 1967 | * sample variance, as we have data for the whole population, so 1968 | * Bessel's correction is not used, and we don't divide by 1969 | * tmp.calls - 1. 1970 | */ 1971 | if (tmp.calls > 1) 1972 | stddev = sqrt(tmp.sum_var_time / tmp.calls); 1973 | else 1974 | stddev = 0.0; 1975 | values[i++] = Float8GetDatumFast(stddev); 1976 | values[i++] = Float8GetDatumFast(tmp.plan_time); 1977 | values[i++] = Float8GetDatumFast(tmp.exec_time); 1978 | values[i++] = Float8GetDatumFast(tmp.extn_time); 1979 | } 1980 | values[i++] = Int64GetDatumFast(tmp.rows); 1981 | values[i++] = Int64GetDatumFast(tmp.shared_blks_hit); 1982 | values[i++] = Int64GetDatumFast(tmp.shared_blks_read); 1983 | if (api_version >= pgssp_V1_1) 1984 | values[i++] = Int64GetDatumFast(tmp.shared_blks_dirtied); 1985 | values[i++] = Int64GetDatumFast(tmp.shared_blks_written); 1986 | values[i++] = Int64GetDatumFast(tmp.local_blks_hit); 1987 | values[i++] = Int64GetDatumFast(tmp.local_blks_read); 1988 | if (api_version >= pgssp_V1_1) 1989 | values[i++] = Int64GetDatumFast(tmp.local_blks_dirtied); 1990 | values[i++] = Int64GetDatumFast(tmp.local_blks_written); 1991 | values[i++] = Int64GetDatumFast(tmp.temp_blks_read); 1992 | values[i++] = Int64GetDatumFast(tmp.temp_blks_written); 1993 | if (api_version >= pgssp_V1_1) 1994 | { 1995 | values[i++] = Float8GetDatumFast(tmp.blk_read_time); 1996 | values[i++] = Float8GetDatumFast(tmp.blk_write_time); 1997 | } 1998 | values[i++] = TimestampTzGetDatum(tmp.first_call); 1999 | values[i++] = TimestampTzGetDatum(tmp.last_call); 2000 | 2001 | Assert(i == (api_version == pgssp_V1_0 ? pg_stat_sql_plans_COLS_V1_0 : 2002 | api_version == pgssp_V1_1 ? pg_stat_sql_plans_COLS_V1_1 : 2003 | api_version == pgssp_V1_2 ? pg_stat_sql_plans_COLS_V1_2 : 2004 | api_version == pgssp_V1_3 ? pg_stat_sql_plans_COLS_V1_3 : 2005 | -1 /* fail if you forget to update this assert */ )); 2006 | 2007 | tuplestore_putvalues(tupstore, tupdesc, values, nulls); 2008 | } 2009 | 2010 | /* clean up and return the tuplestore */ 2011 | LWLockRelease(pgssp->lock); 2012 | 2013 | if (qbuffer) 2014 | free(qbuffer); 2015 | 2016 | tuplestore_donestoring(tupstore); 2017 | } 2018 | 2019 | /* 2020 | * Estimate shared memory space needed. 2021 | */ 2022 | static Size 2023 | pgssp_memsize(void) 2024 | { 2025 | Size size; 2026 | 2027 | size = MAXALIGN(sizeof(pgsspSharedState)); 2028 | size = add_size(size, hash_estimate_size(pgssp_max, sizeof(pgsspEntry))); 2029 | 2030 | return size; 2031 | } 2032 | 2033 | /* 2034 | * Allocate a new hashtable entry. 2035 | * caller must hold an exclusive lock on pgssp->lock 2036 | * 2037 | * "query" need not be null-terminated; we rely on query_len instead 2038 | * 2039 | * If "sticky" is true, make the new entry artificially sticky so that it will 2040 | * probably still be there when the query finishes execution. We do this by 2041 | * giving it a median usage value rather than the normal value. (Strictly 2042 | * speaking, query strings are normalized on a best effort basis, though it 2043 | * would be difficult to demonstrate this even under artificial conditions.) 2044 | * 2045 | * Note: despite needing exclusive lock, it's not an error for the target 2046 | * entry to already exist. This is because pgssp_store releases and 2047 | * reacquires lock after failing to find a match; so someone else could 2048 | * have made the entry while we waited to get exclusive lock. 2049 | */ 2050 | static pgsspEntry * 2051 | entry_alloc(pgsspHashKey *key, Size query_offset, int query_len, int encoding) 2052 | { 2053 | pgsspEntry *entry; 2054 | bool found; 2055 | 2056 | /* Make space if needed */ 2057 | while (hash_get_num_entries(pgssp_hash) >= pgssp_max) 2058 | entry_dealloc(); 2059 | 2060 | /* Find or create an entry with desired hash code */ 2061 | entry = (pgsspEntry *) hash_search(pgssp_hash, key, HASH_ENTER, &found); 2062 | 2063 | if (!found) 2064 | { 2065 | /* New entry, initialize it */ 2066 | 2067 | /* reset the statistics */ 2068 | memset(&entry->counters, 0, sizeof(Counters)); 2069 | 2070 | /* re-initialize the mutex each time ... we assume no one using it */ 2071 | SpinLockInit(&entry->mutex); 2072 | /* ... and don't forget the query text metadata */ 2073 | Assert(query_len >= 0); 2074 | entry->query_offset = query_offset; 2075 | entry->query_len = query_len; 2076 | entry->encoding = encoding; 2077 | } 2078 | 2079 | return entry; 2080 | } 2081 | 2082 | /* 2083 | * qsort comparator for sorting into increasing usage order 2084 | * !!! this is a pgssp specific eviction criteria based on last_call time !!! 2085 | */ 2086 | static int 2087 | entry_cmp(const void *lhs, const void *rhs) 2088 | { 2089 | double l_usage = (*(pgsspEntry *const *) lhs)->counters.last_call; 2090 | double r_usage = (*(pgsspEntry *const *) rhs)->counters.last_call; 2091 | 2092 | if (l_usage < r_usage) 2093 | return -1; 2094 | else if (l_usage > r_usage) 2095 | return +1; 2096 | else 2097 | return 0; 2098 | } 2099 | 2100 | /* 2101 | * Deallocate least-used entries. 2102 | * 2103 | * Caller must hold an exclusive lock on pgssp->lock. 2104 | */ 2105 | static void 2106 | entry_dealloc(void) 2107 | { 2108 | HASH_SEQ_STATUS hash_seq; 2109 | pgsspEntry **entries; 2110 | pgsspEntry *entry; 2111 | int nvictims; 2112 | int i; 2113 | Size tottextlen; 2114 | int nvalidtexts; 2115 | 2116 | /* 2117 | * Sort entries by usage and deallocate USAGE_DEALLOC_PERCENT of them. 2118 | * While we're scanning the table, apply the decay factor to the usage 2119 | * values, and update the mean query length. 2120 | * 2121 | * Note that the mean query length is almost immediately obsolete, since 2122 | * we compute it before not after discarding the least-used entries. 2123 | * Hopefully, that doesn't affect the mean too much; it doesn't seem worth 2124 | * making two passes to get a more current result. Likewise, the new 2125 | * cur_median_usage includes the entries we're about to zap. 2126 | */ 2127 | 2128 | 2129 | entries = palloc(hash_get_num_entries(pgssp_hash) * sizeof(pgsspEntry *)); 2130 | 2131 | i = 0; 2132 | tottextlen = 0; 2133 | nvalidtexts = 0; 2134 | 2135 | hash_seq_init(&hash_seq, pgssp_hash); 2136 | while ((entry = hash_seq_search(&hash_seq)) != NULL) 2137 | { 2138 | entries[i++] = entry; 2139 | 2140 | /* In the mean length computation, ignore dropped texts. */ 2141 | if (entry->query_len >= 0) 2142 | { 2143 | tottextlen += entry->query_len + 1; 2144 | nvalidtexts++; 2145 | } 2146 | } 2147 | 2148 | /* Sort into increasing order by usage */ 2149 | qsort(entries, i, sizeof(pgsspEntry *), entry_cmp); 2150 | 2151 | /* Record the mean query length */ 2152 | if (nvalidtexts > 0) 2153 | pgssp->mean_query_len = tottextlen / nvalidtexts; 2154 | else 2155 | pgssp->mean_query_len = ASSUMED_LENGTH_INIT; 2156 | 2157 | /* Now zap an appropriate fraction of lowest-usage entries */ 2158 | nvictims = Max(10, i * USAGE_DEALLOC_PERCENT / 100); 2159 | nvictims = Min(nvictims, i); 2160 | 2161 | for (i = 0; i < nvictims; i++) 2162 | { 2163 | hash_search(pgssp_hash, &entries[i]->key, HASH_REMOVE, NULL); 2164 | } 2165 | 2166 | pfree(entries); 2167 | 2168 | /* trace when evicting entries, if appening too often this can slow queries ... 2169 | * increasing pg_stat_sql_plans.max value could help */ 2170 | ereport(LOG, 2171 | (errmsg("pg_stat_sql_plans evicting %d entries", nvictims), 2172 | errhidecontext(true), errhidestmt(true))); 2173 | 2174 | } 2175 | 2176 | /* 2177 | * Given a query string (not necessarily null-terminated), allocate a new 2178 | * entry in the external query text file and store the string there. 2179 | * 2180 | * If successful, returns true, and stores the new entry's offset in the file 2181 | * into *query_offset. Also, if gc_count isn't NULL, *gc_count is set to the 2182 | * number of garbage collections that have occurred so far. 2183 | * 2184 | * On failure, returns false. 2185 | * 2186 | * At least a shared lock on pgssp->lock must be held by the caller, so as 2187 | * to prevent a concurrent garbage collection. Share-lock-holding callers 2188 | * should pass a gc_count pointer to obtain the number of garbage collections, 2189 | * so that they can recheck the count after obtaining exclusive lock to 2190 | * detect whether a garbage collection occurred (and removed this entry). 2191 | */ 2192 | static bool 2193 | qtext_store(const char *query, int query_len, 2194 | Size *query_offset, int *gc_count) 2195 | { 2196 | Size off; 2197 | int fd; 2198 | 2199 | /* 2200 | * We use a spinlock to protect extent/n_writers/gc_count, so that 2201 | * multiple processes may execute this function concurrently. 2202 | */ 2203 | { 2204 | volatile pgsspSharedState *s = (volatile pgsspSharedState *) pgssp; 2205 | 2206 | SpinLockAcquire(&s->mutex); 2207 | off = s->extent; 2208 | s->extent += query_len + 1; 2209 | s->n_writers++; 2210 | if (gc_count) 2211 | *gc_count = s->gc_count; 2212 | SpinLockRelease(&s->mutex); 2213 | } 2214 | 2215 | *query_offset = off; 2216 | 2217 | /* Now write the data into the successfully-reserved part of the file */ 2218 | fd = OpenTransientFile(pgssp_TEXT_FILE, O_RDWR | O_CREAT | PG_BINARY); 2219 | if (fd < 0) 2220 | goto error; 2221 | 2222 | if (lseek(fd, off, SEEK_SET) != off) 2223 | goto error; 2224 | 2225 | if (write(fd, query, query_len) != query_len) 2226 | goto error; 2227 | if (write(fd, "\0", 1) != 1) 2228 | goto error; 2229 | 2230 | CloseTransientFile(fd); 2231 | 2232 | /* Mark our write complete */ 2233 | { 2234 | volatile pgsspSharedState *s = (volatile pgsspSharedState *) pgssp; 2235 | 2236 | SpinLockAcquire(&s->mutex); 2237 | s->n_writers--; 2238 | SpinLockRelease(&s->mutex); 2239 | } 2240 | 2241 | return true; 2242 | 2243 | error: 2244 | ereport(LOG, 2245 | (errcode_for_file_access(), 2246 | errmsg("could not write pg_stat_statement file \"%s\": %m", 2247 | pgssp_TEXT_FILE))); 2248 | 2249 | if (fd >= 0) 2250 | CloseTransientFile(fd); 2251 | 2252 | /* Mark our write complete */ 2253 | { 2254 | volatile pgsspSharedState *s = (volatile pgsspSharedState *) pgssp; 2255 | 2256 | SpinLockAcquire(&s->mutex); 2257 | s->n_writers--; 2258 | SpinLockRelease(&s->mutex); 2259 | } 2260 | 2261 | return false; 2262 | } 2263 | 2264 | /* 2265 | * Read the external query text file into a malloc'd buffer. 2266 | * 2267 | * Returns NULL (without throwing an error) if unable to read, eg 2268 | * file not there or insufficient memory. 2269 | * 2270 | * On success, the buffer size is also returned into *buffer_size. 2271 | * 2272 | * This can be called without any lock on pgssp->lock, but in that case 2273 | * the caller is responsible for verifying that the result is sane. 2274 | */ 2275 | static char * 2276 | qtext_load_file(Size *buffer_size) 2277 | { 2278 | char *buf; 2279 | int fd; 2280 | struct stat stat; 2281 | 2282 | fd = OpenTransientFile(pgssp_TEXT_FILE, O_RDONLY | PG_BINARY); 2283 | if (fd < 0) 2284 | { 2285 | if (errno != ENOENT) 2286 | ereport(LOG, 2287 | (errcode_for_file_access(), 2288 | errmsg("could not read pg_stat_statement file \"%s\": %m", 2289 | pgssp_TEXT_FILE))); 2290 | return NULL; 2291 | } 2292 | 2293 | /* Get file length */ 2294 | if (fstat(fd, &stat)) 2295 | { 2296 | ereport(LOG, 2297 | (errcode_for_file_access(), 2298 | errmsg("could not stat pg_stat_statement file \"%s\": %m", 2299 | pgssp_TEXT_FILE))); 2300 | CloseTransientFile(fd); 2301 | return NULL; 2302 | } 2303 | 2304 | /* Allocate buffer; beware that off_t might be wider than size_t */ 2305 | if (stat.st_size <= MaxAllocHugeSize) 2306 | buf = (char *) malloc(stat.st_size); 2307 | else 2308 | buf = NULL; 2309 | if (buf == NULL) 2310 | { 2311 | ereport(LOG, 2312 | (errcode(ERRCODE_OUT_OF_MEMORY), 2313 | errmsg("out of memory"), 2314 | errdetail("Could not allocate enough memory to read pg_stat_statement file \"%s\".", 2315 | pgssp_TEXT_FILE))); 2316 | CloseTransientFile(fd); 2317 | return NULL; 2318 | } 2319 | 2320 | /* 2321 | * OK, slurp in the file. If we get a short read and errno doesn't get 2322 | * set, the reason is probably that garbage collection truncated the file 2323 | * since we did the fstat(), so we don't log a complaint --- but we don't 2324 | * return the data, either, since it's most likely corrupt due to 2325 | * concurrent writes from garbage collection. 2326 | */ 2327 | errno = 0; 2328 | if (read(fd, buf, stat.st_size) != stat.st_size) 2329 | { 2330 | if (errno) 2331 | ereport(LOG, 2332 | (errcode_for_file_access(), 2333 | errmsg("could not read pg_stat_statement file \"%s\": %m", 2334 | pgssp_TEXT_FILE))); 2335 | free(buf); 2336 | CloseTransientFile(fd); 2337 | return NULL; 2338 | } 2339 | 2340 | CloseTransientFile(fd); 2341 | 2342 | *buffer_size = stat.st_size; 2343 | return buf; 2344 | } 2345 | 2346 | /* 2347 | * Locate a query text in the file image previously read by qtext_load_file(). 2348 | * 2349 | * We validate the given offset/length, and return NULL if bogus. Otherwise, 2350 | * the result points to a null-terminated string within the buffer. 2351 | */ 2352 | static char * 2353 | qtext_fetch(Size query_offset, int query_len, 2354 | char *buffer, Size buffer_size) 2355 | { 2356 | /* File read failed? */ 2357 | if (buffer == NULL) 2358 | return NULL; 2359 | /* Bogus offset/length? */ 2360 | if (query_len < 0 || 2361 | query_offset + query_len >= buffer_size) 2362 | return NULL; 2363 | /* As a further sanity check, make sure there's a trailing null */ 2364 | if (buffer[query_offset + query_len] != '\0') 2365 | return NULL; 2366 | /* Looks OK */ 2367 | return buffer + query_offset; 2368 | } 2369 | 2370 | /* 2371 | * Do we need to garbage-collect the external query text file? 2372 | * 2373 | * Caller should hold at least a shared lock on pgssp->lock. 2374 | */ 2375 | static bool 2376 | need_gc_qtexts(void) 2377 | { 2378 | Size extent; 2379 | 2380 | /* Read shared extent pointer */ 2381 | { 2382 | volatile pgsspSharedState *s = (volatile pgsspSharedState *) pgssp; 2383 | 2384 | SpinLockAcquire(&s->mutex); 2385 | extent = s->extent; 2386 | SpinLockRelease(&s->mutex); 2387 | } 2388 | 2389 | /* Don't proceed if file does not exceed 512 bytes per possible entry */ 2390 | if (extent < 512 * pgssp_max) 2391 | return false; 2392 | 2393 | /* 2394 | * Don't proceed if file is less than about 50% bloat. Nothing can or 2395 | * should be done in the event of unusually large query texts accounting 2396 | * for file's large size. We go to the trouble of maintaining the mean 2397 | * query length in order to prevent garbage collection from thrashing 2398 | * uselessly. 2399 | */ 2400 | if (extent < pgssp->mean_query_len * pgssp_max * 2) 2401 | return false; 2402 | 2403 | return true; 2404 | } 2405 | 2406 | /* 2407 | * Garbage-collect orphaned query texts in external file. 2408 | * 2409 | * This won't be called often in the typical case, since it's likely that 2410 | * there won't be too much churn, and besides, a similar compaction process 2411 | * occurs when serializing to disk at shutdown or as part of resetting. 2412 | * Despite this, it seems prudent to plan for the edge case where the file 2413 | * becomes unreasonably large, with no other method of compaction likely to 2414 | * occur in the foreseeable future. 2415 | * 2416 | * The caller must hold an exclusive lock on pgssp->lock. 2417 | * 2418 | * At the first sign of trouble we unlink the query text file to get a clean 2419 | * slate (although existing statistics are retained), rather than risk 2420 | * thrashing by allowing the same problem case to recur indefinitely. 2421 | */ 2422 | static void 2423 | gc_qtexts(void) 2424 | { 2425 | char *qbuffer; 2426 | Size qbuffer_size; 2427 | FILE *qfile = NULL; 2428 | HASH_SEQ_STATUS hash_seq; 2429 | pgsspEntry *entry; 2430 | Size extent; 2431 | int nentries; 2432 | 2433 | /* 2434 | * When called from pgssp_store, some other session might have proceeded 2435 | * with garbage collection in the no-lock-held interim of lock strength 2436 | * escalation. Check once more that this is actually necessary. 2437 | */ 2438 | if (!need_gc_qtexts()) 2439 | return; 2440 | 2441 | /* 2442 | * Load the old texts file. If we fail (out of memory, for instance), 2443 | * invalidate query texts. Hopefully this is rare. It might seem better 2444 | * to leave things alone on an OOM failure, but the problem is that the 2445 | * file is only going to get bigger; hoping for a future non-OOM result is 2446 | * risky and can easily lead to complete denial of service. 2447 | */ 2448 | qbuffer = qtext_load_file(&qbuffer_size); 2449 | if (qbuffer == NULL) 2450 | goto gc_fail; 2451 | 2452 | /* 2453 | * We overwrite the query texts file in place, so as to reduce the risk of 2454 | * an out-of-disk-space failure. Since the file is guaranteed not to get 2455 | * larger, this should always work on traditional filesystems; though we 2456 | * could still lose on copy-on-write filesystems. 2457 | */ 2458 | qfile = AllocateFile(pgssp_TEXT_FILE, PG_BINARY_W); 2459 | if (qfile == NULL) 2460 | { 2461 | ereport(LOG, 2462 | (errcode_for_file_access(), 2463 | errmsg("could not write pg_stat_statement file \"%s\": %m", 2464 | pgssp_TEXT_FILE))); 2465 | goto gc_fail; 2466 | } 2467 | 2468 | extent = 0; 2469 | nentries = 0; 2470 | 2471 | hash_seq_init(&hash_seq, pgssp_hash); 2472 | while ((entry = hash_seq_search(&hash_seq)) != NULL) 2473 | { 2474 | int query_len = entry->query_len; 2475 | char *qry = qtext_fetch(entry->query_offset, 2476 | query_len, 2477 | qbuffer, 2478 | qbuffer_size); 2479 | 2480 | if (qry == NULL) 2481 | { 2482 | /* Trouble ... drop the text */ 2483 | entry->query_offset = 0; 2484 | entry->query_len = -1; 2485 | /* entry will not be counted in mean query length computation */ 2486 | continue; 2487 | } 2488 | 2489 | if (fwrite(qry, 1, query_len + 1, qfile) != query_len + 1) 2490 | { 2491 | ereport(LOG, 2492 | (errcode_for_file_access(), 2493 | errmsg("could not write pg_stat_statement file \"%s\": %m", 2494 | pgssp_TEXT_FILE))); 2495 | hash_seq_term(&hash_seq); 2496 | goto gc_fail; 2497 | } 2498 | 2499 | entry->query_offset = extent; 2500 | extent += query_len + 1; 2501 | nentries++; 2502 | } 2503 | 2504 | /* 2505 | * Truncate away any now-unused space. If this fails for some odd reason, 2506 | * we log it, but there's no need to fail. 2507 | */ 2508 | if (ftruncate(fileno(qfile), extent) != 0) 2509 | ereport(LOG, 2510 | (errcode_for_file_access(), 2511 | errmsg("could not truncate pg_stat_statement file \"%s\": %m", 2512 | pgssp_TEXT_FILE))); 2513 | 2514 | if (FreeFile(qfile)) 2515 | { 2516 | ereport(LOG, 2517 | (errcode_for_file_access(), 2518 | errmsg("could not write pg_stat_statement file \"%s\": %m", 2519 | pgssp_TEXT_FILE))); 2520 | qfile = NULL; 2521 | goto gc_fail; 2522 | } 2523 | 2524 | elog(DEBUG1, "pgssp gc of queries file shrunk size from %zu to %zu", 2525 | pgssp->extent, extent); 2526 | 2527 | /* Reset the shared extent pointer */ 2528 | pgssp->extent = extent; 2529 | 2530 | /* 2531 | * Also update the mean query length, to be sure that need_gc_qtexts() 2532 | * won't still think we have a problem. 2533 | */ 2534 | if (nentries > 0) 2535 | pgssp->mean_query_len = extent / nentries; 2536 | else 2537 | pgssp->mean_query_len = ASSUMED_LENGTH_INIT; 2538 | 2539 | free(qbuffer); 2540 | 2541 | /* 2542 | * OK, count a garbage collection cycle. (Note: even though we have 2543 | * exclusive lock on pgssp->lock, we must take pgssp->mutex for this, since 2544 | * other processes may examine gc_count while holding only the mutex. 2545 | * Also, we have to advance the count *after* we've rewritten the file, 2546 | * else other processes might not realize they read a stale file.) 2547 | */ 2548 | record_gc_qtexts(); 2549 | 2550 | return; 2551 | 2552 | gc_fail: 2553 | /* clean up resources */ 2554 | if (qfile) 2555 | FreeFile(qfile); 2556 | if (qbuffer) 2557 | free(qbuffer); 2558 | 2559 | /* 2560 | * Since the contents of the external file are now uncertain, mark all 2561 | * hashtable entries as having invalid texts. 2562 | */ 2563 | hash_seq_init(&hash_seq, pgssp_hash); 2564 | while ((entry = hash_seq_search(&hash_seq)) != NULL) 2565 | { 2566 | entry->query_offset = 0; 2567 | entry->query_len = -1; 2568 | } 2569 | 2570 | /* 2571 | * Destroy the query text file and create a new, empty one 2572 | */ 2573 | (void) unlink(pgssp_TEXT_FILE); 2574 | qfile = AllocateFile(pgssp_TEXT_FILE, PG_BINARY_W); 2575 | if (qfile == NULL) 2576 | ereport(LOG, 2577 | (errcode_for_file_access(), 2578 | errmsg("could not write new pg_stat_statement file \"%s\": %m", 2579 | pgssp_TEXT_FILE))); 2580 | else 2581 | FreeFile(qfile); 2582 | 2583 | /* Reset the shared extent pointer */ 2584 | pgssp->extent = 0; 2585 | 2586 | /* Reset mean_query_len to match the new state */ 2587 | pgssp->mean_query_len = ASSUMED_LENGTH_INIT; 2588 | 2589 | /* 2590 | * Bump the GC count even though we failed. 2591 | * 2592 | * This is needed to make concurrent readers of file without any lock on 2593 | * pgssp->lock notice existence of new version of file. Once readers 2594 | * subsequently observe a change in GC count with pgssp->lock held, that 2595 | * forces a safe reopen of file. Writers also require that we bump here, 2596 | * of course. (As required by locking protocol, readers and writers don't 2597 | * trust earlier file contents until gc_count is found unchanged after 2598 | * pgssp->lock acquired in shared or exclusive mode respectively.) 2599 | */ 2600 | record_gc_qtexts(); 2601 | } 2602 | 2603 | /* 2604 | * Release all entries. 2605 | */ 2606 | static void 2607 | entry_reset(void) 2608 | { 2609 | HASH_SEQ_STATUS hash_seq; 2610 | pgsspEntry *entry; 2611 | FILE *qfile; 2612 | 2613 | LWLockAcquire(pgssp->lock, LW_EXCLUSIVE); 2614 | 2615 | hash_seq_init(&hash_seq, pgssp_hash); 2616 | while ((entry = hash_seq_search(&hash_seq)) != NULL) 2617 | { 2618 | hash_search(pgssp_hash, &entry->key, HASH_REMOVE, NULL); 2619 | } 2620 | 2621 | /* 2622 | * Write new empty query file, perhaps even creating a new one to recover 2623 | * if the file was missing. 2624 | */ 2625 | qfile = AllocateFile(pgssp_TEXT_FILE, PG_BINARY_W); 2626 | if (qfile == NULL) 2627 | { 2628 | ereport(LOG, 2629 | (errcode_for_file_access(), 2630 | errmsg("could not create pg_stat_statement file \"%s\": %m", 2631 | pgssp_TEXT_FILE))); 2632 | goto done; 2633 | } 2634 | 2635 | /* If ftruncate fails, log it, but it's not a fatal problem */ 2636 | if (ftruncate(fileno(qfile), 0) != 0) 2637 | ereport(LOG, 2638 | (errcode_for_file_access(), 2639 | errmsg("could not truncate pg_stat_statement file \"%s\": %m", 2640 | pgssp_TEXT_FILE))); 2641 | 2642 | FreeFile(qfile); 2643 | 2644 | done: 2645 | pgssp->extent = 0; 2646 | /* This counts as a query text garbage collection for our purposes */ 2647 | record_gc_qtexts(); 2648 | 2649 | LWLockRelease(pgssp->lock); 2650 | } 2651 | 2652 | 2653 | /* 2654 | * Look for these operator characters in order to decide whether to strip 2655 | * whitespaces which are needless from the view of sql syntax in 2656 | * normalize_expr(). This must be synced with op_chars in scan.l. 2657 | */ 2658 | #define OPCHARS "~!@#^&|`?+-*/%<>=" 2659 | #define IS_WSCHAR(c) ((c) == ' ' || (c) == '\n' || (c) == '\t') 2660 | #define IS_CONST(tok) (tok == FCONST || tok == SCONST || tok == BCONST || \ 2661 | tok == XCONST || tok == ICONST || tok == NULL_P || \ 2662 | tok == TRUE_P || tok == FALSE_P || \ 2663 | tok == CURRENT_DATE || tok == CURRENT_TIME || \ 2664 | tok == LOCALTIME || tok == LOCALTIMESTAMP) 2665 | #define IS_INDENTED_ARRAY(v) ((v) == P_GroupKeys || (v) == P_HashKeys) 2666 | 2667 | /* 2668 | * norm_yylex: core_yylex with replacing some tokens. 2669 | */ 2670 | static int 2671 | norm_yylex(char *str, core_YYSTYPE *yylval, YYLTYPE *yylloc, core_yyscan_t yyscanner) 2672 | { 2673 | int tok; 2674 | 2675 | PG_TRY(); 2676 | { 2677 | tok = core_yylex(yylval, yylloc, yyscanner); 2678 | } 2679 | PG_CATCH(); 2680 | { 2681 | /* 2682 | * Error might occur during parsing quoted tokens that chopped 2683 | * halfway. Just ignore the rest of this query even if there might 2684 | * be other reasons for parsing to fail. 2685 | */ 2686 | FlushErrorState(); 2687 | return -1; 2688 | } 2689 | PG_END_TRY(); 2690 | 2691 | /* 2692 | * '?' alone is assumed to be an IDENT. If there's a real 2693 | * operator '?', this should be confused but there's hardly be. 2694 | */ 2695 | if (tok == Op && str[*yylloc] == '?' && 2696 | strchr(OPCHARS, str[*yylloc + 1]) == NULL) 2697 | tok = SCONST; 2698 | 2699 | /* 2700 | * Replace tokens with '=' if the operator is consists of two or 2701 | * more opchars only. Assuming that opchars do not compose a token 2702 | * with non-opchars, check the first char only is sufficient. 2703 | */ 2704 | if (tok == Op && strchr(OPCHARS, str[*yylloc]) != NULL) 2705 | tok = '='; 2706 | 2707 | return tok; 2708 | } 2709 | 2710 | /* 2711 | * normalize_expr - Normalize statements or expressions. 2712 | * 2713 | * Mask constants, strip unnecessary whitespaces and upcase keywords. expr is 2714 | * modified in-place (destructively). If readability is more important than 2715 | * uniqueness, preserve_space puts one space for one existent whitespace for 2716 | * more readability. 2717 | */ 2718 | 2719 | /* scanner interface is changed in PG12 */ 2720 | #if PG_VERSION_NUM < 120000 2721 | #define ScanKeywords (*ScanKeywords) 2722 | #define ScanKeywordTokens NumScanKeywords 2723 | #endif 2724 | 2725 | void 2726 | normalize_expr(char *expr, bool preserve_space) 2727 | { 2728 | core_yyscan_t yyscanner; 2729 | core_yy_extra_type yyextra; 2730 | core_YYSTYPE yylval; 2731 | YYLTYPE yylloc; 2732 | YYLTYPE lastloc; 2733 | YYLTYPE start; 2734 | char *wp; 2735 | int tok, lasttok; 2736 | 2737 | wp = expr; 2738 | yyscanner = scanner_init(expr, 2739 | &yyextra, 2740 | &ScanKeywords, 2741 | ScanKeywordTokens); 2742 | 2743 | /* 2744 | * The warnings about nonstandard escape strings is already emitted in the 2745 | * core. Just silence them here. 2746 | */ 2747 | #if PG_VERSION_NUM >= 90500 2748 | yyextra.escape_string_warning = false; 2749 | #endif 2750 | lasttok = 0; 2751 | lastloc = -1; 2752 | 2753 | for (;;) 2754 | { 2755 | tok = norm_yylex(expr, &yylval, &yylloc, yyscanner); 2756 | 2757 | start = yylloc; 2758 | 2759 | if (lastloc >= 0) 2760 | { 2761 | int i, i2; 2762 | 2763 | /* Skipping preceding whitespaces */ 2764 | for(i = lastloc ; i < start && IS_WSCHAR(expr[i]) ; i++); 2765 | 2766 | /* Searching for trailing whitespace */ 2767 | for(i2 = i; i2 < start && !IS_WSCHAR(expr[i2]) ; i2++); 2768 | 2769 | if (lasttok == IDENT) 2770 | { 2771 | /* Identifiers are copied in case-sensitive manner. */ 2772 | memcpy(wp, expr + i, i2 - i); 2773 | wp += i2 - i; 2774 | } 2775 | else 2776 | { 2777 | /* Upcase keywords */ 2778 | char *sp; 2779 | for (sp = expr + i ; sp < expr + i2 ; sp++, wp++) 2780 | *wp = (*sp >= 'a' && *sp <= 'z' ? 2781 | *sp - ('a' - 'A') : *sp); 2782 | } 2783 | 2784 | /* 2785 | * Because of destructive writing, wp must not go advance the 2786 | * reading point. 2787 | * Although this function's output does not need any validity as a 2788 | * statement or an expression, spaces are added where it should be 2789 | * to keep some extent of sanity. If readability is more important 2790 | * than uniqueness, preserve_space adds one space for each 2791 | * existent whitespace. 2792 | */ 2793 | if (tok > 0 && 2794 | i2 < start && 2795 | (preserve_space || 2796 | (tok >= IDENT && lasttok >= IDENT && 2797 | !IS_CONST(tok) && !IS_CONST(lasttok)))) 2798 | *wp++ = ' '; 2799 | 2800 | start = i2; 2801 | } 2802 | 2803 | /* Exit on parse error. */ 2804 | if (tok < 0) 2805 | { 2806 | *wp = 0; 2807 | return; 2808 | } 2809 | 2810 | /* 2811 | * Negative signs before numbers are tokenized separately. And 2812 | * explicit positive signs won't appear in deparsed expressions. 2813 | */ 2814 | if (tok == '-') 2815 | tok = norm_yylex(expr, &yylval, &yylloc, yyscanner); 2816 | 2817 | /* Exit on parse error. */ 2818 | if (tok < 0) 2819 | { 2820 | *wp = 0; 2821 | return; 2822 | } 2823 | 2824 | if (IS_CONST(tok)) 2825 | { 2826 | YYLTYPE end; 2827 | 2828 | tok = norm_yylex(expr, &yylval, &end, yyscanner); 2829 | 2830 | /* Exit on parse error. */ 2831 | if (tok < 0) 2832 | { 2833 | *wp = 0; 2834 | return; 2835 | } 2836 | 2837 | /* 2838 | * Negative values may be surrounded with parens by the 2839 | * deparser. Mask involving them. 2840 | */ 2841 | if (lasttok == '(' && tok == ')') 2842 | { 2843 | wp -= (start - lastloc); 2844 | start = lastloc; 2845 | end++; 2846 | } 2847 | 2848 | while (expr[end - 1] == ' ') end--; 2849 | 2850 | *wp++ = '?'; 2851 | yylloc = end; 2852 | } 2853 | 2854 | if (tok == 0) 2855 | break; 2856 | 2857 | lasttok = tok; 2858 | lastloc = yylloc; 2859 | } 2860 | *wp = 0; 2861 | } 2862 | 2863 | static uint64 2864 | hash_query(const char* query) 2865 | { 2866 | uint64 queryid; 2867 | 2868 | char *normquery = pstrdup(query); 2869 | normalize_expr(normquery, true); 2870 | queryid = DatumGetUInt64(hash_any_extended((const unsigned char*)normquery, strlen(normquery),0)); 2871 | pfree(normquery); 2872 | 2873 | return queryid; 2874 | } 2875 | 2876 | 2877 | Datum 2878 | pgssp_normalize_query(PG_FUNCTION_ARGS) 2879 | { 2880 | text *query = PG_GETARG_TEXT_P(0); 2881 | char *cquery = text_to_cstring(query); 2882 | char *normquery = pstrdup(cquery); 2883 | normalize_expr(normquery, true); 2884 | PG_RETURN_TEXT_P(cstring_to_text(normquery)); 2885 | pfree(normquery); 2886 | } 2887 | 2888 | Datum 2889 | pgssp_backend_qpid(PG_FUNCTION_ARGS) 2890 | { 2891 | if(pgssp_track_pid) 2892 | { 2893 | int i; 2894 | 2895 | for (i = 0; i < ProcGlobal->allProcCount; i++) 2896 | { 2897 | PGPROC *proc = &ProcGlobal->allProcs[i]; 2898 | if (proc != NULL && proc->pid != 0 && proc->pid == PG_GETARG_INT32(0)) 2899 | { 2900 | return ProcEntryArray[i].qpid; 2901 | } 2902 | } 2903 | return 0; 2904 | } 2905 | else 2906 | return -1; 2907 | } 2908 | 2909 | //TODO reset existing ProcEntryArray[i].qpid when pgssp_track_pid is enabled 2910 | -------------------------------------------------------------------------------- /pg_stat_sql_plans.control: -------------------------------------------------------------------------------- 1 | # pg_stat_sql_plans extension 2 | comment = 'track execution statistics of all SQL statements executed' 3 | default_version = '0.2' 4 | module_pathname = '$libdir/pg_stat_sql_plans' 5 | relocatable = true 6 | --------------------------------------------------------------------------------