From 56d8375c38fb8a5df926e4d27c44fc2a26e549b6 Mon Sep 17 00:00:00 2001 From: Ibrar Ahmed Date: Wed, 20 Nov 2019 10:30:57 +0000 Subject: [PATCH] Issue - (#2): Extended pg_stat_statement to provide new features. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support for database/user/client based aggregates added to access these statistics with three new views added. Some new counters added including min/max/mean's time histograms. We are saving the parameters of the slow queries, which can be tested later. Did some refactoring of the code, by renaming the whole extension from pg_stat_statement to pg_stat_monitor. --- Makefile | 25 +- README.md | 0 ...tat_statements.out => pg_stat_monitor.out} | 0 pg_stat_monitor--1.0.sql | 142 +++++ pg_stat_statements.c => pg_stat_monitor.c | 599 +++++++++++++++--- pg_stat_monitor.conf | 1 + pg_stat_monitor.control | 5 + pg_stat_statements--1.0--1.1.sql | 42 -- pg_stat_statements--1.1--1.2.sql | 43 -- pg_stat_statements--1.2--1.3.sql | 47 -- pg_stat_statements--1.3--1.4.sql | 7 - pg_stat_statements--1.4--1.5.sql | 6 - pg_stat_statements--1.4.sql | 48 -- pg_stat_statements--1.5--1.6.sql | 7 - pg_stat_statements--unpackaged--1.0.sql | 8 - pg_stat_statements.conf | 1 - pg_stat_statements.control | 5 - ...tat_statements.sql => pg_stat_monitor.sql} | 0 18 files changed, 658 insertions(+), 328 deletions(-) create mode 100644 README.md rename expected/{pg_stat_statements.out => pg_stat_monitor.out} (100%) create mode 100644 pg_stat_monitor--1.0.sql rename pg_stat_statements.c => pg_stat_monitor.c (83%) create mode 100644 pg_stat_monitor.conf create mode 100644 pg_stat_monitor.control delete mode 100644 pg_stat_statements--1.0--1.1.sql delete mode 100644 pg_stat_statements--1.1--1.2.sql delete mode 100644 pg_stat_statements--1.2--1.3.sql delete mode 100644 pg_stat_statements--1.3--1.4.sql delete mode 100644 pg_stat_statements--1.4--1.5.sql delete mode 100644 pg_stat_statements--1.4.sql delete mode 100644 pg_stat_statements--1.5--1.6.sql delete mode 100644 pg_stat_statements--unpackaged--1.0.sql delete mode 100644 pg_stat_statements.conf delete mode 100644 pg_stat_statements.control rename sql/{pg_stat_statements.sql => pg_stat_monitor.sql} (100%) diff --git a/Makefile b/Makefile index 14a50380dcb0..a6e622c6c07f 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,19 @@ -# contrib/pg_stat_statements/Makefile +# contrib/pg_stat_monitor/Makefile -MODULE_big = pg_stat_statements -OBJS = pg_stat_statements.o $(WIN32RES) +MODULE_big = pg_stat_monitor +OBJS = pg_stat_monitor.o $(WIN32RES) -EXTENSION = pg_stat_statements -DATA = pg_stat_statements--1.4.sql pg_stat_statements--1.5--1.6.sql \ - pg_stat_statements--1.4--1.5.sql pg_stat_statements--1.3--1.4.sql \ - pg_stat_statements--1.2--1.3.sql pg_stat_statements--1.1--1.2.sql \ - pg_stat_statements--1.0--1.1.sql \ - pg_stat_statements--unpackaged--1.0.sql -PGFILEDESC = "pg_stat_statements - execution statistics of SQL statements" +EXTENSION = pg_stat_monitor + +DATA = pg_stat_monitor--1.0.sql + +PGFILEDESC = "pg_stat_monitor - execution statistics of SQL statements" LDFLAGS_SL += $(filter -lm, $(LIBS)) -REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/pg_stat_statements/pg_stat_statements.conf -REGRESS = pg_stat_statements +REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/pg_stat_monitor/pg_stat_monitor.conf +REGRESS = pg_stat_monitor + # Disabled because these tests require "shared_preload_libraries=pg_stat_statements", # which typical installcheck users do not have (e.g. buildfarm clients). NO_INSTALLCHECK = 1 @@ -24,7 +23,7 @@ PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) else -subdir = contrib/pg_stat_statements +subdir = contrib/pg_stat_monitor top_builddir = ../.. include $(top_builddir)/src/Makefile.global include $(top_srcdir)/contrib/contrib-global.mk diff --git a/README.md b/README.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/expected/pg_stat_statements.out b/expected/pg_stat_monitor.out similarity index 100% rename from expected/pg_stat_statements.out rename to expected/pg_stat_monitor.out diff --git a/pg_stat_monitor--1.0.sql b/pg_stat_monitor--1.0.sql new file mode 100644 index 000000000000..8f742272e9a9 --- /dev/null +++ b/pg_stat_monitor--1.0.sql @@ -0,0 +1,142 @@ +/* contrib/pg_stat_monitor/pg_stat_monitor--1.4.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pg_stat_monitor" to load this file. \quit + +-- Register functions. +CREATE FUNCTION pg_stat_monitor_reset() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION pg_stat_monitor(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT queryid bigint, + OUT query text, + OUT calls int8, + OUT total_time float8, + OUT min_time float8, + OUT max_time float8, + OUT mean_time float8, + OUT stddev_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT host int, + OUT hist_calls text, + OUT hist_min_time text, + OUT hist_max_time text, + OUT hist_mean_time text, + OUT slow_query text, + OUT cpu_user_time float8, + OUT cpu_sys_time float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_monitor_1_3' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE FUNCTION pg_stat_agg( + OUT queryid bigint, + OUT id bigint, + OUT type bigint, + OUT total_calls int, + OUT first_call_time timestamptz, + OUT last_call_time timestamptz) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_agg' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE VIEW pg_stat_monitor AS + SELECT * FROM pg_stat_monitor(true); + +GRANT SELECT ON pg_stat_monitor TO PUBLIC; + +CREATE VIEW pg_stat_agg_database AS +SELECT + agg.queryid, + agg.id AS dbid, + ss.userid, + '0.0.0.0'::inet + ss.host AS host, + agg.total_calls, + ss.min_time, + ss.max_time, + ss.mean_time, + (string_to_array(hist_calls, ',')) hist_calls, + (string_to_array(hist_min_time, ',')) hist_min_time, + (string_to_array(hist_max_time, ',')) hist_max_time, + (string_to_array(hist_mean_time, ',')) hist_mean_time, + agg.first_call_time AS first_log_time, + agg.last_call_time AS last_log_time, + ss.cpu_user_time, + ss.cpu_sys_time, + ss.query, + ss.slow_query +FROM pg_stat_agg() agg +INNER JOIN (SELECT DISTINCT queryid, userid, query, host, min_time, max_time, mean_time, hist_calls, hist_min_time, hist_max_time,hist_mean_time,slow_query,cpu_user_time,cpu_sys_time +FROM pg_stat_monitor) ss +ON agg.queryid = ss.queryid AND agg.type = 0; + +CREATE VIEW pg_stat_agg_user AS +SELECT + agg.queryid, + agg.id AS dbid, + ss.userid, + '0.0.0.0'::inet + ss.host AS host, + agg.total_calls, + ss.min_time, + ss.max_time, + ss.mean_time, + (string_to_array(hist_calls, ',')) hist_calls, + (string_to_array(hist_min_time, ',')) hist_min_time, + (string_to_array(hist_max_time, ',')) hist_max_time, + (string_to_array(hist_mean_time, ',')) hist_mean_time, + agg.first_call_time AS first_log_time, + agg.last_call_time AS last_log_time, + ss.cpu_user_time, + ss.cpu_sys_time, + ss.query, + ss.slow_query +FROM pg_stat_agg() agg +INNER JOIN (SELECT DISTINCT queryid, userid, query, host, min_time, max_time, mean_time, hist_calls, hist_min_time, hist_max_time,hist_mean_time,slow_query,cpu_user_time,cpu_sys_time FROM pg_stat_monitor) ss +ON agg.queryid = ss.queryid AND agg.type = 1; + +CREATE VIEW pg_stat_agg_host AS +SELECT + agg.queryid, + agg.id AS dbid, + ss.userid, + '0.0.0.0'::inet + ss.host AS host, + agg.total_calls, + ss.min_time, + ss.max_time, + ss.mean_time, + (string_to_array(hist_calls, ',')) hist_calls, + (string_to_array(hist_min_time, ',')) hist_min_time, + (string_to_array(hist_max_time, ',')) hist_max_time, + (string_to_array(hist_mean_time, ',')) hist_mean_time, + agg.first_call_time AS first_log_time, + agg.last_call_time AS last_log_time, + ss.cpu_user_time, + ss.cpu_sys_time, + ss.query, + ss.slow_query +FROM pg_stat_agg() agg +INNER JOIN (SELECT DISTINCT queryid, userid, query, host, min_time, max_time, mean_time, hist_calls, hist_min_time, hist_max_time,hist_mean_time,slow_query,cpu_user_time,cpu_sys_time FROM pg_stat_monitor) ss +ON agg.queryid = ss.queryid AND agg.type = 2; + +GRANT SELECT ON pg_stat_agg_database TO PUBLIC; + +-- Don't want this to be available to non-superusers. +REVOKE ALL ON FUNCTION pg_stat_monitor_reset() FROM PUBLIC; diff --git a/pg_stat_statements.c b/pg_stat_monitor.c similarity index 83% rename from pg_stat_statements.c rename to pg_stat_monitor.c index cc9efab2431b..518bde46d18a 100644 --- a/pg_stat_statements.c +++ b/pg_stat_monitor.c @@ -1,6 +1,6 @@ /*------------------------------------------------------------------------- * - * pg_stat_statements.c + * pg_stat_monitor.c * Track statement execution times across a whole database cluster. * * Execution costs are totalled for each distinct source query, and kept in @@ -51,19 +51,23 @@ * Copyright (c) 2008-2018, PostgreSQL Global Development Group * * IDENTIFICATION - * contrib/pg_stat_statements/pg_stat_statements.c + * contrib/pg_stat_monitor/pg_stat_monitor.c * *------------------------------------------------------------------------- */ #include "postgres.h" +#include #include #include #include +#include +#include #include "access/hash.h" #include "catalog/pg_authid.h" #include "executor/instrument.h" +#include "common/ip.h" #include "funcapi.h" #include "mb/pg_wchar.h" #include "miscadmin.h" @@ -79,11 +83,22 @@ #include "utils/acl.h" #include "utils/builtins.h" #include "utils/memutils.h" +#include "utils/timestamp.h" PG_MODULE_MAGIC; + +/* Maximum length of the stord query (with actual values) in shared mememory, */ +#define MAX_QUERY_LEN 255 + +/* Time difference in miliseconds */ +#define TIMEVAL_DIFF(start, end) (((double) end.tv_sec + (double) end.tv_usec / 1000000.0) \ + - ((double) start.tv_sec + (double) start.tv_usec / 1000000.0)) * 1000 + + /* Location of permanent stats file (valid when database is shut down) */ -#define PGSS_DUMP_FILE PGSTAT_STAT_PERMANENT_DIRECTORY "/pg_stat_statements.stat" +#define PGSS_DUMP_FILE PGSTAT_STAT_PERMANENT_DIRECTORY "/pg_stat_monitor.stat" +#define ArrayGetTextDatum(x) arry_get_datum(x) /* * Location of external query text file. We don't keep it in the core @@ -93,7 +108,7 @@ PG_MODULE_MAGIC; * race conditions. Besides, we only expect modest, infrequent I/O for query * strings, so placing the file on a faster filesystem is not compelling. */ -#define PGSS_TEXT_FILE PG_STAT_TMP_DIR "/pgss_query_texts.stat" +#define PGSS_TEXT_FILE PG_STAT_TMP_DIR "/pgsm_query_texts.stat" /* Magic number identifying the stats file format */ static const uint32 PGSS_FILE_HEADER = 0x20171004; @@ -120,9 +135,31 @@ typedef enum pgssVersion PGSS_V1_0 = 0, PGSS_V1_1, PGSS_V1_2, - PGSS_V1_3 + PGSS_V1_3, + PGSS_V1_3_EXTENDED_1, /* Extended verion based on 3.1 */ } pgssVersion; +typedef struct pgssAggHashKey +{ + uint64 queryid; /* query identifier */ + uint64 id; /* dbid, userid or ip depend upon the type */ + uint64 type; /* query identifier */ +} pgssAggHashKey; + +typedef struct pgssAggCounters +{ + uint64 total_calls; /* number of quries per database/user/ip */ + Timestamp first_call_time; /* first stats collection time */ + Timestamp last_call_time; /* last stats collection time */ +} pgssAggCounters; + +typedef struct pgssAggEntry +{ + pgssAggHashKey key; /* hash key of entry - MUST BE FIRST */ + pgssAggCounters counters; /* the statistics aggregates */ + slock_t mutex; /* protects the counters only */ +} pgssAggEntry; + /* * Hashtable key that defines the identity of a hashtable entry. We separate * queries by user and by database even if they are otherwise identical. @@ -134,8 +171,8 @@ typedef enum pgssVersion */ typedef struct pgssHashKey { - Oid userid; /* user OID */ - Oid dbid; /* database OID */ + Oid userid; /* user OID */ + Oid dbid; /* database OID */ uint64 queryid; /* query identifier */ } pgssHashKey; @@ -149,23 +186,39 @@ typedef struct Counters double min_time; /* minimum execution time in msec */ double max_time; /* maximum execution time in msec */ double mean_time; /* mean execution time in msec */ - double sum_var_time; /* sum of variances in execution time in msec */ + double sum_var_time; /* sum of variances in execution time in msec */ int64 rows; /* total # of retrieved or affected rows */ int64 shared_blks_hit; /* # of shared buffer hits */ int64 shared_blks_read; /* # of shared disk blocks read */ int64 shared_blks_dirtied; /* # of shared disk blocks dirtied */ int64 shared_blks_written; /* # of shared disk blocks written */ - int64 local_blks_hit; /* # of local buffer hits */ + int64 local_blks_hit; /* # of local buffer hits */ int64 local_blks_read; /* # of local disk blocks read */ - int64 local_blks_dirtied; /* # of local disk blocks dirtied */ - int64 local_blks_written; /* # of local disk blocks written */ - int64 temp_blks_read; /* # of temp blocks read */ + int64 local_blks_dirtied; /* # of local disk blocks dirtied */ + int64 local_blks_written; /* # of local disk blocks written */ + int64 temp_blks_read; /* # of temp blocks read */ int64 temp_blks_written; /* # of temp blocks written */ - double blk_read_time; /* time spent reading, in msec */ - double blk_write_time; /* time spent writing, in msec */ + double blk_read_time; /* time spent reading, in msec */ + double blk_write_time; /* time spent writing, in msec */ double usage; /* usage factor */ + + /* Extra counters for extended version */ + uint host; /* client IP */ + Timestamp first_call_time; /* first stats collection time */ + Timestamp last_call_time; /* last stats collection time */ + double hist_calls[24]; /* execution time's histogram in msec */ + double hist_min_time[24]; /* min execution time's histogram in msec */ + double hist_max_time[24]; /* max execution time's histogram in msec */ + double hist_mean_time[24]; /* mean execution time's histogram in msec */ + char slow_query[MAX_QUERY_LEN]; /* slowes query */ + float utime; /* user cpu time */ + float stime; /* system cpu time */ } Counters; +/* Some global structure to get the cpu usage, really don't like the idea of global variable */ +static struct rusage rusage_start; +static struct rusage rusage_end; + /* * Statistics per statement * @@ -177,9 +230,9 @@ typedef struct pgssEntry { pgssHashKey key; /* hash key of entry - MUST BE FIRST */ Counters counters; /* the statistics for this query */ - Size query_offset; /* query text offset in external file */ - int query_len; /* # of valid bytes in query string, or -1 */ - int encoding; /* query text encoding */ + int query_offset; /* query text offset in external file */ + int query_len; /* # of valid bytes in query string, or -1 */ + int encoding; /* query text encoding */ slock_t mutex; /* protects the counters only */ } pgssEntry; @@ -188,13 +241,13 @@ typedef struct pgssEntry */ typedef struct pgssSharedState { - LWLock *lock; /* protects hashtable search/modification */ + LWLock *lock; /* protects hashtable search/modification */ double cur_median_usage; /* current median usage in hashtable */ - Size mean_query_len; /* current mean entry text length */ + Size mean_query_len; /* current mean entry text length */ slock_t mutex; /* protects following fields only: */ Size extent; /* current extent of query file */ - int n_writers; /* number of active writers to query file */ - int gc_count; /* query file garbage collection cycle count */ + int n_writers; /* number of active writers to query file */ + int gc_count; /* query file garbage collection cycle count */ } pgssSharedState; /* @@ -249,6 +302,9 @@ static ProcessUtility_hook_type prev_ProcessUtility = NULL; static pgssSharedState *pgss = NULL; static HTAB *pgss_hash = NULL; +/* Hash table for aggegates */ +static HTAB *pgss_agghash = NULL; + /*---- GUC variables ----*/ typedef enum @@ -266,10 +322,10 @@ static const struct config_enum_entry track_options[] = {NULL, 0, false} }; -static int pgss_max; /* max # statements to track */ -static int pgss_track; /* tracking level */ -static bool pgss_track_utility; /* whether to track utility commands */ -static bool pgss_save; /* whether to save stats across shutdown */ +static int pgss_max; /* max # statements to track */ +static int pgss_track; /* tracking level */ +static bool pgss_track_utility; /* whether to track utility commands */ +static bool pgss_save; /* whether to save stats across shutdown */ #define pgss_enabled() \ @@ -289,10 +345,19 @@ static bool pgss_save; /* whether to save stats across shutdown */ void _PG_init(void); void _PG_fini(void); -PG_FUNCTION_INFO_V1(pg_stat_statements_reset); -PG_FUNCTION_INFO_V1(pg_stat_statements_1_2); -PG_FUNCTION_INFO_V1(pg_stat_statements_1_3); -PG_FUNCTION_INFO_V1(pg_stat_statements); +PG_FUNCTION_INFO_V1(pg_stat_monitor_reset); +PG_FUNCTION_INFO_V1(pg_stat_monitor_1_2); +PG_FUNCTION_INFO_V1(pg_stat_monitor_1_3); +PG_FUNCTION_INFO_V1(pg_stat_monitor); + + +/* Extended version function prototypes */ +PG_FUNCTION_INFO_V1(pg_stat_agg); +static uint pg_get_client_addr(); +static Datum arry_get_datum(double arr[]); +static void update_agg_counters(uint64 queryid, uint64 id, uint64 type); +static void hash_remove_agg(uint64 queryid); +static pgssAggEntry *agg_entry_alloc(pgssAggHashKey *key); static void pgss_shmem_startup(void); static void pgss_shmem_shutdown(int code, Datum arg); @@ -311,14 +376,15 @@ static uint64 pgss_hash_string(const char *str, int len); static void pgss_store(const char *query, uint64 queryId, int query_location, int query_len, double total_time, uint64 rows, - const BufferUsage *bufusage, + const BufferUsage *bufusage, float utime, float stime, pgssJumbleState *jstate); -static void pg_stat_statements_internal(FunctionCallInfo fcinfo, +static void pg_stat_monitor_internal(FunctionCallInfo fcinfo, pgssVersion api_version, bool showtext); static Size pgss_memsize(void); static pgssEntry *entry_alloc(pgssHashKey *key, Size query_offset, int query_len, int encoding, bool sticky); + static void entry_dealloc(void); static bool qtext_store(const char *query, int query_len, Size *query_offset, int *gc_count); @@ -361,21 +427,20 @@ _PG_init(void) /* * Define (or redefine) custom GUC variables. */ - DefineCustomIntVariable("pg_stat_statements.max", - "Sets the maximum number of statements tracked by pg_stat_statements.", + DefineCustomIntVariable("pg_stat_monitor.max", + "Sets the maximum number of statements tracked by pg_stat_monitor.", NULL, &pgss_max, 5000, - 100, + 5, INT_MAX, PGC_POSTMASTER, 0, NULL, NULL, NULL); - - DefineCustomEnumVariable("pg_stat_statements.track", - "Selects which statements are tracked by pg_stat_statements.", + DefineCustomEnumVariable("pg_stat_monitor.track", + "Selects which statements are tracked by pg_stat_monitor.", NULL, &pgss_track, PGSS_TRACK_TOP, @@ -386,8 +451,8 @@ _PG_init(void) NULL, NULL); - DefineCustomBoolVariable("pg_stat_statements.track_utility", - "Selects whether utility commands are tracked by pg_stat_statements.", + DefineCustomBoolVariable("pg_stat_monitor.track_utility", + "Selects whether utility commands are tracked by pg_stat_monitor.", NULL, &pgss_track_utility, true, @@ -397,8 +462,8 @@ _PG_init(void) NULL, NULL); - DefineCustomBoolVariable("pg_stat_statements.save", - "Save pg_stat_statements statistics across server shutdowns.", + DefineCustomBoolVariable("pg_stat_monitor.save", + "Save pg_stat_monitor statistics across server shutdowns.", NULL, &pgss_save, true, @@ -408,7 +473,7 @@ _PG_init(void) NULL, NULL); - EmitWarningsOnPlaceholders("pg_stat_statements"); + EmitWarningsOnPlaceholders("pg_stat_monitor"); /* * Request additional shared resources. (These are no-ops if we're not in @@ -416,7 +481,7 @@ _PG_init(void) * resources in pgss_shmem_startup(). */ RequestAddinShmemSpace(pgss_memsize()); - RequestNamedLWLockTranche("pg_stat_statements", 1); + RequestNamedLWLockTranche("pg_stat_monitor", 1); /* * Install hooks. @@ -462,16 +527,17 @@ _PG_fini(void) static void pgss_shmem_startup(void) { - bool found; + bool found = false; HASHCTL info; - FILE *file = NULL; - FILE *qfile = NULL; + FILE *file = NULL; + FILE *qfile = NULL; uint32 header; int32 num; + int32 agg_num; int32 pgver; int32 i; - int buffer_size; - char *buffer = NULL; + int buffer_size; + char *buffer = NULL; if (prev_shmem_startup_hook) prev_shmem_startup_hook(); @@ -479,20 +545,18 @@ pgss_shmem_startup(void) /* reset in case this is a restart within the postmaster */ pgss = NULL; pgss_hash = NULL; + pgss_agghash = NULL; /* * Create or attach to the shared memory state, including hash table */ LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); - pgss = ShmemInitStruct("pg_stat_statements", - sizeof(pgssSharedState), - &found); - + pgss = ShmemInitStruct("pg_stat_monitor", sizeof(pgssSharedState), &found); if (!found) { /* First time through ... */ - pgss->lock = &(GetNamedLWLockTranche("pg_stat_statements"))->lock; + pgss->lock = &(GetNamedLWLockTranche("pg_stat_monitor"))->lock; pgss->cur_median_usage = ASSUMED_MEDIAN_INIT; pgss->mean_query_len = ASSUMED_LENGTH_INIT; SpinLockInit(&pgss->mutex); @@ -504,11 +568,24 @@ pgss_shmem_startup(void) memset(&info, 0, sizeof(info)); info.keysize = sizeof(pgssHashKey); info.entrysize = sizeof(pgssEntry); - pgss_hash = ShmemInitHash("pg_stat_statements hash", + pgss_hash = ShmemInitHash("pg_stat_monitor hash", pgss_max, pgss_max, &info, HASH_ELEM | HASH_BLOBS); + memset(&info, 0, sizeof(info)); + info.keysize = sizeof(pgssAggHashKey); + info.entrysize = sizeof(pgssAggEntry); + + /* + * Create a aggregate hash 3 times than the the normal hash because we have + * three different type of aggregate stored in the aggregate hash. Aggregate + * by database, aggraget by user and aggragete by host. + */ + pgss_agghash = ShmemInitHash("pg_stat_monitor aggrage_hash", + pgss_max * 3, pgss_max * 3, + &info, + HASH_ELEM | HASH_BLOBS); LWLockRelease(AddinShmemInitLock); /* @@ -566,7 +643,8 @@ pgss_shmem_startup(void) if (fread(&header, sizeof(uint32), 1, file) != 1 || fread(&pgver, sizeof(uint32), 1, file) != 1 || - fread(&num, sizeof(int32), 1, file) != 1) + fread(&num, sizeof(int32), 1, file) != 1 || + fread(&agg_num, sizeof(int32), 1, file) != 1) goto read_error; if (header != PGSS_FILE_HEADER || @@ -618,6 +696,19 @@ pgss_shmem_startup(void) entry->counters = temp.counters; } + /* Read the aggregates information from the file. */ + for (i = 0; i < agg_num; i++) + { + pgssAggEntry temp; + pgssAggEntry *entry; + + if (fread(&temp, sizeof(pgssAggEntry), 1, file) != 1) + goto read_error; + + entry = agg_entry_alloc(&temp.key); + memcpy(entry, &temp, sizeof(pgssAggEntry)); + } + pfree(buffer); FreeFile(file); FreeFile(qfile); @@ -642,19 +733,19 @@ pgss_shmem_startup(void) read_error: ereport(LOG, (errcode_for_file_access(), - errmsg("could not read pg_stat_statement file \"%s\": %m", + errmsg("could not read pg_stat_monitor file \"%s\": %m", PGSS_DUMP_FILE))); goto fail; data_error: ereport(LOG, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("ignoring invalid data in pg_stat_statement file \"%s\"", + errmsg("ignoring invalid data in pg_stat_monitor file \"%s\"", PGSS_DUMP_FILE))); goto fail; write_error: ereport(LOG, (errcode_for_file_access(), - errmsg("could not write pg_stat_statement file \"%s\": %m", + errmsg("could not write pg_stat_monitor file \"%s\": %m", PGSS_TEXT_FILE))); fail: if (buffer) @@ -681,12 +772,13 @@ pgss_shmem_startup(void) static void pgss_shmem_shutdown(int code, Datum arg) { - FILE *file; - char *qbuffer = NULL; + FILE *file; + char *qbuffer = NULL; Size qbuffer_size = 0; HASH_SEQ_STATUS hash_seq; int32 num_entries; - pgssEntry *entry; + pgssEntry *entry; + pgssAggEntry *aggentry; /* Don't try to dump during a crash. */ if (code) @@ -706,12 +798,18 @@ pgss_shmem_shutdown(int code, Datum arg) if (fwrite(&PGSS_FILE_HEADER, sizeof(uint32), 1, file) != 1) goto error; + if (fwrite(&PGSS_PG_MAJOR_VERSION, sizeof(uint32), 1, file) != 1) goto error; + num_entries = hash_get_num_entries(pgss_hash); if (fwrite(&num_entries, sizeof(int32), 1, file) != 1) goto error; + num_entries = hash_get_num_entries(pgss_agghash); + if (fwrite(&num_entries, sizeof(int32), 1, file) != 1) + goto error; + qbuffer = qtext_load_file(&qbuffer_size); if (qbuffer == NULL) goto error; @@ -723,8 +821,8 @@ pgss_shmem_shutdown(int code, Datum arg) hash_seq_init(&hash_seq, pgss_hash); while ((entry = hash_seq_search(&hash_seq)) != NULL) { - int len = entry->query_len; - char *qstr = qtext_fetch(entry->query_offset, len, + int len = entry->query_len; + char *qstr = qtext_fetch(entry->query_offset, len, qbuffer, qbuffer_size); if (qstr == NULL) @@ -739,6 +837,20 @@ pgss_shmem_shutdown(int code, Datum arg) } } + hash_seq_init(&hash_seq, pgss_agghash); + while ((aggentry = hash_seq_search(&hash_seq)) != NULL) + { + if (fwrite(aggentry, sizeof(pgssAggEntry), 1, file) != 1) + { + /* note: we assume hash_seq_term won't change errno */ + hash_seq_term(&hash_seq); + goto error; + } + } + + free(qbuffer); + qbuffer = NULL; + free(qbuffer); qbuffer = NULL; @@ -761,7 +873,7 @@ pgss_shmem_shutdown(int code, Datum arg) error: ereport(LOG, (errcode_for_file_access(), - errmsg("could not write pg_stat_statement file \"%s\": %m", + errmsg("could not write pg_stat_monitor file \"%s\": %m", PGSS_DUMP_FILE ".tmp"))); if (qbuffer) free(qbuffer); @@ -839,6 +951,8 @@ pgss_post_parse_analyze(ParseState *pstate, Query *query) 0, 0, NULL, + 0, + 0, &jstate); } @@ -848,6 +962,8 @@ pgss_post_parse_analyze(ParseState *pstate, Query *query) static void pgss_ExecutorStart(QueryDesc *queryDesc, int eflags) { + getrusage(RUSAGE_SELF, &rusage_start); + if (prev_ExecutorStart) prev_ExecutorStart(queryDesc, eflags); else @@ -929,7 +1045,9 @@ pgss_ExecutorFinish(QueryDesc *queryDesc) static void pgss_ExecutorEnd(QueryDesc *queryDesc) { - uint64 queryId = queryDesc->plannedstmt->queryId; + uint64 queryId = queryDesc->plannedstmt->queryId; + float utime; + float stime; if (queryId != UINT64CONST(0) && queryDesc->totaltime && pgss_enabled()) { @@ -938,7 +1056,9 @@ pgss_ExecutorEnd(QueryDesc *queryDesc) * levels of hook all do this.) */ InstrEndLoop(queryDesc->totaltime); - + getrusage(RUSAGE_SELF, &rusage_end); + utime = TIMEVAL_DIFF(rusage_start.ru_utime, rusage_end.ru_utime); + stime = TIMEVAL_DIFF(rusage_start.ru_stime, rusage_end.ru_stime); pgss_store(queryDesc->sourceText, queryId, queryDesc->plannedstmt->stmt_location, @@ -946,6 +1066,8 @@ pgss_ExecutorEnd(QueryDesc *queryDesc) queryDesc->totaltime->total * 1000.0, /* convert to msec */ queryDesc->estate->es_processed, &queryDesc->totaltime->bufusage, + utime, + stime, NULL); } @@ -953,6 +1075,7 @@ pgss_ExecutorEnd(QueryDesc *queryDesc) prev_ExecutorEnd(queryDesc); else standard_ExecutorEnd(queryDesc); + } /* @@ -1057,6 +1180,8 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, INSTR_TIME_GET_MILLISEC(duration), rows, &bufusage, + 0, + 0, NULL); } else @@ -1084,6 +1209,42 @@ pgss_hash_string(const char *str, int len) len, 0)); } +static uint +pg_get_client_addr() +{ + char remote_host[NI_MAXHOST]; + int num_backends = pgstat_fetch_stat_numbackends(); + int ret; + int i; + + memset(remote_host, 0x0, NI_MAXHOST); + for (i = 1; i <= num_backends; i++) + { + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + + local_beentry = pgstat_fetch_stat_local_beentry(i); + beentry = &local_beentry->backendStatus; + + if (beentry->st_procpid == MyProcPid) + { + ret = pg_getnameinfo_all(&beentry->st_clientaddr.addr, + beentry->st_clientaddr.salen, + remote_host, sizeof(remote_host), + NULL, 0, + NI_NUMERICHOST | NI_NUMERICSERV); + if (ret == 0) + break; + else + return ntohl(inet_addr("127.0.0.1")); + } + } + if (strcmp(remote_host, "[local]") == 0) + return ntohl(inet_addr("127.0.0.1")); + return ntohl(inet_addr(remote_host)); +} + + /* * Store some statistics for a statement. * @@ -1099,12 +1260,16 @@ pgss_store(const char *query, uint64 queryId, int query_location, int query_len, double total_time, uint64 rows, const BufferUsage *bufusage, - pgssJumbleState *jstate) + float utime, float stime, pgssJumbleState *jstate) { - pgssHashKey key; - pgssEntry *entry; - char *norm_query = NULL; - int encoding = GetDatabaseEncoding(); + pgssHashKey key; + pgssEntry *entry; + char *norm_query = NULL; + double old_mean; + int encoding = GetDatabaseEncoding(); + time_t t = time(NULL); + struct tm tm = *localtime(&t); + int current_hour = tm.tm_hour; Assert(query != NULL); @@ -1227,6 +1392,7 @@ pgss_store(const char *query, uint64 queryId, /* Increment the counts, except when jstate is not NULL */ if (!jstate) { + int i; /* * Grab the spinlock while updating the counters (see comment about * locking rules at the head of the file) @@ -1235,6 +1401,11 @@ pgss_store(const char *query, uint64 queryId, SpinLockAcquire(&e->mutex); + /* Calculate the agregates for database/user and host */ + update_agg_counters(key.queryid, key.dbid, 0); + update_agg_counters(key.queryid, key.userid, 1); + update_agg_counters(key.queryid, pg_get_client_addr(), 2); + /* "Unstick" entry if it was previously sticky */ if (e->counters.calls == 0) e->counters.usage = USAGE_INIT; @@ -1243,9 +1414,19 @@ pgss_store(const char *query, uint64 queryId, e->counters.total_time += total_time; if (e->counters.calls == 1) { + int i; e->counters.min_time = total_time; e->counters.max_time = total_time; e->counters.mean_time = total_time; + for (i = 0; i < 24; i++) + { + e->counters.hist_min_time[i] = -1;; + e->counters.hist_max_time[i] = -1; + e->counters.hist_mean_time[i] = -1; + } + e->counters.hist_min_time[current_hour] = total_time; + e->counters.hist_max_time[current_hour] = total_time; + e->counters.hist_mean_time[current_hour] = total_time; } else { @@ -1280,7 +1461,26 @@ pgss_store(const char *query, uint64 queryId, e->counters.blk_read_time += INSTR_TIME_GET_MILLISEC(bufusage->blk_read_time); e->counters.blk_write_time += INSTR_TIME_GET_MILLISEC(bufusage->blk_write_time); e->counters.usage += USAGE_EXEC(total_time); - + e->counters.host = pg_get_client_addr(); + { + e->counters.hist_calls[current_hour] += 1; + if (total_time < e->counters.hist_min_time[current_hour]) + e->counters.hist_min_time[current_hour] = USAGE_EXEC(total_time); + if (total_time > e->counters.hist_min_time[current_hour]) + e->counters.hist_max_time[current_hour] = USAGE_EXEC(total_time); + + old_mean = e->counters.hist_mean_time[current_hour]; + e->counters.hist_mean_time[current_hour] += + (total_time - old_mean) / e->counters.hist_calls[current_hour]; + } + if (total_time >= e->counters.max_time) + { + for(i = 0; i < MAX_QUERY_LEN - 1; i++) + e->counters.slow_query[i] = query[i]; + } + e->counters.slow_query[MAX_QUERY_LEN - 1] = 0; + e->counters.utime = utime; + e->counters.stime = stime; SpinLockRelease(&e->mutex); } @@ -1296,12 +1496,12 @@ pgss_store(const char *query, uint64 queryId, * Reset all statement statistics. */ Datum -pg_stat_statements_reset(PG_FUNCTION_ARGS) +pg_stat_monitor_reset(PG_FUNCTION_ARGS) { - if (!pgss || !pgss_hash) + if (!pgss || !pgss_hash || !pgss_agghash) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("pg_stat_statements must be loaded via shared_preload_libraries"))); + errmsg("pg_stat_monitor must be loaded via shared_preload_libraries"))); entry_reset(); PG_RETURN_VOID(); } @@ -1311,7 +1511,8 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS) #define PG_STAT_STATEMENTS_COLS_V1_1 18 #define PG_STAT_STATEMENTS_COLS_V1_2 19 #define PG_STAT_STATEMENTS_COLS_V1_3 23 -#define PG_STAT_STATEMENTS_COLS 23 /* maximum of above */ +#define PG_STAT_STATEMENTS_COLS_V1_3_EXTENED_1 31 +#define PG_STAT_STATEMENTS_COLS 31 /* maximum of above */ /* * Retrieve statement statistics. @@ -1324,21 +1525,21 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS) * function. Unfortunately we weren't bright enough to do that for 1.1. */ Datum -pg_stat_statements_1_3(PG_FUNCTION_ARGS) +pg_stat_monitor_1_3(PG_FUNCTION_ARGS) { bool showtext = PG_GETARG_BOOL(0); - pg_stat_statements_internal(fcinfo, PGSS_V1_3, showtext); + pg_stat_monitor_internal(fcinfo, PGSS_V1_3, showtext); return (Datum) 0; } Datum -pg_stat_statements_1_2(PG_FUNCTION_ARGS) +pg_stat_monitor_1_2(PG_FUNCTION_ARGS) { bool showtext = PG_GETARG_BOOL(0); - pg_stat_statements_internal(fcinfo, PGSS_V1_2, showtext); + pg_stat_monitor_internal(fcinfo, PGSS_V1_2, showtext); return (Datum) 0; } @@ -1348,17 +1549,17 @@ pg_stat_statements_1_2(PG_FUNCTION_ARGS) * This can be removed someday, perhaps. */ Datum -pg_stat_statements(PG_FUNCTION_ARGS) +pg_stat_monitor(PG_FUNCTION_ARGS) { /* If it's really API 1.1, we'll figure that out below */ - pg_stat_statements_internal(fcinfo, PGSS_V1_0, true); + pg_stat_monitor_internal(fcinfo, PGSS_V1_0, true); return (Datum) 0; } /* Common code for all versions of pg_stat_statements() */ static void -pg_stat_statements_internal(FunctionCallInfo fcinfo, +pg_stat_monitor_internal(FunctionCallInfo fcinfo, pgssVersion api_version, bool showtext) { @@ -1383,7 +1584,7 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo, if (!pgss || !pgss_hash) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("pg_stat_statements must be loaded via shared_preload_libraries"))); + errmsg("pg_stat_monitor must be loaded via shared_preload_libraries"))); /* check to see if caller supports us returning a tuplestore */ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) @@ -1429,6 +1630,11 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo, if (api_version != PGSS_V1_3) elog(ERROR, "incorrect number of output arguments"); break; + case PG_STAT_STATEMENTS_COLS_V1_3_EXTENED_1: + if (api_version != PGSS_V1_3) + elog(ERROR, "incorrect number of output arguments %d", api_version); + api_version = PGSS_V1_3_EXTENDED_1; + break; default: elog(ERROR, "incorrect number of output arguments"); } @@ -1625,11 +1831,20 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo, values[i++] = Float8GetDatumFast(tmp.blk_read_time); values[i++] = Float8GetDatumFast(tmp.blk_write_time); } + values[i++] = Int64GetDatumFast(tmp.host); + values[i++] = ArrayGetTextDatum(tmp.hist_calls); + values[i++] = ArrayGetTextDatum(tmp.hist_min_time); + values[i++] = ArrayGetTextDatum(tmp.hist_max_time); + values[i++] = ArrayGetTextDatum(tmp.hist_mean_time); + values[i++] = CStringGetTextDatum(tmp.slow_query); + values[i++] = Float8GetDatumFast(tmp.utime); + values[i++] = Float8GetDatumFast(tmp.stime); Assert(i == (api_version == PGSS_V1_0 ? PG_STAT_STATEMENTS_COLS_V1_0 : api_version == PGSS_V1_1 ? PG_STAT_STATEMENTS_COLS_V1_1 : api_version == PGSS_V1_2 ? PG_STAT_STATEMENTS_COLS_V1_2 : api_version == PGSS_V1_3 ? PG_STAT_STATEMENTS_COLS_V1_3 : + api_version == PGSS_V1_3_EXTENDED_1 ? PG_STAT_STATEMENTS_COLS_V1_3_EXTENED_1 : -1 /* fail if you forget to update this assert */ )); tuplestore_putvalues(tupstore, tupdesc, values, nulls); @@ -1688,7 +1903,6 @@ entry_alloc(pgssHashKey *key, Size query_offset, int query_len, int encoding, /* Find or create an entry with desired hash code */ entry = (pgssEntry *) hash_search(pgss_hash, key, HASH_ENTER, &found); - if (!found) { /* New entry, initialize it */ @@ -1796,11 +2010,28 @@ entry_dealloc(void) for (i = 0; i < nvictims; i++) { hash_search(pgss_hash, &entries[i]->key, HASH_REMOVE, NULL); + hash_remove_agg(entries[i]->key.queryid); } - pfree(entries); + + +} + +static void +hash_remove_agg(uint64 queryid) +{ + HASH_SEQ_STATUS hash_seq; + pgssAggEntry *entry; + + hash_seq_init(&hash_seq, pgss_agghash); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + if (entry->key.queryid == queryid) + hash_search(pgss_agghash, &entry->key, HASH_REMOVE, NULL); + } } + /* * Given a query string (not necessarily null-terminated), allocate a new * entry in the external query text file and store the string there. @@ -1871,7 +2102,7 @@ qtext_store(const char *query, int query_len, error: ereport(LOG, (errcode_for_file_access(), - errmsg("could not write pg_stat_statement file \"%s\": %m", + errmsg("could not write pg_stat_monitor file \"%s\": %m", PGSS_TEXT_FILE))); if (fd >= 0) @@ -1913,7 +2144,7 @@ qtext_load_file(Size *buffer_size) if (errno != ENOENT) ereport(LOG, (errcode_for_file_access(), - errmsg("could not read pg_stat_statement file \"%s\": %m", + errmsg("could not read pg_stat_monitor file \"%s\": %m", PGSS_TEXT_FILE))); return NULL; } @@ -1923,7 +2154,7 @@ qtext_load_file(Size *buffer_size) { ereport(LOG, (errcode_for_file_access(), - errmsg("could not stat pg_stat_statement file \"%s\": %m", + errmsg("could not stat pg_stat_monitor file \"%s\": %m", PGSS_TEXT_FILE))); CloseTransientFile(fd); return NULL; @@ -1939,7 +2170,7 @@ qtext_load_file(Size *buffer_size) ereport(LOG, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"), - errdetail("Could not allocate enough memory to read pg_stat_statement file \"%s\".", + errdetail("Could not allocate enough memory to read pg_stat_monitor file \"%s\".", PGSS_TEXT_FILE))); CloseTransientFile(fd); return NULL; @@ -1958,7 +2189,7 @@ qtext_load_file(Size *buffer_size) if (errno) ereport(LOG, (errcode_for_file_access(), - errmsg("could not read pg_stat_statement file \"%s\": %m", + errmsg("could not read pg_stat_monitor file \"%s\": %m", PGSS_TEXT_FILE))); free(buf); CloseTransientFile(fd); @@ -2088,7 +2319,7 @@ gc_qtexts(void) { ereport(LOG, (errcode_for_file_access(), - errmsg("could not write pg_stat_statement file \"%s\": %m", + errmsg("could not write pg_stat_monitor file \"%s\": %m", PGSS_TEXT_FILE))); goto gc_fail; } @@ -2118,7 +2349,7 @@ gc_qtexts(void) { ereport(LOG, (errcode_for_file_access(), - errmsg("could not write pg_stat_statement file \"%s\": %m", + errmsg("could not write pg_stat_monitor file \"%s\": %m", PGSS_TEXT_FILE))); hash_seq_term(&hash_seq); goto gc_fail; @@ -2136,14 +2367,14 @@ gc_qtexts(void) if (ftruncate(fileno(qfile), extent) != 0) ereport(LOG, (errcode_for_file_access(), - errmsg("could not truncate pg_stat_statement file \"%s\": %m", + errmsg("could not truncate pg_stat_monitor file \"%s\": %m", PGSS_TEXT_FILE))); if (FreeFile(qfile)) { ereport(LOG, (errcode_for_file_access(), - errmsg("could not write pg_stat_statement file \"%s\": %m", + errmsg("could not write pg_stat_monitor file \"%s\": %m", PGSS_TEXT_FILE))); qfile = NULL; goto gc_fail; @@ -2203,7 +2434,7 @@ gc_qtexts(void) if (qfile == NULL) ereport(LOG, (errcode_for_file_access(), - errmsg("could not write new pg_stat_statement file \"%s\": %m", + errmsg("could not write new pg_stat_monitor file \"%s\": %m", PGSS_TEXT_FILE))); else FreeFile(qfile); @@ -2234,9 +2465,10 @@ gc_qtexts(void) static void entry_reset(void) { - HASH_SEQ_STATUS hash_seq; - pgssEntry *entry; - FILE *qfile; + HASH_SEQ_STATUS hash_seq; + pgssEntry *entry; + pgssAggEntry *dbentry; + FILE *qfile; LWLockAcquire(pgss->lock, LW_EXCLUSIVE); @@ -2246,6 +2478,12 @@ entry_reset(void) hash_search(pgss_hash, &entry->key, HASH_REMOVE, NULL); } + hash_seq_init(&hash_seq, pgss_agghash); + while ((dbentry = hash_seq_search(&hash_seq)) != NULL) + { + hash_search(pgss_agghash, &dbentry->key, HASH_REMOVE, NULL); + } + /* * Write new empty query file, perhaps even creating a new one to recover * if the file was missing. @@ -2255,7 +2493,7 @@ entry_reset(void) { ereport(LOG, (errcode_for_file_access(), - errmsg("could not create pg_stat_statement file \"%s\": %m", + errmsg("could not create pg_stat_monitor file \"%s\": %m", PGSS_TEXT_FILE))); goto done; } @@ -2264,7 +2502,7 @@ entry_reset(void) if (ftruncate(fileno(qfile), 0) != 0) ereport(LOG, (errcode_for_file_access(), - errmsg("could not truncate pg_stat_statement file \"%s\": %m", + errmsg("could not truncate pg_stat_monitor file \"%s\": %m", PGSS_TEXT_FILE))); FreeFile(qfile); @@ -3164,3 +3402,162 @@ comp_location(const void *a, const void *b) else return 0; } + +/* Convert array into Text dataum */ +static Datum +arry_get_datum(double arr[]) +{ + int j; + char str[1024]; + bool first = true; + + /* Need to calculate the actual size, and avoid unnessary memory usage */ + memset(str, 0, 1024); + for (j = 0; j < 24; j++) + { + if (first) + { + snprintf(str, 1024, "%s %04.1f", str, arr[j]); + first = false; + continue; + } + sprintf(str, "%s, %04.1f", str, arr[j]); + } + return CStringGetTextDatum(str); +} + +/* Alocate memory for a new entry */ +static pgssAggEntry * +agg_entry_alloc(pgssAggHashKey *key) +{ + pgssAggEntry *entry; + bool found; + + /* + * Make space if needed in reallity this should not happen, + * because we alread delete entry case of non-aggregate query. + */ + while (hash_get_num_entries(pgss_hash) >= pgss_max) + entry_dealloc(); + + entry = (pgssAggEntry *) hash_search(pgss_agghash, key, HASH_ENTER, &found); + if (!found) + { + SpinLockAcquire(&entry->mutex); + memset(&entry->counters, 0, sizeof(pgssAggCounters)); + entry->counters.total_calls = 0; + entry->counters.first_call_time = GetCurrentTimestamp(); + SpinLockRelease(&entry->mutex); + } + return entry; +} + +static void +update_agg_counters(uint64 queryid, uint64 id, uint64 type) +{ + pgssAggHashKey key; + pgssAggEntry *entry; + + key.id = id; + key.type = type; + key.queryid = queryid; + + entry = agg_entry_alloc(&key); + if (!entry) + { + elog(WARNING, "no space left in shared buffer"); + return; + } + SpinLockAcquire(&entry->mutex); + entry->key.queryid = queryid; + entry->key.id = id; + entry->key.type = key.type; + entry->counters.total_calls = 1; + if (entry->counters.total_calls == 1) + entry->counters.first_call_time = GetCurrentTimestamp(); + entry->counters.last_call_time= GetCurrentTimestamp(); + SpinLockRelease(&entry->mutex); +} + +Datum +pg_stat_agg(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + HASH_SEQ_STATUS hash_seq; + pgssAggEntry *entry; + + /* hash table must exist already */ + if (!pgss || !pgss_agghash) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("pg_stat_monitor must be loaded via shared_preload_libraries"))); + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not " \ + "allowed in this context"))); + + /* Switch into long-lived context to construct returned data structures */ + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + if (tupdesc->natts != 6) + elog(ERROR, "incorrect number of output arguments, required %d", tupdesc->natts); + + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + MemoryContextSwitchTo(oldcontext); + + /* + * Get shared lock, load or reload the query text file if we must, and + * iterate over the hashtable entries. + * + * With a large hash table, we might be holding the lock rather longer + * than one could wish. However, this only blocks creation of new hash + * table entries, and the larger the hash table the less likely that is to + * be needed. So we can hope this is okay. Perhaps someday we'll decide + * we need to partition the hash table to limit the time spent holding any + * one lock. + */ + LWLockAcquire(pgss->lock, LW_SHARED); + hash_seq_init(&hash_seq, pgss_agghash); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + Datum values[6]; + bool nulls[6]; + int i = 0; + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + values[i++] = Int64GetDatumFast(entry->key.queryid); + values[i++] = Int64GetDatumFast(entry->key.id); + values[i++] = Int64GetDatumFast(entry->key.type); + values[i++] = Int64GetDatumFast(entry->counters.total_calls); + values[i++] = TimestampGetDatum(entry->counters.first_call_time); + values[i++] = TimestampGetDatum(entry->counters.last_call_time); + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + } + + /* clean up and return the tuplestore */ + LWLockRelease(pgss->lock); + tuplestore_donestoring(tupstore); + return 0; +} diff --git a/pg_stat_monitor.conf b/pg_stat_monitor.conf new file mode 100644 index 000000000000..03796179a115 --- /dev/null +++ b/pg_stat_monitor.conf @@ -0,0 +1 @@ +shared_preload_libraries = 'pg_stat_monitor' diff --git a/pg_stat_monitor.control b/pg_stat_monitor.control new file mode 100644 index 000000000000..cc93476faf1a --- /dev/null +++ b/pg_stat_monitor.control @@ -0,0 +1,5 @@ +# pg_stat_monitor extension +comment = 'track execution statistics of all SQL statements executed' +default_version = '1.0' +module_pathname = '$libdir/pg_stat_monitor' +relocatable = true diff --git a/pg_stat_statements--1.0--1.1.sql b/pg_stat_statements--1.0--1.1.sql deleted file mode 100644 index 5be281ea47a4..000000000000 --- a/pg_stat_statements--1.0--1.1.sql +++ /dev/null @@ -1,42 +0,0 @@ -/* contrib/pg_stat_statements/pg_stat_statements--1.0--1.1.sql */ - --- complain if script is sourced in psql, rather than via ALTER EXTENSION -\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.1'" to load this file. \quit - -/* First we have to remove them from the extension */ -ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; -ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(); - -/* Then we can drop them */ -DROP VIEW pg_stat_statements; -DROP FUNCTION pg_stat_statements(); - -/* Now redefine */ -CREATE FUNCTION pg_stat_statements( - OUT userid oid, - OUT dbid oid, - OUT query text, - OUT calls int8, - OUT total_time float8, - OUT rows int8, - OUT shared_blks_hit int8, - OUT shared_blks_read int8, - OUT shared_blks_dirtied int8, - OUT shared_blks_written int8, - OUT local_blks_hit int8, - OUT local_blks_read int8, - OUT local_blks_dirtied int8, - OUT local_blks_written int8, - OUT temp_blks_read int8, - OUT temp_blks_written int8, - OUT blk_read_time float8, - OUT blk_write_time float8 -) -RETURNS SETOF record -AS 'MODULE_PATHNAME' -LANGUAGE C; - -CREATE VIEW pg_stat_statements AS - SELECT * FROM pg_stat_statements(); - -GRANT SELECT ON pg_stat_statements TO PUBLIC; diff --git a/pg_stat_statements--1.1--1.2.sql b/pg_stat_statements--1.1--1.2.sql deleted file mode 100644 index 74ae43868d13..000000000000 --- a/pg_stat_statements--1.1--1.2.sql +++ /dev/null @@ -1,43 +0,0 @@ -/* contrib/pg_stat_statements/pg_stat_statements--1.1--1.2.sql */ - --- complain if script is sourced in psql, rather than via ALTER EXTENSION -\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.2'" to load this file. \quit - -/* First we have to remove them from the extension */ -ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; -ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(); - -/* Then we can drop them */ -DROP VIEW pg_stat_statements; -DROP FUNCTION pg_stat_statements(); - -/* Now redefine */ -CREATE FUNCTION pg_stat_statements(IN showtext boolean, - OUT userid oid, - OUT dbid oid, - OUT queryid bigint, - OUT query text, - OUT calls int8, - OUT total_time float8, - OUT rows int8, - OUT shared_blks_hit int8, - OUT shared_blks_read int8, - OUT shared_blks_dirtied int8, - OUT shared_blks_written int8, - OUT local_blks_hit int8, - OUT local_blks_read int8, - OUT local_blks_dirtied int8, - OUT local_blks_written int8, - OUT temp_blks_read int8, - OUT temp_blks_written int8, - OUT blk_read_time float8, - OUT blk_write_time float8 -) -RETURNS SETOF record -AS 'MODULE_PATHNAME', 'pg_stat_statements_1_2' -LANGUAGE C STRICT VOLATILE; - -CREATE VIEW pg_stat_statements AS - SELECT * FROM pg_stat_statements(true); - -GRANT SELECT ON pg_stat_statements TO PUBLIC; diff --git a/pg_stat_statements--1.2--1.3.sql b/pg_stat_statements--1.2--1.3.sql deleted file mode 100644 index a56f151b9946..000000000000 --- a/pg_stat_statements--1.2--1.3.sql +++ /dev/null @@ -1,47 +0,0 @@ -/* contrib/pg_stat_statements/pg_stat_statements--1.2--1.3.sql */ - --- complain if script is sourced in psql, rather than via ALTER EXTENSION -\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.3'" to load this file. \quit - -/* First we have to remove them from the extension */ -ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; -ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); - -/* Then we can drop them */ -DROP VIEW pg_stat_statements; -DROP FUNCTION pg_stat_statements(boolean); - -/* Now redefine */ -CREATE FUNCTION pg_stat_statements(IN showtext boolean, - OUT userid oid, - OUT dbid oid, - OUT queryid bigint, - OUT query text, - OUT calls int8, - OUT total_time float8, - OUT min_time float8, - OUT max_time float8, - OUT mean_time float8, - OUT stddev_time float8, - OUT rows int8, - OUT shared_blks_hit int8, - OUT shared_blks_read int8, - OUT shared_blks_dirtied int8, - OUT shared_blks_written int8, - OUT local_blks_hit int8, - OUT local_blks_read int8, - OUT local_blks_dirtied int8, - OUT local_blks_written int8, - OUT temp_blks_read int8, - OUT temp_blks_written int8, - OUT blk_read_time float8, - OUT blk_write_time float8 -) -RETURNS SETOF record -AS 'MODULE_PATHNAME', 'pg_stat_statements_1_3' -LANGUAGE C STRICT VOLATILE; - -CREATE VIEW pg_stat_statements AS - SELECT * FROM pg_stat_statements(true); - -GRANT SELECT ON pg_stat_statements TO PUBLIC; diff --git a/pg_stat_statements--1.3--1.4.sql b/pg_stat_statements--1.3--1.4.sql deleted file mode 100644 index ae70c1f8028e..000000000000 --- a/pg_stat_statements--1.3--1.4.sql +++ /dev/null @@ -1,7 +0,0 @@ -/* contrib/pg_stat_statements/pg_stat_statements--1.3--1.4.sql */ - --- complain if script is sourced in psql, rather than via ALTER EXTENSION -\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.4'" to load this file. \quit - -ALTER FUNCTION pg_stat_statements_reset() PARALLEL SAFE; -ALTER FUNCTION pg_stat_statements(boolean) PARALLEL SAFE; diff --git a/pg_stat_statements--1.4--1.5.sql b/pg_stat_statements--1.4--1.5.sql deleted file mode 100644 index 9c76122a2b76..000000000000 --- a/pg_stat_statements--1.4--1.5.sql +++ /dev/null @@ -1,6 +0,0 @@ -/* contrib/pg_stat_statements/pg_stat_statements--1.4--1.5.sql */ - --- complain if script is sourced in psql, rather than via ALTER EXTENSION -\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.5'" to load this file. \quit - -GRANT EXECUTE ON FUNCTION pg_stat_statements_reset() TO pg_read_all_stats; diff --git a/pg_stat_statements--1.4.sql b/pg_stat_statements--1.4.sql deleted file mode 100644 index 58cdf600fced..000000000000 --- a/pg_stat_statements--1.4.sql +++ /dev/null @@ -1,48 +0,0 @@ -/* contrib/pg_stat_statements/pg_stat_statements--1.4.sql */ - --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "CREATE EXTENSION pg_stat_statements" to load this file. \quit - --- Register functions. -CREATE FUNCTION pg_stat_statements_reset() -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C PARALLEL SAFE; - -CREATE FUNCTION pg_stat_statements(IN showtext boolean, - OUT userid oid, - OUT dbid oid, - OUT queryid bigint, - OUT query text, - OUT calls int8, - OUT total_time float8, - OUT min_time float8, - OUT max_time float8, - OUT mean_time float8, - OUT stddev_time float8, - OUT rows int8, - OUT shared_blks_hit int8, - OUT shared_blks_read int8, - OUT shared_blks_dirtied int8, - OUT shared_blks_written int8, - OUT local_blks_hit int8, - OUT local_blks_read int8, - OUT local_blks_dirtied int8, - OUT local_blks_written int8, - OUT temp_blks_read int8, - OUT temp_blks_written int8, - OUT blk_read_time float8, - OUT blk_write_time float8 -) -RETURNS SETOF record -AS 'MODULE_PATHNAME', 'pg_stat_statements_1_3' -LANGUAGE C STRICT VOLATILE PARALLEL SAFE; - --- Register a view on the function for ease of use. -CREATE VIEW pg_stat_statements AS - SELECT * FROM pg_stat_statements(true); - -GRANT SELECT ON pg_stat_statements TO PUBLIC; - --- Don't want this to be available to non-superusers. -REVOKE ALL ON FUNCTION pg_stat_statements_reset() FROM PUBLIC; diff --git a/pg_stat_statements--1.5--1.6.sql b/pg_stat_statements--1.5--1.6.sql deleted file mode 100644 index 4f8c7f7ee8a0..000000000000 --- a/pg_stat_statements--1.5--1.6.sql +++ /dev/null @@ -1,7 +0,0 @@ -/* contrib/pg_stat_statements/pg_stat_statements--1.5--1.6.sql */ - --- complain if script is sourced in psql, rather than via ALTER EXTENSION -\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.6'" to load this file. \quit - --- Execution is only allowed for superusers, fixing issue with 1.5. -REVOKE EXECUTE ON FUNCTION pg_stat_statements_reset() FROM pg_read_all_stats; diff --git a/pg_stat_statements--unpackaged--1.0.sql b/pg_stat_statements--unpackaged--1.0.sql deleted file mode 100644 index 116e95834db4..000000000000 --- a/pg_stat_statements--unpackaged--1.0.sql +++ /dev/null @@ -1,8 +0,0 @@ -/* contrib/pg_stat_statements/pg_stat_statements--unpackaged--1.0.sql */ - --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "CREATE EXTENSION pg_stat_statements FROM unpackaged" to load this file. \quit - -ALTER EXTENSION pg_stat_statements ADD function pg_stat_statements_reset(); -ALTER EXTENSION pg_stat_statements ADD function pg_stat_statements(); -ALTER EXTENSION pg_stat_statements ADD view pg_stat_statements; diff --git a/pg_stat_statements.conf b/pg_stat_statements.conf deleted file mode 100644 index 13346e280783..000000000000 --- a/pg_stat_statements.conf +++ /dev/null @@ -1 +0,0 @@ -shared_preload_libraries = 'pg_stat_statements' diff --git a/pg_stat_statements.control b/pg_stat_statements.control deleted file mode 100644 index 617038b4c05d..000000000000 --- a/pg_stat_statements.control +++ /dev/null @@ -1,5 +0,0 @@ -# pg_stat_statements extension -comment = 'track execution statistics of all SQL statements executed' -default_version = '1.6' -module_pathname = '$libdir/pg_stat_statements' -relocatable = true diff --git a/sql/pg_stat_statements.sql b/sql/pg_stat_monitor.sql similarity index 100% rename from sql/pg_stat_statements.sql rename to sql/pg_stat_monitor.sql