This is a proposal to add some features to pg_stat_statements.
Attached is the patch of this.
pg_stat_statements uses a hash table to hold statistics,
and the maximum number of its entries can be configured through
pg_stat_statements.max.
When the number of entries exceeds the pg_stat_statements.max,
pg_stat_statements deallocate existing entries.
Currently, it is impossible to know how many times/when this
deallocation happened.
But, with this information, more detailed performance analysis would be
possible.
So, this patch provides access to this information.
This patch provides a view (pg_stat_statements_ctl) and
a function (pg_stat_statements_ctl).
The pg_stat_statements_ctl view is defined
in terms of a function also named pg_stat_statements_ctl.
The entry of pg_stat_statements_ctl view is removed
when pg_stat_statements_reset(0,0,0) is called.
Maybe, it is convenient to provide a function named
pg_stat_statements_ctl_reset
that removes only the entry of pg_stat_statements_ctl.
The following is an example of this feature.
Here, a deallocation is shown with the INFO message.
pg_stat_statements_ctl tracks the number of deallocations
and timestamp of last deallocation.
testdb=# select * from pg_stat_statements_ctl;
dealloc | last_dealloc
---------+-------------------------------
2 | 2020-09-18 16:55:31.128256+09
(1 row)
testdb=# select count(*) from test1;
2020-09-18 16:59:20.745 JST [3920] INFO: deallocate
2020-09-18 16:59:20.745 JST [3920] STATEMENT: select count(*) from
test1;
INFO: deallocate
count
-------
90
(1 row)
testdb=# select * from pg_stat_statements_ctl;
dealloc | last_dealloc
---------+-------------------------------
3 | 2020-09-18 16:59:20.745652+09
(1 row)
Regards,
Katsuragi Yuta
diff --git a/contrib/pg_stat_statements/Makefile b/contrib/pg_stat_statements/Makefile
index 081f997d70..63fd4874b1 100644
--- a/contrib/pg_stat_statements/Makefile
+++ b/contrib/pg_stat_statements/Makefile
@@ -6,7 +6,7 @@ OBJS = \
pg_stat_statements.o
EXTENSION = pg_stat_statements
-DATA = pg_stat_statements--1.4.sql \
+DATA = pg_stat_statements--1.4.sql pg_stat_statements--1.8--1.9.sql\
pg_stat_statements--1.7--1.8.sql pg_stat_statements--1.6--1.7.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 \
diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.8--1.9.sql b/contrib/pg_stat_statements/pg_stat_statements--1.8--1.9.sql
new file mode 100644
index 0000000000..43c7966946
--- /dev/null
+++ b/contrib/pg_stat_statements/pg_stat_statements--1.8--1.9.sql
@@ -0,0 +1,18 @@
+/* contrib/pg_stat_statements/pg_stat_statements--1.8--1.9.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.9'" to load this file. \quit
+
+--- Define pg_stat_statements_ctl
+CREATE FUNCTION pg_stat_statements_ctl (
+ OUT dealloc integer,
+ OUT last_dealloc TIMESTAMP WITH TIME ZONE
+)
+RETURNS record
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT VOLATILE PARALLEL SAFE;
+
+CREATE VIEW pg_stat_statements_ctl AS
+ SELECT * FROM pg_stat_statements_ctl();
+
+GRANT SELECT ON pg_stat_statements TO PUBLIC;
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 1eac9edaee..aa116db498 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -81,6 +81,7 @@
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
+#include "utils/timestamp.h"
PG_MODULE_MAGIC;
@@ -193,6 +194,17 @@ typedef struct Counters
uint64 wal_bytes; /* total amount of WAL bytes generated */
} Counters;
+/*
+ * Counter for pg_stat_statements_ctl
+ */
+typedef struct pgssCtlCounter
+{
+ int64 dealloc; /* # of deallocation */
+ TimestampTz last_dealloc; /* timestamp of last deallocation */
+ bool ts_isnull; /* if true last_dealloc is null */
+ slock_t mutex;
+} pgssCtlCounter;
+
/*
* Statistics per statement
*
@@ -279,6 +291,7 @@ static ProcessUtility_hook_type prev_ProcessUtility = NULL;
/* Links to shared memory state */
static pgssSharedState *pgss = NULL;
static HTAB *pgss_hash = NULL;
+static pgssCtlCounter *pgss_ctl = NULL;
/*---- GUC variables ----*/
@@ -327,6 +340,7 @@ 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_1_8);
PG_FUNCTION_INFO_V1(pg_stat_statements);
+PG_FUNCTION_INFO_V1(pg_stat_statements_ctl);
static void pgss_shmem_startup(void);
static void pgss_shmem_shutdown(int code, Datum arg);
@@ -380,6 +394,8 @@ static char *generate_normalized_query(pgssJumbleState *jstate, const char *quer
static void fill_in_constant_lengths(pgssJumbleState *jstate, const char *query,
int query_loc);
static int comp_location(const void *a, const void *b);
+static void update_ctl(void);
+static void reset_ctl(void);
/*
@@ -518,6 +534,7 @@ static void
pgss_shmem_startup(void)
{
bool found;
+ bool found_ctl;
HASHCTL info;
FILE *file = NULL;
FILE *qfile = NULL;
@@ -534,6 +551,7 @@ pgss_shmem_startup(void)
/* reset in case this is a restart within the postmaster */
pgss = NULL;
pgss_hash = NULL;
+ pgss_ctl = NULL;
/*
* Create or attach to the shared memory state, including hash table
@@ -556,6 +574,17 @@ pgss_shmem_startup(void)
pgss->gc_count = 0;
}
+ pgss_ctl = ShmemInitStruct("pg_stat_statements ctl",
+ sizeof(pgssCtlCounter),
+ &found_ctl);
+ if (!found_ctl)
+ {
+ pgss_ctl->dealloc = 0;
+ pgss_ctl->ts_isnull = true;
+ SpinLockInit(&pgss_ctl->mutex);
+ Assert(pgss_ctl->dealloc == 0);
+ }
+
memset(&info, 0, sizeof(info));
info.keysize = sizeof(pgssHashKey);
info.entrysize = sizeof(pgssEntry);
@@ -577,7 +606,10 @@ pgss_shmem_startup(void)
* Done if some other process already completed our initialization.
*/
if (found)
+ {
+ Assert(found == found_ctl);
return;
+ }
/*
* Note: we don't bother with locks here, because there should be no other
@@ -672,6 +704,12 @@ pgss_shmem_startup(void)
/* copy in the actual stats */
entry->counters = temp.counters;
}
+ // Assert(feof(file) != 0); // EOF
+
+ /* Read pgss_ctl */
+ if (feof(file) == 0)
+ if (fread(pgss_ctl, sizeof(pgssCtlCounter), 1, file) != 1)
+ goto read_error;
pfree(buffer);
FreeFile(file);
@@ -748,7 +786,7 @@ pgss_shmem_shutdown(int code, Datum arg)
return;
/* Safety check ... shouldn't get here unless shmem is set up. */
- if (!pgss || !pgss_hash)
+ if (!pgss || !pgss_hash || !pgss_ctl)
return;
/* Don't dump if told not to. */
@@ -794,6 +832,10 @@ pgss_shmem_shutdown(int code, Datum arg)
}
}
+ /* Dump pgss_ctl */
+ if (fwrite(pgss_ctl, sizeof(pgssCtlCounter), 1, file) != 1)
+ goto error;
+
free(qbuffer);
qbuffer = NULL;
@@ -818,6 +860,7 @@ error:
(errcode_for_file_access(),
errmsg("could not write file \"%s\": %m",
PGSS_DUMP_FILE ".tmp")));
+
if (qbuffer)
free(qbuffer);
if (file)
@@ -1453,6 +1496,33 @@ done:
pfree(norm_query);
}
+static void
+update_ctl(void)
+{
+ TimestampTz current_ts = GetCurrentTimestamp();
+
+ {
+ volatile pgssCtlCounter *c = (volatile pgssCtlCounter *) pgss_ctl;
+ SpinLockAcquire(&c->mutex);
+ pgss_ctl->dealloc += 1; /* increment dealloc count */
+ pgss_ctl->last_dealloc = current_ts;
+ pgss_ctl->ts_isnull = false;
+ SpinLockRelease(&c->mutex);
+ }
+}
+
+static void
+reset_ctl(void)
+{
+ {
+ volatile pgssCtlCounter *c = (volatile pgssCtlCounter *) pgss_ctl;
+ SpinLockAcquire(&c->mutex);
+ pgss_ctl->dealloc = 0; /* reset dealloc count */
+ pgss_ctl->ts_isnull = true;
+ SpinLockRelease(&c->mutex);
+ }
+}
+
/*
* Reset statement statistics corresponding to userid, dbid, and queryid.
*/
@@ -1490,6 +1560,7 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS)
#define PG_STAT_STATEMENTS_COLS_V1_3 23
#define PG_STAT_STATEMENTS_COLS_V1_8 32
#define PG_STAT_STATEMENTS_COLS 32 /* maximum of above */
+#define PG_STAT_STATEMENTS_CTL_COLS 2
/*
* Retrieve statement statistics.
@@ -1862,6 +1933,44 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
tuplestore_donestoring(tupstore);
}
+Datum
+pg_stat_statements_ctl(PG_FUNCTION_ARGS)
+{
+ TupleDesc tupdesc;
+ HeapTuple result_tuple;
+ Datum result;
+ Datum values[PG_STAT_STATEMENTS_CTL_COLS];
+ bool nulls[PG_STAT_STATEMENTS_CTL_COLS];
+ bool ts_isnull;
+ int i = 0;
+
+ Assert(PG_STAT_STATEMENTS_CTL_COLS == 2);
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ tupdesc = BlessTupleDesc(tupdesc);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ {
+ volatile pgssCtlCounter *c = (volatile pgssCtlCounter *) pgss_ctl;
+ SpinLockAcquire(&c->mutex);
+ values[i++] = Int64GetDatum(pgss_ctl->dealloc);
+ ts_isnull = pgss_ctl->ts_isnull;
+ if (!ts_isnull)
+ values[i++] = TimestampTzGetDatum(pgss_ctl->last_dealloc);
+ else
+ nulls[i++] = true;
+ SpinLockRelease(&c->mutex);
+ }
+
+ result_tuple = heap_form_tuple(tupdesc, values, nulls);
+ result = HeapTupleGetDatum(result_tuple);
+
+ return result;
+}
+
/*
* Estimate shared memory space needed.
*/
@@ -1872,6 +1981,7 @@ pgss_memsize(void)
size = MAXALIGN(sizeof(pgssSharedState));
size = add_size(size, hash_estimate_size(pgss_max, sizeof(pgssEntry)));
+ size = add_size(size, MAXALIGN(sizeof(pgssCtlCounter)));
return size;
}
@@ -1902,7 +2012,10 @@ entry_alloc(pgssHashKey *key, Size query_offset, int query_len, int encoding,
/* Make space if needed */
while (hash_get_num_entries(pgss_hash) >= pgss_max)
+ {
entry_dealloc();
+ update_ctl();
+ }
/* Find or create an entry with desired hash code */
entry = (pgssEntry *) hash_search(pgss_hash, key, HASH_ENTER, &found);
@@ -2503,6 +2616,9 @@ entry_reset(Oid userid, Oid dbid, uint64 queryid)
hash_search(pgss_hash, &entry->key, HASH_REMOVE, NULL);
num_remove++;
}
+
+ /* Reset pgss_ctl */
+ reset_ctl();
}
/* All entries are removed? */
diff --git a/contrib/pg_stat_statements/pg_stat_statements.control b/contrib/pg_stat_statements/pg_stat_statements.control
index 65b18b11d2..2f1ce6ed50 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.control
+++ b/contrib/pg_stat_statements/pg_stat_statements.control
@@ -1,5 +1,5 @@
# pg_stat_statements extension
comment = 'track planning and execution statistics of all SQL statements executed'
-default_version = '1.8'
+default_version = '1.9'
module_pathname = '$libdir/pg_stat_statements'
relocatable = true
diff --git a/doc/src/sgml/pgstatstatements.sgml b/doc/src/sgml/pgstatstatements.sgml
index cf2d25b7b2..605795fe80 100644
--- a/doc/src/sgml/pgstatstatements.sgml
+++ b/doc/src/sgml/pgstatstatements.sgml
@@ -23,10 +23,11 @@
<para>
When <filename>pg_stat_statements</filename> is loaded, it tracks
statistics across all databases of the server. To access and manipulate
- these statistics, the module provides a view, <structname>pg_stat_statements</structname>,
- and the utility functions <function>pg_stat_statements_reset</function> and
- <function>pg_stat_statements</function>. These are not available globally but
- can be enabled for a specific database with
+ these statistics, the module provides views, <structname>pg_stat_statements</structname>
+ and <structname>pg_stat_statements_ctl</structname>,
+ and the utility functions <function>pg_stat_statements_reset</function>,
+ <function>pg_stat_statements</function>, and <function>pg_stat_statements_ctl</function>.
+ These are not available globally but can be enabled for a specific database with
<command>CREATE EXTENSION pg_stat_statements</command>.
</para>
@@ -480,6 +481,57 @@
</para>
</sect2>
+ <sect2>
+ <title>The <structname>pg_stat_statements_ctl</structname> View</title>
+
+ <para>
+ This module tracks statistics of <filename>pg_stat_statements</filename>
+ itself for detailed performance analysis.
+ The statistics of pg_stat_statements itself are made available via a
+ view named <structname>pg_stat_statements_ctl</structname>.
+ This view contains only a single row.
+ The row is reset only when <function>pg_stat_statements_reset(0,0,0)</function>
+ is called. The columns of the view are
+ shown in <xref linkend="pgstatstatementsctl-columns"/>.
+ </para>
+
+ <table id="pgstatstatementsctl-columns">
+ <title><structname>pg_stat_statements_ctl</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>dealloc</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Total number of deallocations of pg_stat_statements view entry
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>last_dealloc</structfield> <type>timestamp with time zone</type>
+ </para>
+ <para>
+ The timestamp of last deallocation
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect2>
+
<sect2>
<title>Functions</title>
@@ -537,6 +589,24 @@
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>pg_stat_statements_ctl() returns record</function>
+ <indexterm>
+ <primary>pg_stat_statements_ctl</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ The <structname>pg_stat_statements_ctl</structname> view is defined
+ in terms of a function also named <function>pg_stat_statements_ctl</function>.
+ It is possible for clients to call the
+ <function>pg_stat_statements_ctl</function> directly.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>