New SQL counter statistics view (pg_stat_sql)
This is a new statistics view that is used to provide the number of
SQL operations that are
happened on a particular interval of time. This view is useful for the
system to find out the
pattern of the operations that are happening in the instance during
particular interval of
time.
Following is the more or less columns and their details of the pg_stat_sql view.
postgres=# \d pg_stat_sql
View "pg_catalog.pg_stat_sql"
Column | Type | Modifiers
-------------+--------------------------+-----------
selects | bigint |
inserts | bigint |
deletes | bigint |
updates | bigint |
declares | bigint |
fetches | bigint |
copies | bigint |
reindexes | bigint |
truncates | bigint |
stats_reset | timestamp with time zone |
The SQL counters gets updated only for the external SQL queries, not for
the SQL queries that are generated internally. The counters gets updated
at exec_simple_query and exec_execute_message functions.
User can reset the SQL counter statistics using an exposed function.
The Stats collection can be turned on/off using a GUC also.
Any comments/objections in providing a patch for the same?
Regards,
Hari Babu
Fujitsu Australia
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Haribabu Kommi <kommi.haribabu@gmail.com> writes:
This is a new statistics view that is used to provide the number of
SQL operations that are
happened on a particular interval of time. This view is useful for the
system to find out the
pattern of the operations that are happening in the instance during
particular interval of
time.
Following is the more or less columns and their details of the pg_stat_sql view.
postgres=# \d pg_stat_sql
View "pg_catalog.pg_stat_sql"
Column | Type | Modifiers
-------------+--------------------------+-----------
selects | bigint |
inserts | bigint |
deletes | bigint |
updates | bigint |
declares | bigint |
fetches | bigint |
copies | bigint |
reindexes | bigint |
truncates | bigint |
stats_reset | timestamp with time zone |
1. This set of counters seems remarkably random. Why, for instance,
count reindexes but not original index creations?
2. What will you do with cases such as SELECTs containing modifying CTEs?
Or EXPLAIN ANALYZE? Does CLUSTER count as a REINDEX? Does REFRESH
MATERIALIZED VIEW count as a SELECT? (Or maybe it's an INSERT?)
If you're going to be selective about what you count, you're forever
going to be fielding complaints from users who are unhappy that you
didn't count some statement type they care about, or feel that you
misclassified some complex case.
I'm inclined to suggest you forget this approach and propose a single
counter for "SQL commands executed", which avoids all of the above
definitional problems. People who need more detail than that are
probably best advised to look to contrib/pg_stat_statements, anyway.
Also, if you do that, there hardly seems a need for a whole new view.
You could just add a column to pg_stat_database. The incremental
maintenance effort doesn't seem enough to justify its own GUC, either.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Aug 21, 2016 at 1:17 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Haribabu Kommi <kommi.haribabu@gmail.com> writes:
This is a new statistics view that is used to provide the number of
SQL operations that are
happened on a particular interval of time. This view is useful for the
system to find out the
pattern of the operations that are happening in the instance during
particular interval of
time.1. This set of counters seems remarkably random. Why, for instance,
count reindexes but not original index creations?
I thought of adding the columns to the view that are mostly used(excluding
all DDL commands), otherwise the view columns fill with all the commands
that are supported by PostgreSQL.
2. What will you do with cases such as SELECTs containing modifying CTEs?
Or EXPLAIN ANALYZE? Does CLUSTER count as a REINDEX? Does REFRESH
MATERIALIZED VIEW count as a SELECT? (Or maybe it's an INSERT?)
Cluster commands will be calculated as clusters, explain needs some
logic to identify what SQL operation exactly. This is because of using
nodeTag data from the parseTree structure to identify what type of SQL
statement it is.
The counters will not updated for any SQL type that is not as part of the view.
If you're going to be selective about what you count, you're forever
going to be fielding complaints from users who are unhappy that you
didn't count some statement type they care about, or feel that you
misclassified some complex case.
Yes, I agree that users may complain regarding with limited columns that are not
sufficient for their use. But with the mostly used columns, this view
may be useful for
some of the users to find out the pattern of the SQL operations that
are happening
on the instance.
I'm inclined to suggest you forget this approach and propose a single
counter for "SQL commands executed", which avoids all of the above
definitional problems. People who need more detail than that are
probably best advised to look to contrib/pg_stat_statements, anyway.
I will add a new column to the pg_stat_database, to track the number of SQL
operations that are happened on this particular database.
Regards,
Hari Babu
Fujitsu Australia
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Aug 20, 2016 at 11:17 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I'm inclined to suggest you forget this approach and propose a single
counter for "SQL commands executed", which avoids all of the above
definitional problems. People who need more detail than that are
probably best advised to look to contrib/pg_stat_statements, anyway.
I disagree. I think SQL commands executed, lumping absolutely
everything together, really isn't much use. Haribabu's categorization
scheme seems to need some work, but the idea of categorizing
statements by type and counting executions per type seems very
reasonable.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2016-08-22 13:54:43 -0400, Robert Haas wrote:
On Sat, Aug 20, 2016 at 11:17 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I'm inclined to suggest you forget this approach and propose a single
counter for "SQL commands executed", which avoids all of the above
definitional problems. People who need more detail than that are
probably best advised to look to contrib/pg_stat_statements, anyway.I disagree. I think SQL commands executed, lumping absolutely
everything together, really isn't much use.
I'm inclined to agree. I think that's a quite useful stat when looking
at an installation one previously didn't have a lot of interaction with.
Haribabu's categorization
scheme seems to need some work, but the idea of categorizing
statements by type and counting executions per type seems very
reasonable.
I'd consider instead using something like COALESCE(commandType,
nodeTag(Query->utilityStmt)) as the categories. Not sure if I'd even
pivot that.
Andres
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Andres Freund <andres@anarazel.de> writes:
On 2016-08-22 13:54:43 -0400, Robert Haas wrote:
On Sat, Aug 20, 2016 at 11:17 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I'm inclined to suggest you forget this approach and propose a single
counter for "SQL commands executed", which avoids all of the above
definitional problems. People who need more detail than that are
probably best advised to look to contrib/pg_stat_statements, anyway.
I disagree. I think SQL commands executed, lumping absolutely
everything together, really isn't much use.
I'm inclined to agree. I think that's a quite useful stat when looking
at an installation one previously didn't have a lot of interaction with.
Well, let's at least have an "other" category so you can add up the
counters and get a meaningful total.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 23/08/16 08:27, Tom Lane wrote:
Andres Freund <andres@anarazel.de> writes:
On 2016-08-22 13:54:43 -0400, Robert Haas wrote:
On Sat, Aug 20, 2016 at 11:17 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I'm inclined to suggest you forget this approach and propose a single
counter for "SQL commands executed", which avoids all of the above
definitional problems. People who need more detail than that are
probably best advised to look to contrib/pg_stat_statements, anyway.I disagree. I think SQL commands executed, lumping absolutely
everything together, really isn't much use.I'm inclined to agree. I think that's a quite useful stat when looking
at an installation one previously didn't have a lot of interaction with.Well, let's at least have an "other" category so you can add up the
counters and get a meaningful total.regards, tom lane
Initially I thought of something I thought was factious, but then
realized it might actually be both practicable & useful...
How about 2 extra categories (if appropriate!!!):
1. Things that actually might be sort of as fitting in 2 or more of the
existing categories, or there is an ambiguity as to which category
is appropriate.
2. Things that don't fit into any existing category
This was inspired by a real use case, in a totally unrelated area - but
where I attempted to ensure counts were in the most useful categories I
was able to provide. The user had listed categories, but I found that
the data didn't always fit neatly into them as specified.
Cheers,
Gavin
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Andres Freund <andres@anarazel.de> writes:
On 2016-08-22 13:54:43 -0400, Robert Haas wrote:
On Sat, Aug 20, 2016 at 11:17 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:I'm inclined to suggest you forget this approach and propose a single
counter for "SQL commands executed", which avoids all of the above
definitional problems. People who need more detail than that are
probably best advised to look to contrib/pg_stat_statements, anyway.
I disagree. I think SQL commands executed, lumping absolutely
everything together, really isn't much use.
I'm inclined to agree. I think that's a quite useful stat when looking
at an installation one previously didn't have a lot of interaction with.
Well, let's at least have an "other" category so you can add up the
counters and get a meaningful total.
How would that meaningful total might help a user. What can a user might
analyse with the counter in 'other' category.
Neha
On 24/08/16 12:02, neha khatri wrote:
Andres Freund <andres@anarazel.de <mailto:andres@anarazel.de>> writes:
On 2016-08-22 13:54:43 -0400, Robert Haas wrote:
On Sat, Aug 20, 2016 at 11:17 AM, Tom Lane <tgl@sss.pgh.pa.us<mailto:tgl@sss.pgh.pa.us>> wrote:
I'm inclined to suggest you forget this approach and propose a single
counter for "SQL commands executed", which avoids all of the above
definitional problems. People who need more detail than that are
probably best advised to look to contrib/pg_stat_statements, anyway.I disagree. I think SQL commands executed, lumping absolutely
everything together, really isn't much use.I'm inclined to agree. I think that's a quite useful stat when looking
at an installation one previously didn't have a lot of interactionwith.
Well, let's at least have an "other" category so you can add up the
counters and get a meaningful total.How would that meaningful total might help a user. What can a user
might analyse with the counter in 'other' category.Neha
The user could then judge if there were a significant number of examples
not covered in the other categories - this may, or may not, be a
problem; depending on the use case.
Cheers,
Gavin
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Aug 24, 2016 at 10:07 AM, Gavin Flower <
GavinFlower@archidevsys.co.nz> wrote:
On 24/08/16 12:02, neha khatri wrote:
Andres Freund <andres@anarazel.de <mailto:andres@anarazel.de>> writes:
On 2016-08-22 13:54:43 -0400, Robert Haas wrote:
On Sat, Aug 20, 2016 at 11:17 AM, Tom Lane <tgl@sss.pgh.pa.us <mailto:tgl@sss.pgh.pa.us>> wrote:
I'm inclined to suggest you forget this approach and propose a single
counter for "SQL commands executed", which avoids all of the above
definitional problems. People who need more detail than that are
probably best advised to look to contrib/pg_stat_statements, anyway.I disagree. I think SQL commands executed, lumping absolutely
everything together, really isn't much use.I'm inclined to agree. I think that's a quite useful stat when looking
at an installation one previously didn't have a lot of interactionwith.
Well, let's at least have an "other" category so you can add up the
counters and get a meaningful total.How would that meaningful total might help a user. What can a user might
analyse with the counter in 'other' category.The user could then judge if there were a significant number of examples
not covered in the other categories - this may, or may not, be a problem;
depending on the use case.Cheers,
GavinFor the user to be able to judge that whether the number in the 'other'
category is a problem or not, the user is also required to know what all
might fall under the 'other' category. It may not be good to say that
_anything_ that is not part of the already defined category is part of
'other'. Probably, 'other' should also be a set of predefined operations.
Neha
On Tue, Aug 23, 2016 at 3:59 AM, Andres Freund <andres@anarazel.de> wrote:
Haribabu's categorization
scheme seems to need some work, but the idea of categorizing
statements by type and counting executions per type seems very
reasonable.I'd consider instead using something like COALESCE(commandType,
nodeTag(Query->utilityStmt)) as the categories. Not sure if I'd even
pivot that.
CommandType means to use select, insert, update, delete and utility
as the categorization to display the counters? I think I am not getting
your point correctly.
Apart from the above, here are the following list of command tags that
are generated in the code, I took only the first word of the command tag
just to see how many categories present. The number indicates the
subset of operations or number of types it is used. Like create table,
create function and etc.
insert
delete
update
select (6)
transaction (10)
declare
close (2)
move
fetch
create (37)
drop (36)
Alter (56)
import
truncate
comment
security
copy
grant
revoke
notify
listen
unlisten
load
cluster
vacuum
analyze
explain
refresh
set (2)
reset
show
discard (4)
reassign
lock
checkpoint
reindex
prepare
execute
deallocate
we can try to pick something from the above list also for main
categories and rest will fall under
other.
Regards,
Hari Babu
Fujitsu Australia
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Haribabu Kommi wrote:
Apart from the above, here are the following list of command tags that
are generated in the code, I took only the first word of the command tag
just to see how many categories present. The number indicates the
subset of operations or number of types it is used. Like create table,
create function and etc.
Sounds about right. I suppose all those cases that you aggregated here
would expand to full tags in the actual code. I furthermore suppose
that some of these could be ignored, such as the transaction ones and
things like load, lock, move, fetch, discard, deallocate (maybe lump
them all together under "other", or some other rough categorization, as
Tom suggests). Also, for many of these commands it's probably relevant
whether they are acting on a temporary object or not; we should either
count these separately, or not count the temp ones at all.
insert
delete
update
select (6)
transaction (10)
declare
close (2)
move
fetch
create (37)
drop (36)
Alter (56)
import
truncate
comment
security
copy
grant
revoke
notify
listen
unlisten
load
cluster
vacuum
analyze
explain
refresh
set (2)
reset
show
discard (4)
reassign
lock
checkpoint
reindex
prepare
execute
deallocate
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Aug 31, 2016 at 3:19 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
Haribabu Kommi wrote:
Apart from the above, here are the following list of command tags that
are generated in the code, I took only the first word of the command tag
just to see how many categories present. The number indicates the
subset of operations or number of types it is used. Like create table,
create function and etc.Sounds about right. I suppose all those cases that you aggregated here
would expand to full tags in the actual code. I furthermore suppose
that some of these could be ignored, such as the transaction ones and
things like load, lock, move, fetch, discard, deallocate (maybe lump
them all together under "other", or some other rough categorization, as
Tom suggests).
Following is the pg_stat_sql view with the SQL categories that I considered
that are important. Rest of the them will be shown under others category.
postgres=# \d pg_stat_sql
View "pg_catalog.pg_stat_sql"
Column | Type | Modifiers
-----------------+--------------------------+-----------
inserts | bigint |
deletes | bigint |
updates | bigint |
selects | bigint |
declare_cursors | bigint |
closes | bigint |
creates | bigint |
drops | bigint |
alters | bigint |
imports | bigint |
truncates | bigint |
copies | bigint |
grants | bigint |
revokes | bigint |
clusters | bigint |
vacuums | bigint |
analyzes | bigint |
refreshs | bigint |
locks | bigint |
checkpoints | bigint |
reindexes | bigint |
deallocates | bigint |
others | bigint |
stats_reset | timestamp with time zone |
If any additions/deletions, I can accommodate them.
The stats data gets updated in exec_simple_query and exec_execute_message
functions and the collected stats will be sent to stats collector similar
like function usage stats in pgstat_report_stat function.
These SQL statistics data is stored in the stats file similar like global
statistics. The STAT file format is updated to accommodate the new stats.
A new GUC "track_sql" is added to track the SQL statement
statistics, by default this is off. Only superuser can change this
parameter.
Attached a patch for the same.
Also, for many of these commands it's probably relevant
whether they are acting on a temporary object or not; we should either
count these separately, or not count the temp ones at all.
Currently the SQL stats are not checking any object level that is a temp
one or not? The temp objects are specific to a backend only. But what
I feel, any way that is an SQL query that was executed on a temp object,
so we need to count that operation.
I feel this SQL stats should not worry about the object type. May be I am
wrong.
Regards,
Hari Babu
Fujitsu Australia
Attachments:
pg_stat_sql_1.patchapplication/octet-stream; name=pg_stat_sql_1.patchDownload
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ada2142..c6f7e53 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -842,6 +842,35 @@ CREATE VIEW pg_replication_origin_status AS
REVOKE ALL ON pg_replication_origin_status FROM public;
+CREATE VIEW pg_stat_sql AS
+ SELECT
+ pg_stat_get_insert_sqlstmt() AS inserts,
+ pg_stat_get_delete_sqlstmt() AS deletes,
+ pg_stat_get_update_sqlstmt() AS updates,
+ pg_stat_get_select_sqlstmt() AS selects,
+ pg_stat_get_declare_cursor_sqlstmt() AS declare_cursors,
+ pg_stat_get_close_sqlstmt() AS closes,
+ pg_stat_get_create_sqlstmt() AS creates,
+ pg_stat_get_drop_sqlstmt() AS drops,
+ pg_stat_get_alter_sqlstmt() AS alters,
+ pg_stat_get_import_sqlstmt() AS imports,
+ pg_stat_get_truncate_sqlstmt() AS truncates,
+ pg_stat_get_copy_sqlstmt() AS copies,
+ pg_stat_get_grant_sqlstmt() AS grants,
+ pg_stat_get_revoke_sqlstmt() AS revokes,
+ pg_stat_get_cluster_sqlstmt() AS clusters,
+ pg_stat_get_vacuum_sqlstmt() AS vacuums,
+ pg_stat_get_analyze_sqlstmt() AS analyzes,
+ pg_stat_get_refresh_sqlstmt() AS refreshs,
+ pg_stat_get_lock_sqlstmt() AS locks,
+ pg_stat_get_checkpoint_sqlstmt() AS checkpoints,
+ pg_stat_get_reindex_sqlstmt() AS reindexes,
+ pg_stat_get_deallocate_sqlstmt() AS deallocates,
+ pg_stat_get_others_sqlstmt() AS others,
+ pgx_stat_get_reset_time_sqlstmt() AS stats_reset;
+
+GRANT SELECT ON pg_stat_sql TO PUBLIC;
+
--
-- We have a few function definitions in here, too.
-- At some point there might be enough to justify breaking them out into
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 2f99aea..0f3bca8 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -105,6 +105,7 @@
*/
bool pgstat_track_activities = false;
bool pgstat_track_counts = false;
+bool pgstat_track_sql = false;
int pgstat_track_functions = TRACK_FUNC_OFF;
int pgstat_track_activity_query_size = 1024;
@@ -123,6 +124,13 @@ char *pgstat_stat_tmpname = NULL;
*/
PgStat_MsgBgWriter BgWriterStats;
+/*
+ * SQL statistics counter.
+ * This counter is incremented by each SQL Statement,
+ * And then, sent to the stat collector process.
+ */
+PgStat_MsgSqlstmt SqlstmtStatsLocal;
+
/* ----------
* Local data
* ----------
@@ -219,6 +227,7 @@ static int localNumBackends = 0;
*/
static PgStat_ArchiverStats archiverStats;
static PgStat_GlobalStats globalStats;
+static PgStat_SqlStmtStats sqlstmtStats;
/*
* List of OIDs of databases we need to write out. If an entry is InvalidOid,
@@ -267,6 +276,7 @@ static bool pgstat_db_requested(Oid databaseid);
static void pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg);
static void pgstat_send_funcstats(void);
+static void pgstat_send_sqlstmt(void);
static HTAB *pgstat_collect_oids(Oid catalogid);
static PgStat_TableStatus *get_tabstat_entry(Oid rel_id, bool isshared);
@@ -293,6 +303,7 @@ static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len);
static void pgstat_recv_recoveryconflict(PgStat_MsgRecoveryConflict *msg, int len);
static void pgstat_recv_deadlock(PgStat_MsgDeadlock *msg, int len);
static void pgstat_recv_tempfile(PgStat_MsgTempFile *msg, int len);
+static void pgstat_recv_sqlstmt(PgStat_MsgSqlstmt * msg, int len);
/* ------------------------------------------------------------
* Public functions called from postmaster follow
@@ -826,6 +837,10 @@ pgstat_report_stat(bool force)
/* Now, send function statistics */
pgstat_send_funcstats();
+
+ /* Now, send sql statistics */
+ if (pgstat_track_sql)
+ pgstat_send_sqlstmt();
}
/*
@@ -1252,11 +1267,13 @@ pgstat_reset_shared_counters(const char *target)
msg.m_resettarget = RESET_ARCHIVER;
else if (strcmp(target, "bgwriter") == 0)
msg.m_resettarget = RESET_BGWRITER;
+ else if (strcmp(target, "sqlstmt") == 0)
+ msg.m_resettarget = RESET_SQLSTMT;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized reset target: \"%s\"", target),
- errhint("Target must be \"archiver\" or \"bgwriter\".")));
+ errhint("Target must be \"archiver\" or \"bgwriter\" or \"sqlstmt\".")));
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSHAREDCOUNTER);
pgstat_send(&msg, sizeof(msg));
@@ -2465,6 +2482,21 @@ pgstat_fetch_global(void)
return &globalStats;
}
+/*
+ * ---------
+ * pgstat_fetch_sqlstmt() -
+ *
+ * Support function for the SQL-callable pgstat* functions. Returns
+ * a pointer to the sql statement statistics struct.
+ * ---------
+ */
+PgStat_SqlStmtStats *
+pgstat_fetch_sqlstmt(void)
+{
+ backend_read_statsfile();
+
+ return &sqlstmtStats;
+}
/* ------------------------------------------------------------
* Functions for management of the shared-memory PgBackendStatus array
@@ -3456,6 +3488,37 @@ pgstat_send_bgwriter(void)
MemSet(&BgWriterStats, 0, sizeof(BgWriterStats));
}
+/* ----------
+ * pgstat_send_sqlstmt(void)
+ *
+ * Send SQL statement statistics to the collector
+ * ----------
+ */
+static void
+pgstat_send_sqlstmt(void)
+{
+ /* We assume this initializes to zeroes */
+ static const PgStat_MsgSqlstmt all_zeroes;
+
+ /*
+ * This function can be called even if nothing at all has happened. In
+ * this case, avoid sending a completely empty message to the stats
+ * collector.
+ */
+ if (memcmp(&SqlstmtStatsLocal, &all_zeroes, sizeof(PgStat_MsgSqlstmt)) == 0)
+ return;
+
+ /*
+ * Prepare and send the message
+ */
+ pgstat_setheader(&SqlstmtStatsLocal.m_hdr, PGSTAT_MTYPE_SQLSTMT);
+ pgstat_send(&SqlstmtStatsLocal, sizeof(SqlstmtStatsLocal));
+
+ /*
+ * Clear out the statistics buffer, so it can be re-used.
+ */
+ MemSet(&SqlstmtStatsLocal, 0, sizeof(SqlstmtStatsLocal));
+}
/* ----------
* PgstatCollectorMain() -
@@ -3672,6 +3735,9 @@ PgstatCollectorMain(int argc, char *argv[])
pgstat_recv_tempfile((PgStat_MsgTempFile *) &msg, len);
break;
+ case PGSTAT_MTYPE_SQLSTMT:
+ pgstat_recv_sqlstmt((PgStat_MsgSqlstmt *) & msg, len);
+ break;
default:
break;
}
@@ -3937,6 +4003,12 @@ pgstat_write_statsfiles(bool permanent, bool allDbs)
(void) rc; /* we'll check for error with ferror */
/*
+ * Write Sql statement stats struct
+ */
+ rc = fwrite(&sqlstmtStats, sizeof(PgStat_SqlStmtStats), 1, fpout);
+ (void) rc; /* we'll check for error with ferror */
+
+ /*
* Walk through the database table.
*/
hash_seq_init(&hstat, pgStatDBHash);
@@ -4193,6 +4265,7 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep)
*/
memset(&globalStats, 0, sizeof(globalStats));
memset(&archiverStats, 0, sizeof(archiverStats));
+ memset(&sqlstmtStats, 0, sizeof(sqlstmtStats));
/*
* Set the current timestamp (will be kept only in case we can't load an
@@ -4200,6 +4273,7 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep)
*/
globalStats.stat_reset_timestamp = GetCurrentTimestamp();
archiverStats.stat_reset_timestamp = globalStats.stat_reset_timestamp;
+ sqlstmtStats.stat_reset_timestamp = globalStats.stat_reset_timestamp;
/*
* Try to open the stats file. If it doesn't exist, the backends simply
@@ -4252,6 +4326,16 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep)
}
/*
+ * Read Sql statement stats struct
+ */
+ if (fread(&sqlstmtStats, 1, sizeof(sqlstmtStats), fpin) != sizeof(sqlstmtStats))
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"", statfile)));
+ goto done;
+ }
+
+ /*
* We found an existing collector stats file. Read it and put all the
* hashtable entries into place.
*/
@@ -4540,6 +4624,7 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool permanent,
PgStat_StatDBEntry dbentry;
PgStat_GlobalStats myGlobalStats;
PgStat_ArchiverStats myArchiverStats;
+ PgStat_SqlStmtStats mySqlstmtStats;
FILE *fpin;
int32 format_id;
const char *statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : pgstat_stat_filename;
@@ -4594,6 +4679,18 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool permanent,
return false;
}
+ /*
+ * Read sql statement stats struct
+ */
+ if (fread(&mySqlstmtStats, 1, sizeof(mySqlstmtStats),
+ fpin) != sizeof(mySqlstmtStats))
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"", statfile)));
+ FreeFile(fpin);
+ return false;
+ }
+
/* By default, we're going to return the timestamp of the global file. */
*ts = myGlobalStats.stats_timestamp;
@@ -5156,6 +5253,12 @@ pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter *msg, int len)
memset(&archiverStats, 0, sizeof(archiverStats));
archiverStats.stat_reset_timestamp = GetCurrentTimestamp();
}
+ else if (msg->m_resettarget == RESET_SQLSTMT)
+ {
+ /* Reset the archiver statistics for the cluster. */
+ memset(&sqlstmtStats, 0, sizeof(sqlstmtStats));
+ sqlstmtStats.stat_reset_timestamp = GetCurrentTimestamp();
+ }
/*
* Presumably the sender of this message validated the target, don't
@@ -5336,6 +5439,40 @@ pgstat_recv_bgwriter(PgStat_MsgBgWriter *msg, int len)
}
/* ----------
+ * pgstat_recv_sqlstmt() -
+ *
+ * Process a SQL statement statistics message.
+ * ----------
+ */
+static void
+pgstat_recv_sqlstmt(PgStat_MsgSqlstmt * msg, int len)
+{
+ sqlstmtStats.n_insert_sqlstmt += msg->n_insert_sqlstmt;
+ sqlstmtStats.n_delete_sqlstmt += msg->n_delete_sqlstmt;
+ sqlstmtStats.n_update_sqlstmt += msg->n_update_sqlstmt;
+ sqlstmtStats.n_select_sqlstmt += msg->n_select_sqlstmt;
+ sqlstmtStats.n_declare_cursor_sqlstmt += msg->n_declare_cursor_sqlstmt;
+ sqlstmtStats.n_close_sqlstmt += msg->n_close_sqlstmt;
+ sqlstmtStats.n_create_sqlstmt += msg->n_create_sqlstmt;
+ sqlstmtStats.n_drop_sqlstmt += msg->n_drop_sqlstmt;
+ sqlstmtStats.n_alter_sqlstmt += msg->n_alter_sqlstmt;
+ sqlstmtStats.n_import_sqlstmt += msg->n_import_sqlstmt;
+ sqlstmtStats.n_truncate_sqlstmt += msg->n_truncate_sqlstmt;
+ sqlstmtStats.n_copy_sqlstmt += msg->n_copy_sqlstmt;
+ sqlstmtStats.n_grant_sqlstmt += msg->n_grant_sqlstmt;
+ sqlstmtStats.n_revoke_sqlstmt += msg->n_revoke_sqlstmt;
+ sqlstmtStats.n_cluster_sqlstmt += msg->n_cluster_sqlstmt;
+ sqlstmtStats.n_vacuum_sqlstmt += msg->n_vacuum_sqlstmt;
+ sqlstmtStats.n_analyze_sqlstmt += msg->n_analyze_sqlstmt;
+ sqlstmtStats.n_refresh_sqlstmt += msg->n_refresh_sqlstmt;
+ sqlstmtStats.n_lock_sqlstmt += msg->n_lock_sqlstmt;
+ sqlstmtStats.n_checkpoint_sqlstmt += msg->n_checkpoint_sqlstmt;
+ sqlstmtStats.n_reindex_sqlstmt += msg->n_reindex_sqlstmt;
+ sqlstmtStats.n_deallocate_sqlstmt += msg->n_deallocate_sqlstmt;
+ sqlstmtStats.n_others_sqlstmt += msg->n_others_sqlstmt;
+}
+
+/* ----------
* pgstat_recv_recoveryconflict() -
*
* Process a RECOVERYCONFLICT message.
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 98ccbbb..620a6c9 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -41,6 +41,7 @@
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "commands/async.h"
+#include "commands/defrem.h"
#include "commands/prepare.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
@@ -188,7 +189,7 @@ static bool IsTransactionStmtList(List *parseTrees);
static void drop_unnamed_stmt(void);
static void SigHupHandler(SIGNAL_ARGS);
static void log_disconnections(int code, Datum arg);
-
+static void CountSQLStmtExecutions(Node *parsetree);
/* ----------------------------------------------------------------
* routines to obtain user input
@@ -1102,6 +1103,12 @@ exec_simple_query(const char *query_string)
PortalDrop(portal, false);
+ /*
+ * Count SQL statement for pg_stat_sql view
+ */
+ if (pgstat_track_sql)
+ CountSQLStmtExecutions(parsetree);
+
if (IsA(parsetree, TransactionStmt))
{
/*
@@ -1983,6 +1990,29 @@ exec_execute_message(const char *portal_name, long max_rows)
(*receiver->rDestroy) (receiver);
+ /*
+ * Count SQL Statement for pgx_stat_sql
+ */
+ if (pgstat_track_sql)
+ {
+ CachedPlanSource *psrc = NULL;
+
+ if (portal->prepStmtName)
+ {
+ PreparedStatement *pstmt;
+
+ pstmt = FetchPreparedStatement(portal->prepStmtName, false);
+ if (pstmt)
+ psrc = pstmt->plansource;
+ }
+ else
+ psrc = unnamed_stmt_psrc;
+
+ if (psrc && !execute_is_fetch) /* psrc should not be NULL here, just
+ * for paranoia */
+ CountSQLStmtExecutions(psrc->raw_parse_tree);
+ }
+
if (completed)
{
if (is_xact_command)
@@ -4506,3 +4536,319 @@ log_disconnections(int code, Datum arg)
port->user_name, port->database_name, port->remote_host,
port->remote_port[0] ? " port=" : "", port->remote_port)));
}
+
+
+/*
+ * Count SQL statement for pg_stat_sql view
+ */
+static void
+CountSQLStmtExecutions(Node *parsetree)
+{
+ /*
+ * If pgstat_track_sql is true, parsetree should not be NULL. For safer,
+ * Check NULL here.
+ */
+ if (!parsetree)
+ return;
+
+ switch (nodeTag(parsetree))
+ {
+ /* raw plannable queries */
+ case T_InsertStmt:
+ SqlstmtStatsLocal.n_insert_sqlstmt++;
+ break;
+
+ case T_DeleteStmt:
+ SqlstmtStatsLocal.n_delete_sqlstmt++;
+ break;
+
+ case T_UpdateStmt:
+ SqlstmtStatsLocal.n_update_sqlstmt++;
+ break;
+
+ case T_SelectStmt:
+ SqlstmtStatsLocal.n_select_sqlstmt++;
+ break;
+
+ case T_DeclareCursorStmt:
+ SqlstmtStatsLocal.n_declare_cursor_sqlstmt++;
+ break;
+
+ case T_ClosePortalStmt:
+ SqlstmtStatsLocal.n_close_sqlstmt++;
+ break;
+
+ case T_CreateDomainStmt:
+ case T_CreateSchemaStmt:
+ case T_CreateStmt:
+ case T_CreateTableSpaceStmt:
+ case T_CreateExtensionStmt:
+ case T_CreateFdwStmt:
+ case T_CreateForeignServerStmt:
+ case T_CreateUserMappingStmt:
+ case T_CreateForeignTableStmt:
+ case T_DefineStmt:
+ case T_CompositeTypeStmt:
+ case T_CreateEnumStmt:
+ case T_CreateRangeStmt:
+ case T_ViewStmt:
+ case T_CreateFunctionStmt:
+ case T_IndexStmt:
+ case T_RuleStmt:
+ case T_CreateSeqStmt:
+ case T_CreatedbStmt:
+ case T_CreateTransformStmt:
+ case T_CreateTrigStmt:
+ case T_CreateEventTrigStmt:
+ case T_CreatePLangStmt:
+ case T_CreateRoleStmt:
+ case T_CreateConversionStmt:
+ case T_CreateCastStmt:
+ case T_CreateOpClassStmt:
+ case T_CreateOpFamilyStmt:
+ case T_CreatePolicyStmt:
+ case T_CreateAmStmt:
+ SqlstmtStatsLocal.n_create_sqlstmt++;
+ break;
+
+ case T_DropTableSpaceStmt:
+ case T_DropUserMappingStmt:
+ case T_DropStmt:
+ case T_DropdbStmt:
+ case T_DropRoleStmt:
+ case T_DropOwnedStmt:
+ SqlstmtStatsLocal.n_drop_sqlstmt++;
+ break;
+
+ case T_AlterTableSpaceOptionsStmt:
+ case T_AlterExtensionStmt:
+ case T_AlterExtensionContentsStmt:
+ case T_AlterFdwStmt:
+ case T_AlterForeignServerStmt:
+ case T_AlterUserMappingStmt:
+ case T_RenameStmt:
+ case T_AlterObjectDependsStmt:
+ case T_AlterObjectSchemaStmt:
+ case T_AlterOwnerStmt:
+ case T_AlterTableMoveAllStmt:
+ case T_AlterTableStmt:
+ case T_AlterDomainStmt:
+ case T_AlterFunctionStmt:
+ case T_AlterDefaultPrivilegesStmt:
+ case T_AlterEnumStmt:
+ case T_AlterSeqStmt:
+ case T_AlterDatabaseStmt:
+ case T_AlterDatabaseSetStmt:
+ case T_AlterSystemStmt:
+ case T_AlterEventTrigStmt:
+ case T_AlterRoleStmt:
+ case T_AlterRoleSetStmt:
+ case T_AlterOpFamilyStmt:
+ case T_AlterOperatorStmt:
+ case T_AlterTSDictionaryStmt:
+ case T_AlterTSConfigurationStmt:
+ case T_AlterPolicyStmt:
+ SqlstmtStatsLocal.n_alter_sqlstmt++;
+ break;
+
+ case T_ImportForeignSchemaStmt:
+ SqlstmtStatsLocal.n_import_sqlstmt++;
+ break;
+
+ case T_TruncateStmt:
+ SqlstmtStatsLocal.n_truncate_sqlstmt++;
+ break;
+
+ case T_CopyStmt:
+ SqlstmtStatsLocal.n_copy_sqlstmt++;
+ break;
+
+ case T_GrantStmt:
+ {
+ GrantStmt *stmt = (GrantStmt *) parsetree;
+
+ if (stmt->is_grant)
+ SqlstmtStatsLocal.n_grant_sqlstmt++;
+ else
+ SqlstmtStatsLocal.n_revoke_sqlstmt++;
+ }
+ break;
+
+ case T_GrantRoleStmt:
+ {
+ GrantRoleStmt *stmt = (GrantRoleStmt *) parsetree;
+
+ if (stmt->is_grant)
+ SqlstmtStatsLocal.n_grant_sqlstmt++;
+ else
+ SqlstmtStatsLocal.n_revoke_sqlstmt++;
+ }
+ break;
+
+ case T_ClusterStmt:
+ SqlstmtStatsLocal.n_cluster_sqlstmt++;
+ break;
+
+ case T_VacuumStmt:
+ if (((VacuumStmt *) parsetree)->options & VACOPT_VACUUM)
+ SqlstmtStatsLocal.n_vacuum_sqlstmt++;
+ else
+ SqlstmtStatsLocal.n_analyze_sqlstmt++;
+ break;
+
+ case T_ExplainStmt:
+ {
+ ExplainStmt *stmt = (ExplainStmt *) parsetree;
+ bool analyze = false;
+ ListCell *lc;
+
+ /* Look through an EXPLAIN ANALYZE to the contained stmt */
+ foreach(lc, stmt->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(lc);
+
+ if (strcmp(opt->defname, "analyze") == 0)
+ {
+ analyze = defGetBoolean(opt);
+ break;
+ }
+ }
+ if (analyze)
+ CountSQLStmtExecutions(stmt->query);
+ }
+ break;
+
+ case T_CreateTableAsStmt:
+ switch (((CreateTableAsStmt *) parsetree)->relkind)
+ {
+ case OBJECT_TABLE:
+ if (((CreateTableAsStmt *) parsetree)->is_select_into)
+ SqlstmtStatsLocal.n_select_sqlstmt++;
+ else
+ SqlstmtStatsLocal.n_create_sqlstmt++;
+ break;
+ case OBJECT_MATVIEW:
+ SqlstmtStatsLocal.n_create_sqlstmt++;
+ break;
+ default:
+ SqlstmtStatsLocal.n_others_sqlstmt++;
+ }
+ break;
+
+ case T_RefreshMatViewStmt:
+ SqlstmtStatsLocal.n_refresh_sqlstmt++;
+ break;
+
+ case T_LockStmt:
+ SqlstmtStatsLocal.n_lock_sqlstmt++;
+ break;
+
+ case T_CheckPointStmt:
+ SqlstmtStatsLocal.n_checkpoint_sqlstmt++;
+ break;
+
+ case T_ReindexStmt:
+ SqlstmtStatsLocal.n_reindex_sqlstmt++;
+ break;
+
+ case T_ExecuteStmt:
+ {
+ ExecuteStmt *stmt = (ExecuteStmt *) parsetree;
+ PreparedStatement *entry;
+
+ /* not our business to raise error */
+ entry = FetchPreparedStatement(stmt->name, false);
+ if (!entry || !entry->plansource)
+ break;
+ CountSQLStmtExecutions(entry->plansource->raw_parse_tree);
+ }
+ break;
+
+ case T_DeallocateStmt:
+ SqlstmtStatsLocal.n_deallocate_sqlstmt++;
+ break;
+
+ /* already-planned queries */
+ case T_PlannedStmt:
+ {
+ PlannedStmt *stmt = (PlannedStmt *) parsetree;
+
+ switch (stmt->commandType)
+ {
+ case CMD_SELECT:
+
+ /*
+ * We take a little extra care here so that the result
+ * will be useful for complaints about read-only
+ * statements
+ */
+ if (stmt->utilityStmt != NULL)
+ {
+ Assert(IsA(stmt->utilityStmt, DeclareCursorStmt));
+ SqlstmtStatsLocal.n_declare_cursor_sqlstmt++;
+ }
+ else
+ SqlstmtStatsLocal.n_select_sqlstmt++;
+ break;
+ case CMD_UPDATE:
+ SqlstmtStatsLocal.n_update_sqlstmt++;
+ break;
+ case CMD_INSERT:
+ SqlstmtStatsLocal.n_insert_sqlstmt++;
+ break;
+ case CMD_DELETE:
+ SqlstmtStatsLocal.n_delete_sqlstmt++;
+ break;
+ default:
+ SqlstmtStatsLocal.n_others_sqlstmt++;
+ break;
+ }
+ }
+ break;
+
+ /* parsed-and-rewritten-but-not-planned queries */
+ case T_Query:
+ {
+ Query *stmt = (Query *) parsetree;
+
+ switch (stmt->commandType)
+ {
+ case CMD_SELECT:
+
+ /*
+ * We take a little extra care here so that the result
+ * will be useful for complaints about read-only
+ * statements
+ */
+ if (stmt->utilityStmt != NULL)
+ {
+ Assert(IsA(stmt->utilityStmt, DeclareCursorStmt));
+ SqlstmtStatsLocal.n_declare_cursor_sqlstmt++;
+ }
+ else
+ SqlstmtStatsLocal.n_select_sqlstmt++;
+ break;
+ case CMD_UPDATE:
+ SqlstmtStatsLocal.n_revoke_sqlstmt++;
+ break;
+ case CMD_INSERT:
+ SqlstmtStatsLocal.n_revoke_sqlstmt++;
+ break;
+ case CMD_DELETE:
+ SqlstmtStatsLocal.n_revoke_sqlstmt++;
+ break;
+ case CMD_UTILITY:
+ CountSQLStmtExecutions(stmt->utilityStmt);
+ break;
+ default:
+ SqlstmtStatsLocal.n_others_sqlstmt++;
+ break;
+ }
+ }
+ break;
+
+ default:
+ SqlstmtStatsLocal.n_others_sqlstmt++;
+ break;
+ }
+}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 1bba5fa..4993bad 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -129,6 +129,31 @@ extern Datum pg_stat_reset_shared(PG_FUNCTION_ARGS);
extern Datum pg_stat_reset_single_table_counters(PG_FUNCTION_ARGS);
extern Datum pg_stat_reset_single_function_counters(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_insert_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_delete_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_update_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_select_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_declare_cursor_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_close_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_create_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_drop_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_alter_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_import_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_truncate_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_copy_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_grant_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_revoke_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_cluster_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_vacuum_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_analyze_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_refresh_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_lock_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_checkpoint_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_reindex_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_deallocate_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_others_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pgx_stat_get_reset_time_sqlstmt(PG_FUNCTION_ARGS);
+
/* Global bgwriter statistics, from bgwriter.c */
extern PgStat_MsgBgWriter bgwriterStats;
@@ -1935,3 +1960,147 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(HeapTupleGetDatum(
heap_form_tuple(tupdesc, values, nulls)));
}
+
+Datum
+pg_stat_get_insert_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_insert_sqlstmt);
+}
+
+Datum
+pg_stat_get_delete_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_delete_sqlstmt);
+}
+
+Datum
+pg_stat_get_update_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_update_sqlstmt);
+}
+
+Datum
+pg_stat_get_select_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_select_sqlstmt);
+}
+
+Datum
+pg_stat_get_declare_cursor_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_declare_cursor_sqlstmt);
+}
+
+Datum
+pg_stat_get_close_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_close_sqlstmt);
+}
+
+Datum
+pg_stat_get_create_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_create_sqlstmt);
+}
+
+Datum
+pg_stat_get_drop_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_drop_sqlstmt);
+}
+
+Datum
+pg_stat_get_alter_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_alter_sqlstmt);
+}
+
+Datum
+pg_stat_get_import_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_import_sqlstmt);
+}
+
+Datum
+pg_stat_get_truncate_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_truncate_sqlstmt);
+}
+
+Datum
+pg_stat_get_copy_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_copy_sqlstmt);
+}
+
+Datum
+pg_stat_get_grant_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_grant_sqlstmt);
+}
+
+Datum
+pg_stat_get_revoke_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_revoke_sqlstmt);
+}
+
+Datum
+pg_stat_get_cluster_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_cluster_sqlstmt);
+}
+
+Datum
+pg_stat_get_vacuum_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_vacuum_sqlstmt);
+}
+
+Datum
+pg_stat_get_analyze_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_analyze_sqlstmt);
+}
+
+Datum
+pg_stat_get_refresh_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_refresh_sqlstmt);
+}
+
+Datum
+pg_stat_get_lock_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_lock_sqlstmt);
+}
+
+Datum
+pg_stat_get_checkpoint_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_checkpoint_sqlstmt);
+}
+
+Datum
+pg_stat_get_reindex_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_reindex_sqlstmt);
+}
+
+Datum
+pg_stat_get_deallocate_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_deallocate_sqlstmt);
+}
+
+Datum
+pg_stat_get_others_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_others_sqlstmt);
+}
+
+Datum
+pgx_stat_get_reset_time_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_TIMESTAMPTZ(pgstat_fetch_sqlstmt()->stat_reset_timestamp);
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index c5178f7..647a0c6 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1205,6 +1205,16 @@ static struct config_bool ConfigureNamesBool[] =
},
{
+ {"track_sql", PGC_SUSET, STATS_COLLECTOR,
+ gettext_noop("Collects timing statistics for database I/O activity."),
+ NULL
+ },
+ &pgstat_track_sql,
+ false,
+ NULL, NULL, NULL
+ },
+
+ {
{"update_process_title", PGC_SUSET, PROCESS_TITLE,
gettext_noop("Updates the process title to show the active SQL command."),
gettext_noop("Enables updating of the process title every time a new SQL command is received by the server.")
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..ea77476 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2896,6 +2896,57 @@ DESCR("statistics: total execution time of function in current transaction, in m
DATA(insert OID = 3048 ( pg_stat_get_xact_function_self_time PGNSP PGUID 12 1 0 0 0 f f f f t f v r 1 0 701 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_xact_function_self_time _null_ _null_ _null_ ));
DESCR("statistics: self execution time of function in current transaction, in msec");
+
+DATA(insert OID = 3401 ( pg_stat_get_insert_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_insert_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of insert sql statement executions");
+DATA(insert OID = 3402 ( pg_stat_get_delete_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_delete_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of delete sql statement executions");
+DATA(insert OID = 3403 ( pg_stat_get_update_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_update_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of update sql statement executions");
+DATA(insert OID = 3404 ( pg_stat_get_select_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_select_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of select sql statement executions");
+DATA(insert OID = 3405 ( pg_stat_get_declare_cursor_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_declare_cursor_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of declare cursor sql statement executions");
+DATA(insert OID = 3406 ( pg_stat_get_close_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_close_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of close sql statement executions");
+DATA(insert OID = 3407 ( pg_stat_get_create_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_create_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of create sql statement executions");
+DATA(insert OID = 3408 ( pg_stat_get_drop_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_drop_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of drop sql statement executions");
+DATA(insert OID = 3409 ( pg_stat_get_alter_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_alter_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of alter sql statement executions");
+DATA(insert OID = 3410 ( pg_stat_get_import_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_import_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of import sql statement executions");
+DATA(insert OID = 3411 ( pg_stat_get_truncate_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_truncate_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of truncate sql statement executions");
+DATA(insert OID = 3412 ( pg_stat_get_copy_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_copy_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of copy sql statement executions");
+DATA(insert OID = 3413 ( pg_stat_get_grant_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_grant_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of grant sql statement executions");
+DATA(insert OID = 3414 ( pg_stat_get_revoke_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_revoke_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of revoke sql statement executions");
+DATA(insert OID = 3415 ( pg_stat_get_cluster_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_cluster_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of cluster sql statement executions");
+DATA(insert OID = 3416 ( pg_stat_get_vacuum_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_vacuum_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of vacuum sql statement executions");
+DATA(insert OID = 3417 ( pg_stat_get_analyze_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_analyze_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of analyze sql statement executions");
+DATA(insert OID = 3418 ( pg_stat_get_refresh_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_refresh_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of refresh sql statement executions");
+DATA(insert OID = 3419 ( pg_stat_get_lock_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_lock_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of lock sql statement executions");
+DATA(insert OID = 3420 ( pg_stat_get_checkpoint_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_checkpoint_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of checkpoint sql statement executions");
+DATA(insert OID = 3421 ( pg_stat_get_reindex_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_reindex_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of reindex sql statement executions");
+DATA(insert OID = 3422 ( pg_stat_get_deallocate_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_deallocate_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of deallocate sql statement executions");
+DATA(insert OID = 3423 ( pg_stat_get_others_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_others_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of other sql statement executions");
+DATA(insert OID = 3424 ( pgx_stat_get_reset_time_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 1184 "" _null_ _null_ _null_ _null_ _null_ pgx_stat_get_reset_time_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of other sql statement executions");
+
+
DATA(insert OID = 3788 ( pg_stat_get_snapshot_timestamp PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 1184 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_snapshot_timestamp _null_ _null_ _null_ ));
DESCR("statistics: timestamp of the current statistics snapshot");
DATA(insert OID = 2230 ( pg_stat_clear_snapshot PGNSP PGUID 12 1 0 0 0 f f f f f f v r 0 0 2278 "" _null_ _null_ _null_ _null_ _null_ pg_stat_clear_snapshot _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index dc3320d..44c6127 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -64,7 +64,8 @@ typedef enum StatMsgType
PGSTAT_MTYPE_FUNCPURGE,
PGSTAT_MTYPE_RECOVERYCONFLICT,
PGSTAT_MTYPE_TEMPFILE,
- PGSTAT_MTYPE_DEADLOCK
+ PGSTAT_MTYPE_DEADLOCK,
+ PGSTAT_MTYPE_SQLSTMT
} StatMsgType;
/* ----------
@@ -119,7 +120,8 @@ typedef struct PgStat_TableCounts
typedef enum PgStat_Shared_Reset_Target
{
RESET_ARCHIVER,
- RESET_BGWRITER
+ RESET_BGWRITER,
+ RESET_SQLSTMT
} PgStat_Shared_Reset_Target;
/* Possible object types for resetting single counters */
@@ -530,6 +532,38 @@ typedef struct PgStat_MsgDeadlock
Oid m_databaseid;
} PgStat_MsgDeadlock;
+/* ----------
+ * PgStat_MsgSqlstmt Sent by the sql to update statistics.
+ * ----------
+ */
+typedef struct PgStat_MsgSqlstmt
+{
+ PgStat_MsgHdr m_hdr;
+
+ PgStat_Counter n_insert_sqlstmt;
+ PgStat_Counter n_delete_sqlstmt;
+ PgStat_Counter n_update_sqlstmt;
+ PgStat_Counter n_select_sqlstmt;
+ PgStat_Counter n_declare_cursor_sqlstmt;
+ PgStat_Counter n_close_sqlstmt;
+ PgStat_Counter n_create_sqlstmt;
+ PgStat_Counter n_drop_sqlstmt;
+ PgStat_Counter n_alter_sqlstmt;
+ PgStat_Counter n_import_sqlstmt;
+ PgStat_Counter n_truncate_sqlstmt;
+ PgStat_Counter n_copy_sqlstmt;
+ PgStat_Counter n_grant_sqlstmt;
+ PgStat_Counter n_revoke_sqlstmt;
+ PgStat_Counter n_cluster_sqlstmt;
+ PgStat_Counter n_vacuum_sqlstmt;
+ PgStat_Counter n_analyze_sqlstmt;
+ PgStat_Counter n_refresh_sqlstmt;
+ PgStat_Counter n_lock_sqlstmt;
+ PgStat_Counter n_checkpoint_sqlstmt;
+ PgStat_Counter n_reindex_sqlstmt;
+ PgStat_Counter n_deallocate_sqlstmt;
+ PgStat_Counter n_others_sqlstmt;
+} PgStat_MsgSqlstmt;
/* ----------
* PgStat_Msg Union over all possible messages.
@@ -555,6 +589,7 @@ typedef union PgStat_Msg
PgStat_MsgFuncpurge msg_funcpurge;
PgStat_MsgRecoveryConflict msg_recoveryconflict;
PgStat_MsgDeadlock msg_deadlock;
+ PgStat_MsgSqlstmt msg_sqlstmt;
} PgStat_Msg;
@@ -566,7 +601,7 @@ typedef union PgStat_Msg
* ------------------------------------------------------------
*/
-#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9D
+#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9E
/* ----------
* PgStat_StatDBEntry The collector's data per database
@@ -675,6 +710,34 @@ typedef struct PgStat_ArchiverStats
TimestampTz stat_reset_timestamp;
} PgStat_ArchiverStats;
+typedef struct PgStat_SqlStmtStats
+{
+ PgStat_Counter n_insert_sqlstmt;
+ PgStat_Counter n_delete_sqlstmt;
+ PgStat_Counter n_update_sqlstmt;
+ PgStat_Counter n_select_sqlstmt;
+ PgStat_Counter n_declare_cursor_sqlstmt;
+ PgStat_Counter n_close_sqlstmt;
+ PgStat_Counter n_create_sqlstmt;
+ PgStat_Counter n_drop_sqlstmt;
+ PgStat_Counter n_alter_sqlstmt;
+ PgStat_Counter n_import_sqlstmt;
+ PgStat_Counter n_truncate_sqlstmt;
+ PgStat_Counter n_copy_sqlstmt;
+ PgStat_Counter n_grant_sqlstmt;
+ PgStat_Counter n_revoke_sqlstmt;
+ PgStat_Counter n_cluster_sqlstmt;
+ PgStat_Counter n_vacuum_sqlstmt;
+ PgStat_Counter n_analyze_sqlstmt;
+ PgStat_Counter n_refresh_sqlstmt;
+ PgStat_Counter n_lock_sqlstmt;
+ PgStat_Counter n_checkpoint_sqlstmt;
+ PgStat_Counter n_reindex_sqlstmt;
+ PgStat_Counter n_deallocate_sqlstmt;
+ PgStat_Counter n_others_sqlstmt;
+ TimestampTz stat_reset_timestamp;
+} PgStat_SqlStmtStats;
+
/*
* Global statistics kept in the stats collector
*/
@@ -918,6 +981,7 @@ typedef struct PgStat_FunctionCallUsage
*/
extern bool pgstat_track_activities;
extern bool pgstat_track_counts;
+extern bool pgstat_track_sql;
extern int pgstat_track_functions;
extern PGDLLIMPORT int pgstat_track_activity_query_size;
extern char *pgstat_stat_directory;
@@ -930,6 +994,11 @@ extern char *pgstat_stat_filename;
extern PgStat_MsgBgWriter BgWriterStats;
/*
+ * SQL statistics counter updated by each backend
+ */
+extern PgStat_MsgSqlstmt SqlstmtStatsLocal;
+
+/*
* Updated by pgstat_count_buffer_*_time macros
*/
extern PgStat_Counter pgStatBlockReadTime;
@@ -1141,5 +1210,6 @@ extern PgStat_StatFuncEntry *pgstat_fetch_stat_funcentry(Oid funcid);
extern int pgstat_fetch_stat_numbackends(void);
extern PgStat_ArchiverStats *pgstat_fetch_stat_archiver(void);
extern PgStat_GlobalStats *pgstat_fetch_global(void);
+extern PgStat_SqlStmtStats *pgstat_fetch_sqlstmt(void);
#endif /* PGSTAT_H */
On Fri, Sep 2, 2016 at 2:33 AM, Haribabu Kommi <kommi.haribabu@gmail.com> wrote:
On Wed, Aug 31, 2016 at 3:19 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:Haribabu Kommi wrote:
Apart from the above, here are the following list of command tags that
are generated in the code, I took only the first word of the command tag
just to see how many categories present. The number indicates the
subset of operations or number of types it is used. Like create table,
create function and etc.Sounds about right. I suppose all those cases that you aggregated here
would expand to full tags in the actual code. I furthermore suppose
that some of these could be ignored, such as the transaction ones and
things like load, lock, move, fetch, discard, deallocate (maybe lump
them all together under "other", or some other rough categorization, as
Tom suggests).Following is the pg_stat_sql view with the SQL categories that I considered
that are important. Rest of the them will be shown under others category.postgres=# \d pg_stat_sql
View "pg_catalog.pg_stat_sql"
Column | Type | Modifiers
-----------------+--------------------------+-----------
inserts | bigint |
deletes | bigint |
updates | bigint |
selects | bigint |
declare_cursors | bigint |
closes | bigint |
creates | bigint |
drops | bigint |
alters | bigint |
imports | bigint |
truncates | bigint |
copies | bigint |
grants | bigint |
revokes | bigint |
clusters | bigint |
vacuums | bigint |
analyzes | bigint |
refreshs | bigint |
locks | bigint |
checkpoints | bigint |
reindexes | bigint |
deallocates | bigint |
others | bigint |
stats_reset | timestamp with time zone |If any additions/deletions, I can accommodate them.
I think it is not a good idea to make the command names used here the
plural forms of the command tags. Instead of "inserts", "updates",
"imports", etc. just use "INSERT", "UPDATE", "IMPORT". That's simpler
and less error prone - e.g. you won't end up with things like
"refreshs", which is not a word.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Sep 15, 2016 at 6:01 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Sep 2, 2016 at 2:33 AM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:On Wed, Aug 31, 2016 at 3:19 AM, Alvaro Herrera <
alvherre@2ndquadrant.com>
wrote:
Haribabu Kommi wrote:
Apart from the above, here are the following list of command tags that
are generated in the code, I took only the first word of the commandtag
just to see how many categories present. The number indicates the
subset of operations or number of types it is used. Like create table,
create function and etc.Sounds about right. I suppose all those cases that you aggregated here
would expand to full tags in the actual code. I furthermore suppose
that some of these could be ignored, such as the transaction ones and
things like load, lock, move, fetch, discard, deallocate (maybe lump
them all together under "other", or some other rough categorization, as
Tom suggests).Following is the pg_stat_sql view with the SQL categories that I
considered
that are important. Rest of the them will be shown under others category.
postgres=# \d pg_stat_sql
View "pg_catalog.pg_stat_sql"
Column | Type | Modifiers
-----------------+--------------------------+-----------
inserts | bigint |
deletes | bigint |
updates | bigint |
selects | bigint |
declare_cursors | bigint |
closes | bigint |
creates | bigint |
drops | bigint |
alters | bigint |
imports | bigint |
truncates | bigint |
copies | bigint |
grants | bigint |
revokes | bigint |
clusters | bigint |
vacuums | bigint |
analyzes | bigint |
refreshs | bigint |
locks | bigint |
checkpoints | bigint |
reindexes | bigint |
deallocates | bigint |
others | bigint |
stats_reset | timestamp with time zone |If any additions/deletions, I can accommodate them.
I think it is not a good idea to make the command names used here the
plural forms of the command tags. Instead of "inserts", "updates",
"imports", etc. just use "INSERT", "UPDATE", "IMPORT". That's simpler
and less error prone - e.g. you won't end up with things like
"refreshs", which is not a word.
Thanks for your suggestion. I also thought of changing the name while
writing
"refreshs" as a column name of the view originally. A small restriction
with the
change of names to command names is that, user needs to execute the query
as follows.
select pg_stat_sql.select from pg_stat_sql;
Updated patch is attached with the corrections. Apart from name change,
added documentation and tests.
Regards,
Hari Babu
Fujitsu Australia
Attachments:
pg_stat_sql_2.patchapplication/octet-stream; name=pg_stat_sql_2.patchDownload
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cd66abc..8231d28 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5552,6 +5552,21 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
</listitem>
</varlistentry>
+ <varlistentry id="guc-track-sql" xreflabel="track_sql">
+ <term><varname>track_sql</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>track_sql</> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Enables collection of different SQL statement statistics that are
+ executed on the instance. This parameter is off by default. Only
+ superusers can change this setting.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-stats-temp-directory" xreflabel="stats_temp_directory">
<term><varname>stats_temp_directory</varname> (<type>string</type>)
<indexterm>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 0776428..ff0cb6d 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -513,6 +513,13 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
<command>VACUUM</>, showing current progress.
See <xref linkend='vacuum-progress-reporting'>.</entry>
</row>
+
+ <row>
+ <entry><structname>pg_stat_sql</><indexterm><primary>pg_stat_sql</primary></indexterm></entry>
+ <entry>One row only, showing statistics about the SQL statements that are
+ executed on the instance.
+ See <xref linkend="pg-stat-sql-view"> for details.</entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -2222,6 +2229,150 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
controls exactly which functions are tracked.
</para>
+ <table id="pg-stat-sql-view" xreflabel="pg_stat_sql">
+ <title><structname>pg_stat_sql</structname> View</title>
+ <tgroup cols="3">
+ <thead>
+ <row>
+ <entry>Column</entry>
+ <entry>Type</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry><structfield>insert</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of insert SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>delete</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of delete SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>update</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of update SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>select</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of select SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>declar_cursor</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of declare_cursor SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>close</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of close SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>create</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of create SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>drop</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of drop SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>alter</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of alter SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>import</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of import SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>truncate</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of truncate SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>copy</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of copy SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>grant</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of grant SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>revoke</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of revoke SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>cluster</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of cluster SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>vacuum</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of vacuum SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>analyze</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of analyze SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>refresh</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of refresh SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>lock</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of lock SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>checkpoint</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of checkpoint SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>reindex</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of reindex SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>deallocate</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of deallocate SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>others</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of others SQL statements executed</entry>
+ </row>
+ <row>
+ <entry><structfield>stats_reset</></entry>
+ <entry><type>timestamp with time zone</type></entry>
+ <entry>Time at which these statistics were last reset</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The <structname>pg_stat_sql</structname> view will contain only
+ one row, showing statistics about number of SQL statements that are
+ executed on the instance. The <xref linkend="guc-track-sql"> parameter
+ controls the SQL statement execution statistics.
+ </para>
+
+
</sect2>
<sect2 id="monitoring-stats-functions">
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ada2142..0e39920 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -842,6 +842,35 @@ CREATE VIEW pg_replication_origin_status AS
REVOKE ALL ON pg_replication_origin_status FROM public;
+CREATE VIEW pg_stat_sql AS
+ SELECT
+ pg_stat_get_insert_sqlstmt() AS insert,
+ pg_stat_get_delete_sqlstmt() AS delete,
+ pg_stat_get_update_sqlstmt() AS update,
+ pg_stat_get_select_sqlstmt() AS select,
+ pg_stat_get_declare_cursor_sqlstmt() AS declare_cursor,
+ pg_stat_get_close_sqlstmt() AS close,
+ pg_stat_get_create_sqlstmt() AS create,
+ pg_stat_get_drop_sqlstmt() AS drop,
+ pg_stat_get_alter_sqlstmt() AS alter,
+ pg_stat_get_import_sqlstmt() AS import,
+ pg_stat_get_truncate_sqlstmt() AS truncate,
+ pg_stat_get_copy_sqlstmt() AS copy,
+ pg_stat_get_grant_sqlstmt() AS grant,
+ pg_stat_get_revoke_sqlstmt() AS revoke,
+ pg_stat_get_cluster_sqlstmt() AS cluster,
+ pg_stat_get_vacuum_sqlstmt() AS vacuum,
+ pg_stat_get_analyze_sqlstmt() AS analyze,
+ pg_stat_get_refresh_sqlstmt() AS refresh,
+ pg_stat_get_lock_sqlstmt() AS lock,
+ pg_stat_get_checkpoint_sqlstmt() AS checkpoint,
+ pg_stat_get_reindex_sqlstmt() AS reindex,
+ pg_stat_get_deallocate_sqlstmt() AS deallocate,
+ pg_stat_get_others_sqlstmt() AS others,
+ pgx_stat_get_reset_time_sqlstmt() AS stats_reset;
+
+GRANT SELECT ON pg_stat_sql TO PUBLIC;
+
--
-- We have a few function definitions in here, too.
-- At some point there might be enough to justify breaking them out into
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 8a2ce91..4cc5533 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -105,6 +105,7 @@
*/
bool pgstat_track_activities = false;
bool pgstat_track_counts = false;
+bool pgstat_track_sql = false;
int pgstat_track_functions = TRACK_FUNC_OFF;
int pgstat_track_activity_query_size = 1024;
@@ -123,6 +124,13 @@ char *pgstat_stat_tmpname = NULL;
*/
PgStat_MsgBgWriter BgWriterStats;
+/*
+ * SQL statistics counter.
+ * This counter is incremented by each SQL Statement,
+ * And then, sent to the stat collector process.
+ */
+PgStat_MsgSqlstmt SqlstmtStatsLocal;
+
/* ----------
* Local data
* ----------
@@ -219,6 +227,7 @@ static int localNumBackends = 0;
*/
static PgStat_ArchiverStats archiverStats;
static PgStat_GlobalStats globalStats;
+static PgStat_SqlStmtStats sqlstmtStats;
/*
* List of OIDs of databases we need to write out. If an entry is InvalidOid,
@@ -267,6 +276,7 @@ static bool pgstat_db_requested(Oid databaseid);
static void pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg);
static void pgstat_send_funcstats(void);
+static void pgstat_send_sqlstmt(void);
static HTAB *pgstat_collect_oids(Oid catalogid);
static PgStat_TableStatus *get_tabstat_entry(Oid rel_id, bool isshared);
@@ -293,6 +303,7 @@ static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len);
static void pgstat_recv_recoveryconflict(PgStat_MsgRecoveryConflict *msg, int len);
static void pgstat_recv_deadlock(PgStat_MsgDeadlock *msg, int len);
static void pgstat_recv_tempfile(PgStat_MsgTempFile *msg, int len);
+static void pgstat_recv_sqlstmt(PgStat_MsgSqlstmt * msg, int len);
/* ------------------------------------------------------------
* Public functions called from postmaster follow
@@ -826,6 +837,10 @@ pgstat_report_stat(bool force)
/* Now, send function statistics */
pgstat_send_funcstats();
+
+ /* Now, send sql statistics */
+ if (pgstat_track_sql)
+ pgstat_send_sqlstmt();
}
/*
@@ -1252,11 +1267,13 @@ pgstat_reset_shared_counters(const char *target)
msg.m_resettarget = RESET_ARCHIVER;
else if (strcmp(target, "bgwriter") == 0)
msg.m_resettarget = RESET_BGWRITER;
+ else if (strcmp(target, "sqlstmt") == 0)
+ msg.m_resettarget = RESET_SQLSTMT;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized reset target: \"%s\"", target),
- errhint("Target must be \"archiver\" or \"bgwriter\".")));
+ errhint("Target must be \"archiver\" or \"bgwriter\" or \"sqlstmt\".")));
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSHAREDCOUNTER);
pgstat_send(&msg, sizeof(msg));
@@ -2465,6 +2482,21 @@ pgstat_fetch_global(void)
return &globalStats;
}
+/*
+ * ---------
+ * pgstat_fetch_sqlstmt() -
+ *
+ * Support function for the SQL-callable pgstat* functions. Returns
+ * a pointer to the sql statement statistics struct.
+ * ---------
+ */
+PgStat_SqlStmtStats *
+pgstat_fetch_sqlstmt(void)
+{
+ backend_read_statsfile();
+
+ return &sqlstmtStats;
+}
/* ------------------------------------------------------------
* Functions for management of the shared-memory PgBackendStatus array
@@ -3456,6 +3488,37 @@ pgstat_send_bgwriter(void)
MemSet(&BgWriterStats, 0, sizeof(BgWriterStats));
}
+/* ----------
+ * pgstat_send_sqlstmt(void)
+ *
+ * Send SQL statement statistics to the collector
+ * ----------
+ */
+static void
+pgstat_send_sqlstmt(void)
+{
+ /* We assume this initializes to zeroes */
+ static const PgStat_MsgSqlstmt all_zeroes;
+
+ /*
+ * This function can be called even if nothing at all has happened. In
+ * this case, avoid sending a completely empty message to the stats
+ * collector.
+ */
+ if (memcmp(&SqlstmtStatsLocal, &all_zeroes, sizeof(PgStat_MsgSqlstmt)) == 0)
+ return;
+
+ /*
+ * Prepare and send the message
+ */
+ pgstat_setheader(&SqlstmtStatsLocal.m_hdr, PGSTAT_MTYPE_SQLSTMT);
+ pgstat_send(&SqlstmtStatsLocal, sizeof(SqlstmtStatsLocal));
+
+ /*
+ * Clear out the statistics buffer, so it can be re-used.
+ */
+ MemSet(&SqlstmtStatsLocal, 0, sizeof(SqlstmtStatsLocal));
+}
/* ----------
* PgstatCollectorMain() -
@@ -3672,6 +3735,9 @@ PgstatCollectorMain(int argc, char *argv[])
pgstat_recv_tempfile((PgStat_MsgTempFile *) &msg, len);
break;
+ case PGSTAT_MTYPE_SQLSTMT:
+ pgstat_recv_sqlstmt((PgStat_MsgSqlstmt *) & msg, len);
+ break;
default:
break;
}
@@ -3937,6 +4003,12 @@ pgstat_write_statsfiles(bool permanent, bool allDbs)
(void) rc; /* we'll check for error with ferror */
/*
+ * Write Sql statement stats struct
+ */
+ rc = fwrite(&sqlstmtStats, sizeof(PgStat_SqlStmtStats), 1, fpout);
+ (void) rc; /* we'll check for error with ferror */
+
+ /*
* Walk through the database table.
*/
hash_seq_init(&hstat, pgStatDBHash);
@@ -4193,6 +4265,7 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep)
*/
memset(&globalStats, 0, sizeof(globalStats));
memset(&archiverStats, 0, sizeof(archiverStats));
+ memset(&sqlstmtStats, 0, sizeof(sqlstmtStats));
/*
* Set the current timestamp (will be kept only in case we can't load an
@@ -4200,6 +4273,7 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep)
*/
globalStats.stat_reset_timestamp = GetCurrentTimestamp();
archiverStats.stat_reset_timestamp = globalStats.stat_reset_timestamp;
+ sqlstmtStats.stat_reset_timestamp = globalStats.stat_reset_timestamp;
/*
* Try to open the stats file. If it doesn't exist, the backends simply
@@ -4252,6 +4326,16 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep)
}
/*
+ * Read Sql statement stats struct
+ */
+ if (fread(&sqlstmtStats, 1, sizeof(sqlstmtStats), fpin) != sizeof(sqlstmtStats))
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"", statfile)));
+ goto done;
+ }
+
+ /*
* We found an existing collector stats file. Read it and put all the
* hashtable entries into place.
*/
@@ -4540,6 +4624,7 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool permanent,
PgStat_StatDBEntry dbentry;
PgStat_GlobalStats myGlobalStats;
PgStat_ArchiverStats myArchiverStats;
+ PgStat_SqlStmtStats mySqlstmtStats;
FILE *fpin;
int32 format_id;
const char *statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : pgstat_stat_filename;
@@ -4594,6 +4679,18 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool permanent,
return false;
}
+ /*
+ * Read sql statement stats struct
+ */
+ if (fread(&mySqlstmtStats, 1, sizeof(mySqlstmtStats),
+ fpin) != sizeof(mySqlstmtStats))
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"", statfile)));
+ FreeFile(fpin);
+ return false;
+ }
+
/* By default, we're going to return the timestamp of the global file. */
*ts = myGlobalStats.stats_timestamp;
@@ -5156,6 +5253,12 @@ pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter *msg, int len)
memset(&archiverStats, 0, sizeof(archiverStats));
archiverStats.stat_reset_timestamp = GetCurrentTimestamp();
}
+ else if (msg->m_resettarget == RESET_SQLSTMT)
+ {
+ /* Reset the archiver statistics for the cluster. */
+ memset(&sqlstmtStats, 0, sizeof(sqlstmtStats));
+ sqlstmtStats.stat_reset_timestamp = GetCurrentTimestamp();
+ }
/*
* Presumably the sender of this message validated the target, don't
@@ -5336,6 +5439,40 @@ pgstat_recv_bgwriter(PgStat_MsgBgWriter *msg, int len)
}
/* ----------
+ * pgstat_recv_sqlstmt() -
+ *
+ * Process a SQL statement statistics message.
+ * ----------
+ */
+static void
+pgstat_recv_sqlstmt(PgStat_MsgSqlstmt * msg, int len)
+{
+ sqlstmtStats.n_insert_sqlstmt += msg->n_insert_sqlstmt;
+ sqlstmtStats.n_delete_sqlstmt += msg->n_delete_sqlstmt;
+ sqlstmtStats.n_update_sqlstmt += msg->n_update_sqlstmt;
+ sqlstmtStats.n_select_sqlstmt += msg->n_select_sqlstmt;
+ sqlstmtStats.n_declare_cursor_sqlstmt += msg->n_declare_cursor_sqlstmt;
+ sqlstmtStats.n_close_sqlstmt += msg->n_close_sqlstmt;
+ sqlstmtStats.n_create_sqlstmt += msg->n_create_sqlstmt;
+ sqlstmtStats.n_drop_sqlstmt += msg->n_drop_sqlstmt;
+ sqlstmtStats.n_alter_sqlstmt += msg->n_alter_sqlstmt;
+ sqlstmtStats.n_import_sqlstmt += msg->n_import_sqlstmt;
+ sqlstmtStats.n_truncate_sqlstmt += msg->n_truncate_sqlstmt;
+ sqlstmtStats.n_copy_sqlstmt += msg->n_copy_sqlstmt;
+ sqlstmtStats.n_grant_sqlstmt += msg->n_grant_sqlstmt;
+ sqlstmtStats.n_revoke_sqlstmt += msg->n_revoke_sqlstmt;
+ sqlstmtStats.n_cluster_sqlstmt += msg->n_cluster_sqlstmt;
+ sqlstmtStats.n_vacuum_sqlstmt += msg->n_vacuum_sqlstmt;
+ sqlstmtStats.n_analyze_sqlstmt += msg->n_analyze_sqlstmt;
+ sqlstmtStats.n_refresh_sqlstmt += msg->n_refresh_sqlstmt;
+ sqlstmtStats.n_lock_sqlstmt += msg->n_lock_sqlstmt;
+ sqlstmtStats.n_checkpoint_sqlstmt += msg->n_checkpoint_sqlstmt;
+ sqlstmtStats.n_reindex_sqlstmt += msg->n_reindex_sqlstmt;
+ sqlstmtStats.n_deallocate_sqlstmt += msg->n_deallocate_sqlstmt;
+ sqlstmtStats.n_others_sqlstmt += msg->n_others_sqlstmt;
+}
+
+/* ----------
* pgstat_recv_recoveryconflict() -
*
* Process a RECOVERYCONFLICT message.
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 98ccbbb..620a6c9 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -41,6 +41,7 @@
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "commands/async.h"
+#include "commands/defrem.h"
#include "commands/prepare.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
@@ -188,7 +189,7 @@ static bool IsTransactionStmtList(List *parseTrees);
static void drop_unnamed_stmt(void);
static void SigHupHandler(SIGNAL_ARGS);
static void log_disconnections(int code, Datum arg);
-
+static void CountSQLStmtExecutions(Node *parsetree);
/* ----------------------------------------------------------------
* routines to obtain user input
@@ -1102,6 +1103,12 @@ exec_simple_query(const char *query_string)
PortalDrop(portal, false);
+ /*
+ * Count SQL statement for pg_stat_sql view
+ */
+ if (pgstat_track_sql)
+ CountSQLStmtExecutions(parsetree);
+
if (IsA(parsetree, TransactionStmt))
{
/*
@@ -1983,6 +1990,29 @@ exec_execute_message(const char *portal_name, long max_rows)
(*receiver->rDestroy) (receiver);
+ /*
+ * Count SQL Statement for pgx_stat_sql
+ */
+ if (pgstat_track_sql)
+ {
+ CachedPlanSource *psrc = NULL;
+
+ if (portal->prepStmtName)
+ {
+ PreparedStatement *pstmt;
+
+ pstmt = FetchPreparedStatement(portal->prepStmtName, false);
+ if (pstmt)
+ psrc = pstmt->plansource;
+ }
+ else
+ psrc = unnamed_stmt_psrc;
+
+ if (psrc && !execute_is_fetch) /* psrc should not be NULL here, just
+ * for paranoia */
+ CountSQLStmtExecutions(psrc->raw_parse_tree);
+ }
+
if (completed)
{
if (is_xact_command)
@@ -4506,3 +4536,319 @@ log_disconnections(int code, Datum arg)
port->user_name, port->database_name, port->remote_host,
port->remote_port[0] ? " port=" : "", port->remote_port)));
}
+
+
+/*
+ * Count SQL statement for pg_stat_sql view
+ */
+static void
+CountSQLStmtExecutions(Node *parsetree)
+{
+ /*
+ * If pgstat_track_sql is true, parsetree should not be NULL. For safer,
+ * Check NULL here.
+ */
+ if (!parsetree)
+ return;
+
+ switch (nodeTag(parsetree))
+ {
+ /* raw plannable queries */
+ case T_InsertStmt:
+ SqlstmtStatsLocal.n_insert_sqlstmt++;
+ break;
+
+ case T_DeleteStmt:
+ SqlstmtStatsLocal.n_delete_sqlstmt++;
+ break;
+
+ case T_UpdateStmt:
+ SqlstmtStatsLocal.n_update_sqlstmt++;
+ break;
+
+ case T_SelectStmt:
+ SqlstmtStatsLocal.n_select_sqlstmt++;
+ break;
+
+ case T_DeclareCursorStmt:
+ SqlstmtStatsLocal.n_declare_cursor_sqlstmt++;
+ break;
+
+ case T_ClosePortalStmt:
+ SqlstmtStatsLocal.n_close_sqlstmt++;
+ break;
+
+ case T_CreateDomainStmt:
+ case T_CreateSchemaStmt:
+ case T_CreateStmt:
+ case T_CreateTableSpaceStmt:
+ case T_CreateExtensionStmt:
+ case T_CreateFdwStmt:
+ case T_CreateForeignServerStmt:
+ case T_CreateUserMappingStmt:
+ case T_CreateForeignTableStmt:
+ case T_DefineStmt:
+ case T_CompositeTypeStmt:
+ case T_CreateEnumStmt:
+ case T_CreateRangeStmt:
+ case T_ViewStmt:
+ case T_CreateFunctionStmt:
+ case T_IndexStmt:
+ case T_RuleStmt:
+ case T_CreateSeqStmt:
+ case T_CreatedbStmt:
+ case T_CreateTransformStmt:
+ case T_CreateTrigStmt:
+ case T_CreateEventTrigStmt:
+ case T_CreatePLangStmt:
+ case T_CreateRoleStmt:
+ case T_CreateConversionStmt:
+ case T_CreateCastStmt:
+ case T_CreateOpClassStmt:
+ case T_CreateOpFamilyStmt:
+ case T_CreatePolicyStmt:
+ case T_CreateAmStmt:
+ SqlstmtStatsLocal.n_create_sqlstmt++;
+ break;
+
+ case T_DropTableSpaceStmt:
+ case T_DropUserMappingStmt:
+ case T_DropStmt:
+ case T_DropdbStmt:
+ case T_DropRoleStmt:
+ case T_DropOwnedStmt:
+ SqlstmtStatsLocal.n_drop_sqlstmt++;
+ break;
+
+ case T_AlterTableSpaceOptionsStmt:
+ case T_AlterExtensionStmt:
+ case T_AlterExtensionContentsStmt:
+ case T_AlterFdwStmt:
+ case T_AlterForeignServerStmt:
+ case T_AlterUserMappingStmt:
+ case T_RenameStmt:
+ case T_AlterObjectDependsStmt:
+ case T_AlterObjectSchemaStmt:
+ case T_AlterOwnerStmt:
+ case T_AlterTableMoveAllStmt:
+ case T_AlterTableStmt:
+ case T_AlterDomainStmt:
+ case T_AlterFunctionStmt:
+ case T_AlterDefaultPrivilegesStmt:
+ case T_AlterEnumStmt:
+ case T_AlterSeqStmt:
+ case T_AlterDatabaseStmt:
+ case T_AlterDatabaseSetStmt:
+ case T_AlterSystemStmt:
+ case T_AlterEventTrigStmt:
+ case T_AlterRoleStmt:
+ case T_AlterRoleSetStmt:
+ case T_AlterOpFamilyStmt:
+ case T_AlterOperatorStmt:
+ case T_AlterTSDictionaryStmt:
+ case T_AlterTSConfigurationStmt:
+ case T_AlterPolicyStmt:
+ SqlstmtStatsLocal.n_alter_sqlstmt++;
+ break;
+
+ case T_ImportForeignSchemaStmt:
+ SqlstmtStatsLocal.n_import_sqlstmt++;
+ break;
+
+ case T_TruncateStmt:
+ SqlstmtStatsLocal.n_truncate_sqlstmt++;
+ break;
+
+ case T_CopyStmt:
+ SqlstmtStatsLocal.n_copy_sqlstmt++;
+ break;
+
+ case T_GrantStmt:
+ {
+ GrantStmt *stmt = (GrantStmt *) parsetree;
+
+ if (stmt->is_grant)
+ SqlstmtStatsLocal.n_grant_sqlstmt++;
+ else
+ SqlstmtStatsLocal.n_revoke_sqlstmt++;
+ }
+ break;
+
+ case T_GrantRoleStmt:
+ {
+ GrantRoleStmt *stmt = (GrantRoleStmt *) parsetree;
+
+ if (stmt->is_grant)
+ SqlstmtStatsLocal.n_grant_sqlstmt++;
+ else
+ SqlstmtStatsLocal.n_revoke_sqlstmt++;
+ }
+ break;
+
+ case T_ClusterStmt:
+ SqlstmtStatsLocal.n_cluster_sqlstmt++;
+ break;
+
+ case T_VacuumStmt:
+ if (((VacuumStmt *) parsetree)->options & VACOPT_VACUUM)
+ SqlstmtStatsLocal.n_vacuum_sqlstmt++;
+ else
+ SqlstmtStatsLocal.n_analyze_sqlstmt++;
+ break;
+
+ case T_ExplainStmt:
+ {
+ ExplainStmt *stmt = (ExplainStmt *) parsetree;
+ bool analyze = false;
+ ListCell *lc;
+
+ /* Look through an EXPLAIN ANALYZE to the contained stmt */
+ foreach(lc, stmt->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(lc);
+
+ if (strcmp(opt->defname, "analyze") == 0)
+ {
+ analyze = defGetBoolean(opt);
+ break;
+ }
+ }
+ if (analyze)
+ CountSQLStmtExecutions(stmt->query);
+ }
+ break;
+
+ case T_CreateTableAsStmt:
+ switch (((CreateTableAsStmt *) parsetree)->relkind)
+ {
+ case OBJECT_TABLE:
+ if (((CreateTableAsStmt *) parsetree)->is_select_into)
+ SqlstmtStatsLocal.n_select_sqlstmt++;
+ else
+ SqlstmtStatsLocal.n_create_sqlstmt++;
+ break;
+ case OBJECT_MATVIEW:
+ SqlstmtStatsLocal.n_create_sqlstmt++;
+ break;
+ default:
+ SqlstmtStatsLocal.n_others_sqlstmt++;
+ }
+ break;
+
+ case T_RefreshMatViewStmt:
+ SqlstmtStatsLocal.n_refresh_sqlstmt++;
+ break;
+
+ case T_LockStmt:
+ SqlstmtStatsLocal.n_lock_sqlstmt++;
+ break;
+
+ case T_CheckPointStmt:
+ SqlstmtStatsLocal.n_checkpoint_sqlstmt++;
+ break;
+
+ case T_ReindexStmt:
+ SqlstmtStatsLocal.n_reindex_sqlstmt++;
+ break;
+
+ case T_ExecuteStmt:
+ {
+ ExecuteStmt *stmt = (ExecuteStmt *) parsetree;
+ PreparedStatement *entry;
+
+ /* not our business to raise error */
+ entry = FetchPreparedStatement(stmt->name, false);
+ if (!entry || !entry->plansource)
+ break;
+ CountSQLStmtExecutions(entry->plansource->raw_parse_tree);
+ }
+ break;
+
+ case T_DeallocateStmt:
+ SqlstmtStatsLocal.n_deallocate_sqlstmt++;
+ break;
+
+ /* already-planned queries */
+ case T_PlannedStmt:
+ {
+ PlannedStmt *stmt = (PlannedStmt *) parsetree;
+
+ switch (stmt->commandType)
+ {
+ case CMD_SELECT:
+
+ /*
+ * We take a little extra care here so that the result
+ * will be useful for complaints about read-only
+ * statements
+ */
+ if (stmt->utilityStmt != NULL)
+ {
+ Assert(IsA(stmt->utilityStmt, DeclareCursorStmt));
+ SqlstmtStatsLocal.n_declare_cursor_sqlstmt++;
+ }
+ else
+ SqlstmtStatsLocal.n_select_sqlstmt++;
+ break;
+ case CMD_UPDATE:
+ SqlstmtStatsLocal.n_update_sqlstmt++;
+ break;
+ case CMD_INSERT:
+ SqlstmtStatsLocal.n_insert_sqlstmt++;
+ break;
+ case CMD_DELETE:
+ SqlstmtStatsLocal.n_delete_sqlstmt++;
+ break;
+ default:
+ SqlstmtStatsLocal.n_others_sqlstmt++;
+ break;
+ }
+ }
+ break;
+
+ /* parsed-and-rewritten-but-not-planned queries */
+ case T_Query:
+ {
+ Query *stmt = (Query *) parsetree;
+
+ switch (stmt->commandType)
+ {
+ case CMD_SELECT:
+
+ /*
+ * We take a little extra care here so that the result
+ * will be useful for complaints about read-only
+ * statements
+ */
+ if (stmt->utilityStmt != NULL)
+ {
+ Assert(IsA(stmt->utilityStmt, DeclareCursorStmt));
+ SqlstmtStatsLocal.n_declare_cursor_sqlstmt++;
+ }
+ else
+ SqlstmtStatsLocal.n_select_sqlstmt++;
+ break;
+ case CMD_UPDATE:
+ SqlstmtStatsLocal.n_revoke_sqlstmt++;
+ break;
+ case CMD_INSERT:
+ SqlstmtStatsLocal.n_revoke_sqlstmt++;
+ break;
+ case CMD_DELETE:
+ SqlstmtStatsLocal.n_revoke_sqlstmt++;
+ break;
+ case CMD_UTILITY:
+ CountSQLStmtExecutions(stmt->utilityStmt);
+ break;
+ default:
+ SqlstmtStatsLocal.n_others_sqlstmt++;
+ break;
+ }
+ }
+ break;
+
+ default:
+ SqlstmtStatsLocal.n_others_sqlstmt++;
+ break;
+ }
+}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 2d3cf9e..bf68b33 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -129,6 +129,31 @@ extern Datum pg_stat_reset_shared(PG_FUNCTION_ARGS);
extern Datum pg_stat_reset_single_table_counters(PG_FUNCTION_ARGS);
extern Datum pg_stat_reset_single_function_counters(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_insert_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_delete_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_update_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_select_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_declare_cursor_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_close_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_create_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_drop_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_alter_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_import_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_truncate_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_copy_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_grant_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_revoke_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_cluster_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_vacuum_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_analyze_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_refresh_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_lock_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_checkpoint_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_reindex_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_deallocate_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_others_sqlstmt(PG_FUNCTION_ARGS);
+extern Datum pgx_stat_get_reset_time_sqlstmt(PG_FUNCTION_ARGS);
+
/* Global bgwriter statistics, from bgwriter.c */
extern PgStat_MsgBgWriter bgwriterStats;
@@ -1931,3 +1956,147 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(HeapTupleGetDatum(
heap_form_tuple(tupdesc, values, nulls)));
}
+
+Datum
+pg_stat_get_insert_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_insert_sqlstmt);
+}
+
+Datum
+pg_stat_get_delete_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_delete_sqlstmt);
+}
+
+Datum
+pg_stat_get_update_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_update_sqlstmt);
+}
+
+Datum
+pg_stat_get_select_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_select_sqlstmt);
+}
+
+Datum
+pg_stat_get_declare_cursor_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_declare_cursor_sqlstmt);
+}
+
+Datum
+pg_stat_get_close_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_close_sqlstmt);
+}
+
+Datum
+pg_stat_get_create_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_create_sqlstmt);
+}
+
+Datum
+pg_stat_get_drop_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_drop_sqlstmt);
+}
+
+Datum
+pg_stat_get_alter_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_alter_sqlstmt);
+}
+
+Datum
+pg_stat_get_import_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_import_sqlstmt);
+}
+
+Datum
+pg_stat_get_truncate_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_truncate_sqlstmt);
+}
+
+Datum
+pg_stat_get_copy_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_copy_sqlstmt);
+}
+
+Datum
+pg_stat_get_grant_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_grant_sqlstmt);
+}
+
+Datum
+pg_stat_get_revoke_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_revoke_sqlstmt);
+}
+
+Datum
+pg_stat_get_cluster_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_cluster_sqlstmt);
+}
+
+Datum
+pg_stat_get_vacuum_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_vacuum_sqlstmt);
+}
+
+Datum
+pg_stat_get_analyze_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_analyze_sqlstmt);
+}
+
+Datum
+pg_stat_get_refresh_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_refresh_sqlstmt);
+}
+
+Datum
+pg_stat_get_lock_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_lock_sqlstmt);
+}
+
+Datum
+pg_stat_get_checkpoint_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_checkpoint_sqlstmt);
+}
+
+Datum
+pg_stat_get_reindex_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_reindex_sqlstmt);
+}
+
+Datum
+pg_stat_get_deallocate_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_deallocate_sqlstmt);
+}
+
+Datum
+pg_stat_get_others_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64(pgstat_fetch_sqlstmt()->n_others_sqlstmt);
+}
+
+Datum
+pgx_stat_get_reset_time_sqlstmt(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_TIMESTAMPTZ(pgstat_fetch_sqlstmt()->stat_reset_timestamp);
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index ce4eef9..154276b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1205,6 +1205,16 @@ static struct config_bool ConfigureNamesBool[] =
},
{
+ {"track_sql", PGC_SUSET, STATS_COLLECTOR,
+ gettext_noop("Collects timing statistics for database I/O activity."),
+ NULL
+ },
+ &pgstat_track_sql,
+ false,
+ NULL, NULL, NULL
+ },
+
+ {
{"update_process_title", PGC_SUSET, PROCESS_TITLE,
gettext_noop("Updates the process title to show the active SQL command."),
gettext_noop("Enables updating of the process title every time a new SQL command is received by the server.")
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..ea77476 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2896,6 +2896,57 @@ DESCR("statistics: total execution time of function in current transaction, in m
DATA(insert OID = 3048 ( pg_stat_get_xact_function_self_time PGNSP PGUID 12 1 0 0 0 f f f f t f v r 1 0 701 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_xact_function_self_time _null_ _null_ _null_ ));
DESCR("statistics: self execution time of function in current transaction, in msec");
+
+DATA(insert OID = 3401 ( pg_stat_get_insert_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_insert_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of insert sql statement executions");
+DATA(insert OID = 3402 ( pg_stat_get_delete_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_delete_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of delete sql statement executions");
+DATA(insert OID = 3403 ( pg_stat_get_update_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_update_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of update sql statement executions");
+DATA(insert OID = 3404 ( pg_stat_get_select_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_select_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of select sql statement executions");
+DATA(insert OID = 3405 ( pg_stat_get_declare_cursor_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_declare_cursor_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of declare cursor sql statement executions");
+DATA(insert OID = 3406 ( pg_stat_get_close_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_close_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of close sql statement executions");
+DATA(insert OID = 3407 ( pg_stat_get_create_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_create_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of create sql statement executions");
+DATA(insert OID = 3408 ( pg_stat_get_drop_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_drop_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of drop sql statement executions");
+DATA(insert OID = 3409 ( pg_stat_get_alter_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_alter_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of alter sql statement executions");
+DATA(insert OID = 3410 ( pg_stat_get_import_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_import_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of import sql statement executions");
+DATA(insert OID = 3411 ( pg_stat_get_truncate_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_truncate_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of truncate sql statement executions");
+DATA(insert OID = 3412 ( pg_stat_get_copy_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_copy_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of copy sql statement executions");
+DATA(insert OID = 3413 ( pg_stat_get_grant_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_grant_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of grant sql statement executions");
+DATA(insert OID = 3414 ( pg_stat_get_revoke_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_revoke_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of revoke sql statement executions");
+DATA(insert OID = 3415 ( pg_stat_get_cluster_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_cluster_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of cluster sql statement executions");
+DATA(insert OID = 3416 ( pg_stat_get_vacuum_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_vacuum_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of vacuum sql statement executions");
+DATA(insert OID = 3417 ( pg_stat_get_analyze_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_analyze_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of analyze sql statement executions");
+DATA(insert OID = 3418 ( pg_stat_get_refresh_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_refresh_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of refresh sql statement executions");
+DATA(insert OID = 3419 ( pg_stat_get_lock_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_lock_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of lock sql statement executions");
+DATA(insert OID = 3420 ( pg_stat_get_checkpoint_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_checkpoint_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of checkpoint sql statement executions");
+DATA(insert OID = 3421 ( pg_stat_get_reindex_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_reindex_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of reindex sql statement executions");
+DATA(insert OID = 3422 ( pg_stat_get_deallocate_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_deallocate_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of deallocate sql statement executions");
+DATA(insert OID = 3423 ( pg_stat_get_others_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 20 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_others_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of other sql statement executions");
+DATA(insert OID = 3424 ( pgx_stat_get_reset_time_sqlstmt PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 1184 "" _null_ _null_ _null_ _null_ _null_ pgx_stat_get_reset_time_sqlstmt _null_ _null_ _null_ ));
+DESCR("statistics: number of other sql statement executions");
+
+
DATA(insert OID = 3788 ( pg_stat_get_snapshot_timestamp PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 1184 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_snapshot_timestamp _null_ _null_ _null_ ));
DESCR("statistics: timestamp of the current statistics snapshot");
DATA(insert OID = 2230 ( pg_stat_clear_snapshot PGNSP PGUID 12 1 0 0 0 f f f f f f v r 0 0 2278 "" _null_ _null_ _null_ _null_ _null_ pg_stat_clear_snapshot _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index dc3320d..44c6127 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -64,7 +64,8 @@ typedef enum StatMsgType
PGSTAT_MTYPE_FUNCPURGE,
PGSTAT_MTYPE_RECOVERYCONFLICT,
PGSTAT_MTYPE_TEMPFILE,
- PGSTAT_MTYPE_DEADLOCK
+ PGSTAT_MTYPE_DEADLOCK,
+ PGSTAT_MTYPE_SQLSTMT
} StatMsgType;
/* ----------
@@ -119,7 +120,8 @@ typedef struct PgStat_TableCounts
typedef enum PgStat_Shared_Reset_Target
{
RESET_ARCHIVER,
- RESET_BGWRITER
+ RESET_BGWRITER,
+ RESET_SQLSTMT
} PgStat_Shared_Reset_Target;
/* Possible object types for resetting single counters */
@@ -530,6 +532,38 @@ typedef struct PgStat_MsgDeadlock
Oid m_databaseid;
} PgStat_MsgDeadlock;
+/* ----------
+ * PgStat_MsgSqlstmt Sent by the sql to update statistics.
+ * ----------
+ */
+typedef struct PgStat_MsgSqlstmt
+{
+ PgStat_MsgHdr m_hdr;
+
+ PgStat_Counter n_insert_sqlstmt;
+ PgStat_Counter n_delete_sqlstmt;
+ PgStat_Counter n_update_sqlstmt;
+ PgStat_Counter n_select_sqlstmt;
+ PgStat_Counter n_declare_cursor_sqlstmt;
+ PgStat_Counter n_close_sqlstmt;
+ PgStat_Counter n_create_sqlstmt;
+ PgStat_Counter n_drop_sqlstmt;
+ PgStat_Counter n_alter_sqlstmt;
+ PgStat_Counter n_import_sqlstmt;
+ PgStat_Counter n_truncate_sqlstmt;
+ PgStat_Counter n_copy_sqlstmt;
+ PgStat_Counter n_grant_sqlstmt;
+ PgStat_Counter n_revoke_sqlstmt;
+ PgStat_Counter n_cluster_sqlstmt;
+ PgStat_Counter n_vacuum_sqlstmt;
+ PgStat_Counter n_analyze_sqlstmt;
+ PgStat_Counter n_refresh_sqlstmt;
+ PgStat_Counter n_lock_sqlstmt;
+ PgStat_Counter n_checkpoint_sqlstmt;
+ PgStat_Counter n_reindex_sqlstmt;
+ PgStat_Counter n_deallocate_sqlstmt;
+ PgStat_Counter n_others_sqlstmt;
+} PgStat_MsgSqlstmt;
/* ----------
* PgStat_Msg Union over all possible messages.
@@ -555,6 +589,7 @@ typedef union PgStat_Msg
PgStat_MsgFuncpurge msg_funcpurge;
PgStat_MsgRecoveryConflict msg_recoveryconflict;
PgStat_MsgDeadlock msg_deadlock;
+ PgStat_MsgSqlstmt msg_sqlstmt;
} PgStat_Msg;
@@ -566,7 +601,7 @@ typedef union PgStat_Msg
* ------------------------------------------------------------
*/
-#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9D
+#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9E
/* ----------
* PgStat_StatDBEntry The collector's data per database
@@ -675,6 +710,34 @@ typedef struct PgStat_ArchiverStats
TimestampTz stat_reset_timestamp;
} PgStat_ArchiverStats;
+typedef struct PgStat_SqlStmtStats
+{
+ PgStat_Counter n_insert_sqlstmt;
+ PgStat_Counter n_delete_sqlstmt;
+ PgStat_Counter n_update_sqlstmt;
+ PgStat_Counter n_select_sqlstmt;
+ PgStat_Counter n_declare_cursor_sqlstmt;
+ PgStat_Counter n_close_sqlstmt;
+ PgStat_Counter n_create_sqlstmt;
+ PgStat_Counter n_drop_sqlstmt;
+ PgStat_Counter n_alter_sqlstmt;
+ PgStat_Counter n_import_sqlstmt;
+ PgStat_Counter n_truncate_sqlstmt;
+ PgStat_Counter n_copy_sqlstmt;
+ PgStat_Counter n_grant_sqlstmt;
+ PgStat_Counter n_revoke_sqlstmt;
+ PgStat_Counter n_cluster_sqlstmt;
+ PgStat_Counter n_vacuum_sqlstmt;
+ PgStat_Counter n_analyze_sqlstmt;
+ PgStat_Counter n_refresh_sqlstmt;
+ PgStat_Counter n_lock_sqlstmt;
+ PgStat_Counter n_checkpoint_sqlstmt;
+ PgStat_Counter n_reindex_sqlstmt;
+ PgStat_Counter n_deallocate_sqlstmt;
+ PgStat_Counter n_others_sqlstmt;
+ TimestampTz stat_reset_timestamp;
+} PgStat_SqlStmtStats;
+
/*
* Global statistics kept in the stats collector
*/
@@ -918,6 +981,7 @@ typedef struct PgStat_FunctionCallUsage
*/
extern bool pgstat_track_activities;
extern bool pgstat_track_counts;
+extern bool pgstat_track_sql;
extern int pgstat_track_functions;
extern PGDLLIMPORT int pgstat_track_activity_query_size;
extern char *pgstat_stat_directory;
@@ -930,6 +994,11 @@ extern char *pgstat_stat_filename;
extern PgStat_MsgBgWriter BgWriterStats;
/*
+ * SQL statistics counter updated by each backend
+ */
+extern PgStat_MsgSqlstmt SqlstmtStatsLocal;
+
+/*
* Updated by pgstat_count_buffer_*_time macros
*/
extern PgStat_Counter pgStatBlockReadTime;
@@ -1141,5 +1210,6 @@ extern PgStat_StatFuncEntry *pgstat_fetch_stat_funcentry(Oid funcid);
extern int pgstat_fetch_stat_numbackends(void);
extern PgStat_ArchiverStats *pgstat_fetch_stat_archiver(void);
extern PgStat_GlobalStats *pgstat_fetch_global(void);
+extern PgStat_SqlStmtStats *pgstat_fetch_sqlstmt(void);
#endif /* PGSTAT_H */
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 00700f2..b3c728d 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1795,6 +1795,30 @@ pg_stat_replication| SELECT s.pid,
FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, ssl, sslversion, sslcipher, sslbits, sslcompression, sslclientdn)
JOIN pg_stat_get_wal_senders() w(pid, state, sent_location, write_location, flush_location, replay_location, sync_priority, sync_state) ON ((s.pid = w.pid)))
LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
+pg_stat_sql| SELECT pg_stat_get_insert_sqlstmt() AS insert,
+ pg_stat_get_delete_sqlstmt() AS delete,
+ pg_stat_get_update_sqlstmt() AS update,
+ pg_stat_get_select_sqlstmt() AS "select",
+ pg_stat_get_declare_cursor_sqlstmt() AS declare_cursor,
+ pg_stat_get_close_sqlstmt() AS close,
+ pg_stat_get_create_sqlstmt() AS "create",
+ pg_stat_get_drop_sqlstmt() AS drop,
+ pg_stat_get_alter_sqlstmt() AS alter,
+ pg_stat_get_import_sqlstmt() AS import,
+ pg_stat_get_truncate_sqlstmt() AS truncate,
+ pg_stat_get_copy_sqlstmt() AS copy,
+ pg_stat_get_grant_sqlstmt() AS "grant",
+ pg_stat_get_revoke_sqlstmt() AS revoke,
+ pg_stat_get_cluster_sqlstmt() AS cluster,
+ pg_stat_get_vacuum_sqlstmt() AS vacuum,
+ pg_stat_get_analyze_sqlstmt() AS "analyze",
+ pg_stat_get_refresh_sqlstmt() AS refresh,
+ pg_stat_get_lock_sqlstmt() AS lock,
+ pg_stat_get_checkpoint_sqlstmt() AS checkpoint,
+ pg_stat_get_reindex_sqlstmt() AS reindex,
+ pg_stat_get_deallocate_sqlstmt() AS deallocate,
+ pg_stat_get_others_sqlstmt() AS others,
+ pgx_stat_get_reset_time_sqlstmt() AS stats_reset;
pg_stat_ssl| SELECT s.pid,
s.ssl,
s.sslversion AS version,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index a811265..3e41de4 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -4,6 +4,55 @@
-- Must be run after tenk2 has been created (by create_table),
-- populated (by create_misc) and indexed (by create_index).
--
+-- pg_stat_sql
+SHOW track_sql; -- must be off
+ track_sql
+-----------
+ off
+(1 row)
+
+SELECT pg_stat_sql.select, pg_stat_sql.others FROM pg_stat_sql;
+ select | others
+--------+--------
+ 0 | 0
+(1 row)
+
+SET track_sql TO ON;
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT pg_stat_sql.select, pg_stat_sql.others FROM pg_stat_sql;
+ select | others
+--------+--------
+ 1 | 1
+(1 row)
+
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT pg_stat_sql.select, pg_stat_sql.others FROM pg_stat_sql;
+ select | others
+--------+--------
+ 0 | 0
+(1 row)
+
-- conditio sine qua non
SHOW track_counts; -- must be on
track_counts
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index b3e2efa..88e1d5e 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -5,6 +5,25 @@
-- populated (by create_misc) and indexed (by create_index).
--
+-- pg_stat_sql
+SHOW track_sql; -- must be off
+SELECT pg_stat_sql.select, pg_stat_sql.others FROM pg_stat_sql;
+
+SET track_sql TO ON;
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+SELECT pg_stat_sql.select, pg_stat_sql.others FROM pg_stat_sql;
+
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+SELECT pg_stat_sql.select, pg_stat_sql.others FROM pg_stat_sql;
+
-- conditio sine qua non
SHOW track_counts; -- must be on
On 9/14/16 4:01 PM, Robert Haas wrote:
I think it is not a good idea to make the command names used here the
plural forms of the command tags. Instead of "inserts", "updates",
"imports", etc. just use "INSERT", "UPDATE", "IMPORT". That's simpler
and less error prone - e.g. you won't end up with things like
"refreshs", which is not a word.
How about having the tag not be a column name but a row entry. So you'd
do something like
SELECT * FROM pg_stat_sql WHERE tag = 'ALTER VIEW';
That way, we don't have to keep updating (and re-debating) this when new
command types or subtypes are added. And queries written for future
versions will not fail when run against old servers.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Sep 21, 2016 at 11:25:14AM -0400, Peter Eisentraut wrote:
On 9/14/16 4:01 PM, Robert Haas wrote:
I think it is not a good idea to make the command names used here the
plural forms of the command tags. Instead of "inserts", "updates",
"imports", etc. just use "INSERT", "UPDATE", "IMPORT". That's simpler
and less error prone - e.g. you won't end up with things like
"refreshs", which is not a word.How about having the tag not be a column name but a row entry. So you'd
do something likeSELECT * FROM pg_stat_sql WHERE tag = 'ALTER VIEW';
+1 for this. It's MUCH easier to deal with changes in row counts than
changes in row type.
Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david(dot)fetter(at)gmail(dot)com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Peter Eisentraut wrote:
How about having the tag not be a column name but a row entry. So you'd
do something likeSELECT * FROM pg_stat_sql WHERE tag = 'ALTER VIEW';
That way, we don't have to keep updating (and re-debating) this when new
command types or subtypes are added. And queries written for future
versions will not fail when run against old servers.
Yeah, good idea.
Let's also discuss the interface from the stats collector. Currently we
have some 20 new SQL functions, all alike, each loading the whole data
and returning a single counter, and then the view invokes each function
separately. That doesn't seem great to me. How about having a single C
function that returns the whole thing as a SRF instead, and the view is
just a single function invocation -- something like pg_lock_status
filling pg_locks in one go.
Another consideration is that the present patch lumps together all ALTER
cases in a single counter. This isn't great, but at the same time we
don't want to bloat the stat files by having hundreds of counters per
database, do we?
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Sep 21, 2016 at 02:05:24PM -0300, Alvaro Herrera wrote:
Another consideration is that the present patch lumps together all
ALTER cases in a single counter. This isn't great, but at the same
time we don't want to bloat the stat files by having hundreds of
counters per database, do we?
I count 37 documented versions of ALTER as of git master. Is there
some multiplier I'm missing?
Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david(dot)fetter(at)gmail(dot)com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Sep 22, 2016 at 3:05 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
Peter Eisentraut wrote:
How about having the tag not be a column name but a row entry. So you'd
do something likeSELECT * FROM pg_stat_sql WHERE tag = 'ALTER VIEW';
That way, we don't have to keep updating (and re-debating) this when new
command types or subtypes are added. And queries written for future
versions will not fail when run against old servers.Yeah, good idea.
Yes, Having it as a row entry is good.
Let's also discuss the interface from the stats collector. Currently we
have some 20 new SQL functions, all alike, each loading the whole data
and returning a single counter, and then the view invokes each function
separately. That doesn't seem great to me. How about having a single C
function that returns the whole thing as a SRF instead, and the view is
just a single function invocation -- something like pg_lock_status
filling pg_locks in one go.Another consideration is that the present patch lumps together all ALTER
cases in a single counter. This isn't great, but at the same time we
don't want to bloat the stat files by having hundreds of counters per
database, do we?
Currently, The SQL stats is a fixed size counter to track the all the ALTER
cases as single counter. So while sending the stats from the backend to
stats collector at the end of the transaction, the cost is same, because of
it's fixed size. This approach adds overhead to send and read the stats
is minimal.
With the following approach, I feel it is possible to support the counter at
command tag level.
Add a Global and local Hash to keep track of the counters by using the
command tag as the key, this hash table increases dynamically whenever
a new type of SQL command gets executed. The Local Hash data is passed
to stats collector whenever the transaction gets committed.
The problem I am thinking is that, Sending data from Hash and populating
the Hash from stats file for all the command tags adds some overhead.
Regards,
Hari Babu
Fujitsu Australia
On Thu, Sep 29, 2016 at 3:45 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:
On Thu, Sep 22, 2016 at 3:05 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:Peter Eisentraut wrote:
How about having the tag not be a column name but a row entry. So you'd
do something likeSELECT * FROM pg_stat_sql WHERE tag = 'ALTER VIEW';
That way, we don't have to keep updating (and re-debating) this when new
command types or subtypes are added. And queries written for future
versions will not fail when run against old servers.Yeah, good idea.
Yes, Having it as a row entry is good.
Let's also discuss the interface from the stats collector. Currently we
have some 20 new SQL functions, all alike, each loading the whole data
and returning a single counter, and then the view invokes each function
separately. That doesn't seem great to me. How about having a single C
function that returns the whole thing as a SRF instead, and the view is
just a single function invocation -- something like pg_lock_status
filling pg_locks in one go.Another consideration is that the present patch lumps together all ALTER
cases in a single counter. This isn't great, but at the same time we
don't want to bloat the stat files by having hundreds of counters per
database, do we?Currently, The SQL stats is a fixed size counter to track the all the
ALTER
cases as single counter. So while sending the stats from the backend to
stats collector at the end of the transaction, the cost is same, because of
it's fixed size. This approach adds overhead to send and read the stats
is minimal.With the following approach, I feel it is possible to support the counter
at
command tag level.Add a Global and local Hash to keep track of the counters by using the
command tag as the key, this hash table increases dynamically whenever
a new type of SQL command gets executed. The Local Hash data is passed
to stats collector whenever the transaction gets committed.The problem I am thinking is that, Sending data from Hash and populating
the Hash from stats file for all the command tags adds some overhead.
I tried changing the pg_stat_sql into row mode and ran the regress suite to
add different type of SQL commands to the view and ran the pgbench test
on my laptop to find out any performance impact with this patch.
HEAD PATCH
pgbench - select 828 816
Here I attached the pg_stat_sql patch to keep track of all SQL commands
based on the commandTag and their counts. I attached the result of this
view that is run on the database after "make installcheck" just for
reference.
Regards,
Hari Babu
Fujitsu Australia
Attachments:
pg_stat_sql_row_mode_1.patchapplication/octet-stream; name=pg_stat_sql_row_mode_1.patchDownload
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e826c19..a55327c 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5558,6 +5558,21 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
</listitem>
</varlistentry>
+ <varlistentry id="guc-track-sql" xreflabel="track_sql">
+ <term><varname>track_sql</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>track_sql</> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Enables collection of different SQL statement statistics that are
+ executed on the instance. This parameter is off by default. Only
+ superusers can change this setting.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-stats-temp-directory" xreflabel="stats_temp_directory">
<term><varname>stats_temp_directory</varname> (<type>string</type>)
<indexterm>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3de489e..26b4bab 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -513,6 +513,13 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
<command>VACUUM</>, showing current progress.
See <xref linkend='vacuum-progress-reporting'>.</entry>
</row>
+
+ <row>
+ <entry><structname>pg_stat_sql</><indexterm><primary>pg_stat_sql</primary></indexterm></entry>
+ <entry>Shows statistics about the SQL statements that are
+ executed on the instance.
+ See <xref linkend="pg-stat-sql-view"> for details.</entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -2391,6 +2398,40 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
controls exactly which functions are tracked.
</para>
+ <table id="pg-stat-sql-view" xreflabel="pg_stat_sql">
+ <title><structname>pg_stat_sql</structname> View</title>
+ <tgroup cols="3">
+ <thead>
+ <row>
+ <entry>Column</entry>
+ <entry>Type</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry><structfield>tag</></entry>
+ <entry><type>text</></entry>
+ <entry>Name of the SQL statement</entry>
+ </row>
+ <row>
+ <entry><structfield>count</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of times the SQL statement is executed</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The <structname>pg_stat_sql</structname> view will contain statistics
+ about number of SQL statements that are executed on the instance.
+ The <xref linkend="guc-track-sql"> parameter controls the SQL statement
+ execution statistics.
+ </para>
+
+
</sect2>
<sect2 id="monitoring-stats-functions">
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ada2142..0a177b4 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -842,6 +842,11 @@ CREATE VIEW pg_replication_origin_status AS
REVOKE ALL ON pg_replication_origin_status FROM public;
+CREATE VIEW pg_stat_sql AS
+ SELECT * FROM pg_stat_sql();
+
+REVOKE EXECUTE ON FUNCTION pg_stat_sql() FROM public;
+
--
-- We have a few function definitions in here, too.
-- At some point there might be enough to justify breaking them out into
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index a9efee8..0649022 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -100,6 +100,7 @@
#define PGSTAT_DB_HASH_SIZE 16
#define PGSTAT_TAB_HASH_SIZE 512
#define PGSTAT_FUNCTION_HASH_SIZE 512
+#define PGSTAT_SQLSTMT_HASH_SIZE 256
/* ----------
@@ -108,6 +109,7 @@
*/
bool pgstat_track_activities = false;
bool pgstat_track_counts = false;
+bool pgstat_track_sql = false;
int pgstat_track_functions = TRACK_FUNC_OFF;
int pgstat_track_activity_query_size = 1024;
@@ -126,6 +128,19 @@ char *pgstat_stat_tmpname = NULL;
*/
PgStat_MsgBgWriter BgWriterStats;
+/*
+ * Backend specific SQL statement information stored in this hash table,
+ * before sent it to the stats collector.
+ */
+static HTAB *pgStatBackendSql = NULL;
+
+/*
+ * Global SQL statement stats information gathered in the collector are stored in
+ * this hash table.
+ */
+HTAB *pgStatSql = NULL;
+
+
/* ----------
* Local data
* ----------
@@ -270,6 +285,7 @@ static bool pgstat_db_requested(Oid databaseid);
static void pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg);
static void pgstat_send_funcstats(void);
+static void pgstat_send_sqlstmt(void);
static HTAB *pgstat_collect_oids(Oid catalogid);
static PgStat_TableStatus *get_tabstat_entry(Oid rel_id, bool isshared);
@@ -301,6 +317,7 @@ static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len);
static void pgstat_recv_recoveryconflict(PgStat_MsgRecoveryConflict *msg, int len);
static void pgstat_recv_deadlock(PgStat_MsgDeadlock *msg, int len);
static void pgstat_recv_tempfile(PgStat_MsgTempFile *msg, int len);
+static void pgstat_recv_sqlstmt(PgStat_MsgSqlstmt * msg, int len);
/* ------------------------------------------------------------
* Public functions called from postmaster follow
@@ -834,6 +851,10 @@ pgstat_report_stat(bool force)
/* Now, send function statistics */
pgstat_send_funcstats();
+
+ /* Now, send sql statistics */
+ if (pgstat_track_sql)
+ pgstat_send_sqlstmt();
}
/*
@@ -1260,11 +1281,13 @@ pgstat_reset_shared_counters(const char *target)
msg.m_resettarget = RESET_ARCHIVER;
else if (strcmp(target, "bgwriter") == 0)
msg.m_resettarget = RESET_BGWRITER;
+ else if (strcmp(target, "sqlstmt") == 0)
+ msg.m_resettarget = RESET_SQLSTMT;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized reset target: \"%s\"", target),
- errhint("Target must be \"archiver\" or \"bgwriter\".")));
+ errhint("Target must be \"archiver\" or \"bgwriter\" or \"sqlstmt\".")));
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSHAREDCOUNTER);
pgstat_send(&msg, sizeof(msg));
@@ -3677,6 +3700,89 @@ pgstat_send_bgwriter(void)
MemSet(&BgWriterStats, 0, sizeof(BgWriterStats));
}
+/* ----------
+ * pgstat_send_sqlstmt(void)
+ *
+ * Send SQL statement statistics to the collector
+ * ----------
+ */
+static void
+pgstat_send_sqlstmt(void)
+{
+ PgStat_MsgSqlstmt msg;
+ PgStatSqlstmtEntry *entry;
+ HASH_SEQ_STATUS hstat;
+
+ if (pgStatBackendSql == NULL)
+ return;
+
+ pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_SQLSTMT);
+ msg.m_nentries = 0;
+
+ hash_seq_init(&hstat, pgStatBackendSql);
+ while ((entry = (PgStatSqlstmtEntry *) hash_seq_search(&hstat)) != NULL)
+ {
+ PgStatSqlstmtEntry *m_ent;
+
+ /* Skip it if no counts accumulated since last time */
+ if (entry->count == 0)
+ continue;
+
+ /* need to convert format of time accumulators */
+ m_ent = &msg.m_entry[msg.m_nentries];
+ memcpy(m_ent, entry, sizeof(PgStatSqlstmtEntry));
+
+ if (++msg.m_nentries >= PGSTAT_NUM_SQLSTMTS)
+ {
+ pgstat_send(&msg, offsetof(PgStat_MsgSqlstmt, m_entry[0]) +
+ msg.m_nentries * sizeof(PgStatSqlstmtEntry));
+ msg.m_nentries = 0;
+ }
+
+ /* reset the entry's count */
+ entry->count = 0;
+ }
+
+ if (msg.m_nentries > 0)
+ pgstat_send(&msg, offsetof(PgStat_MsgSqlstmt, m_entry[0]) +
+ msg.m_nentries * sizeof(PgStatSqlstmtEntry));
+}
+
+/*
+ * Count SQL statement for pg_stat_sql view
+ */
+void
+pgstat_count_sqlstmt(const char *commandTag)
+{
+ PgStatSqlstmtEntry *htabent;
+ bool found;
+
+ if (!pgstat_track_sql)
+ return;
+
+ if (!pgStatBackendSql)
+ {
+ /* First time through - initialize SQL statement stat table */
+ HASHCTL hash_ctl;
+
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = NAMEDATALEN;
+ hash_ctl.entrysize = sizeof(PgStatSqlstmtEntry);
+ pgStatBackendSql = hash_create("SQL statement stat entries",
+ PGSTAT_SQLSTMT_HASH_SIZE,
+ &hash_ctl,
+ HASH_ELEM | HASH_BLOBS);
+ }
+
+ /* Get the stats entry for this SQL statement, create if necessary */
+ htabent = hash_search(pgStatBackendSql, commandTag,
+ HASH_ENTER, &found);
+ if (!found)
+ htabent->count = 1;
+ else
+ htabent->count++;
+}
+
/* ----------
* PgstatCollectorMain() -
@@ -3893,6 +3999,9 @@ PgstatCollectorMain(int argc, char *argv[])
pgstat_recv_tempfile((PgStat_MsgTempFile *) &msg, len);
break;
+ case PGSTAT_MTYPE_SQLSTMT:
+ pgstat_recv_sqlstmt((PgStat_MsgSqlstmt *) & msg, len);
+ break;
default:
break;
}
@@ -4113,6 +4222,7 @@ pgstat_write_statsfiles(bool permanent, bool allDbs)
{
HASH_SEQ_STATUS hstat;
PgStat_StatDBEntry *dbentry;
+ PgStatSqlstmtEntry *sqlstmtentry;
FILE *fpout;
int32 format_id;
const char *tmpfile = permanent ? PGSTAT_STAT_PERMANENT_TMPFILE : pgstat_stat_tmpname;
@@ -4185,6 +4295,15 @@ pgstat_write_statsfiles(bool permanent, bool allDbs)
(void) rc; /* we'll check for error with ferror */
}
+ /* Walk through the SQL statement Stats Hash table. */
+ hash_seq_init(&hstat, pgStatSql);
+ while ((sqlstmtentry = (PgStatSqlstmtEntry *) hash_seq_search(&hstat)) != NULL)
+ {
+ fputc('S', fpout);
+ rc = fwrite(sqlstmtentry, sizeof(PgStatSqlstmtEntry), 1, fpout);
+ (void) rc; /* we'll check for error with ferror */
+ }
+
/*
* No more output to be done. Close the temp file and replace the old
* pgstat.stat with it. The ferror() check replaces testing for error
@@ -4409,6 +4528,14 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep)
dbhash = hash_create("Databases hash", PGSTAT_DB_HASH_SIZE, &hash_ctl,
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+ /* Create SQL statement counter Stats HashTable */
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = NAMEDATALEN;
+ hash_ctl.entrysize = sizeof(PgStatSqlstmtEntry);
+ pgStatSql = hash_create("SQL statement counter",
+ PGSTAT_SQLSTMT_HASH_SIZE, &hash_ctl,
+ HASH_ELEM);
+
/*
* Clear out global and archiver statistics so they start from zero in
* case we can't load an existing statsfile.
@@ -4554,6 +4681,38 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep)
break;
+ case 'S':
+ {
+ PgStatSqlstmtEntry stats;
+ PgStatSqlstmtEntry *lstats;
+
+ if (fread(&stats, 1, sizeof(PgStatSqlstmtEntry), fpin) != sizeof(PgStatSqlstmtEntry))
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"",
+ statfile)));
+ goto done;
+ }
+
+ /*
+ * Add to the SQL statement counter hash
+ */
+ lstats = (PgStatSqlstmtEntry *) hash_search(pgStatSql,
+ (void *) &(stats.tag),
+ HASH_ENTER,
+ &found);
+ if (found)
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"",
+ statfile)));
+ goto done;
+ }
+
+ memcpy(lstats, &stats, sizeof(PgStatSqlstmtEntry));
+
+ break;
+ }
case 'E':
goto done;
@@ -4853,6 +5012,21 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool permanent,
break;
+ case 'S':
+ {
+ PgStatSqlstmtEntry stats;
+
+ if (fread(&stats, 1, sizeof(PgStatSqlstmtEntry), fpin) != sizeof(PgStatSqlstmtEntry))
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"",
+ statfile)));
+ goto done;
+ }
+
+ break;
+ }
+
case 'E':
goto done;
@@ -5378,6 +5552,21 @@ pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter *msg, int len)
memset(&archiverStats, 0, sizeof(archiverStats));
archiverStats.stat_reset_timestamp = GetCurrentTimestamp();
}
+ else if (msg->m_resettarget == RESET_SQLSTMT)
+ {
+ HASHCTL hash_ctl;
+
+ /* Destroy the SQL statement counter stats HashTable */
+ hash_destroy(pgStatSql);
+
+ /* Create SQL statement counter Stats HashTable */
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = NAMEDATALEN;
+ hash_ctl.entrysize = sizeof(PgStatSqlstmtEntry);
+ pgStatSql = hash_create("SQL statement counter",
+ PGSTAT_SQLSTMT_HASH_SIZE, &hash_ctl,
+ HASH_ELEM);
+ }
/*
* Presumably the sender of this message validated the target, don't
@@ -5558,6 +5747,37 @@ pgstat_recv_bgwriter(PgStat_MsgBgWriter *msg, int len)
}
/* ----------
+ * pgstat_recv_sqlstmt() -
+ *
+ * Process a SQL statement statistics message.
+ * ----------
+ */
+static void
+pgstat_recv_sqlstmt(PgStat_MsgSqlstmt * msg, int len)
+{
+ PgStatSqlstmtEntry *entry;
+ bool found;
+ int i;
+
+ for (i = 0; i < msg->m_nentries; i++)
+ {
+ /* Get the stats entry for this SQL statement, create if necessary */
+ entry = (PgStatSqlstmtEntry *) hash_search(pgStatSql, (void *) &(msg->m_entry[i].tag),
+ HASH_ENTER, &found);
+
+ if (found)
+ {
+ entry->count += msg->m_entry[i].count;
+ }
+ else
+ {
+ memcpy(entry->tag, msg->m_entry[i].tag, NAMEDATALEN);
+ entry->count = msg->m_entry[i].count;
+ }
+ }
+}
+
+/* ----------
* pgstat_recv_recoveryconflict() -
*
* Process a RECOVERYCONFLICT message.
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 2347f1b..4a168ca 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1102,6 +1102,12 @@ exec_simple_query(const char *query_string)
PortalDrop(portal, false);
+ /*
+ * Count SQL statement for pg_stat_sql view
+ */
+ if (pgstat_track_sql)
+ pgstat_count_sqlstmt(commandTag);
+
if (IsA(parsetree, TransactionStmt))
{
/*
@@ -1983,6 +1989,29 @@ exec_execute_message(const char *portal_name, long max_rows)
(*receiver->rDestroy) (receiver);
+ /*
+ * Count SQL Statement for pgx_stat_sql
+ */
+ if (pgstat_track_sql)
+ {
+ CachedPlanSource *psrc = NULL;
+
+ if (portal->prepStmtName)
+ {
+ PreparedStatement *pstmt;
+
+ pstmt = FetchPreparedStatement(portal->prepStmtName, false);
+ if (pstmt)
+ psrc = pstmt->plansource;
+ }
+ else
+ psrc = unnamed_stmt_psrc;
+
+ /* psrc should not be NULL here */
+ if (psrc && psrc->commandTag && !execute_is_fetch)
+ pgstat_count_sqlstmt(psrc->commandTag);
+ }
+
if (completed)
{
if (is_xact_command)
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 2d3cf9e..c3d451b 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -95,6 +95,7 @@ extern Datum pg_stat_get_db_blk_read_time(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_db_blk_write_time(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_archiver(PG_FUNCTION_ARGS);
+extern Datum pg_stat_sql(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_bgwriter_timed_checkpoints(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_bgwriter_requested_checkpoints(PG_FUNCTION_ARGS);
@@ -1931,3 +1932,77 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(HeapTupleGetDatum(
heap_form_tuple(tupdesc, values, nulls)));
}
+
+Datum
+pg_stat_sql(PG_FUNCTION_ARGS)
+{
+ TupleDesc tupdesc;
+ Datum values[2];
+ bool nulls[2];
+ ReturnSetInfo *rsi;
+ MemoryContext old_cxt;
+ Tuplestorestate *tuple_store;
+ PgStatSqlstmtEntry *sqlstmtentry;
+ HASH_SEQ_STATUS hstat;
+
+ /* Function call to let the backend read the stats file */
+ pgstat_fetch_global();
+
+ /* Initialize values and nulls arrays */
+ MemSet(values, 0, sizeof(values));
+ MemSet(nulls, 0, sizeof(nulls));
+
+ /*
+ * We must use the Materialize mode to be more efficient than checking the
+ * HASH every time.
+ */
+ rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ (rsi->allowedModes & SFRM_Materialize) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ rsi->returnMode = SFRM_Materialize;
+
+ /*
+ * Create the tupledesc and tuplestore in the per_query context as
+ * required for SFRM_Materialize.
+ */
+ old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+ /* Initialize attributes information in the tuple descriptor */
+ tupdesc = CreateTemplateTupleDesc(2, false);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "tag",
+ TEXTOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 2, "count",
+ INT8OID, -1, 0);
+
+ BlessTupleDesc(tupdesc);
+
+ tuple_store =
+ tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ false, work_mem);
+
+ MemoryContextSwitchTo(old_cxt);
+
+ /* Walk through the SQL statement Stats Hash table. */
+ hash_seq_init(&hstat, pgStatSql);
+ while ((sqlstmtentry = (PgStatSqlstmtEntry *) hash_seq_search(&hstat)) != NULL)
+ {
+ HeapTuple tuple;
+
+ /* Fill values and NULLs */
+ values[0] = CStringGetTextDatum(sqlstmtentry->tag);
+ values[1] = Int64GetDatum(sqlstmtentry->count);
+
+ tuple = heap_form_tuple(tupdesc, values, nulls);
+ tuplestore_puttuple(tuple_store, tuple);
+ }
+
+ rsi->setDesc = tupdesc;
+ rsi->setResult = tuple_store;
+
+ PG_RETURN_NULL();
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 622279b..8eb6c85 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1222,6 +1222,16 @@ static struct config_bool ConfigureNamesBool[] =
},
{
+ {"track_sql", PGC_SUSET, STATS_COLLECTOR,
+ gettext_noop("Collects timing statistics for database I/O activity."),
+ NULL
+ },
+ &pgstat_track_sql,
+ false,
+ NULL, NULL, NULL
+ },
+
+ {
{"update_process_title", PGC_SUSET, PROCESS_TITLE,
gettext_noop("Updates the process title to show the active SQL command."),
gettext_noop("Enables updating of the process title every time a new SQL command is received by the server.")
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 05b1373..4b720a6 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -475,6 +475,7 @@
#track_activities = on
#track_counts = on
+#track_sql = off
#track_io_timing = off
#track_functions = none # none, pl, all
#track_activity_query_size = 1024 # (change requires restart)
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..6b52222 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2896,6 +2896,9 @@ DESCR("statistics: total execution time of function in current transaction, in m
DATA(insert OID = 3048 ( pg_stat_get_xact_function_self_time PGNSP PGUID 12 1 0 0 0 f f f f t f v r 1 0 701 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_xact_function_self_time _null_ _null_ _null_ ));
DESCR("statistics: self execution time of function in current transaction, in msec");
+DATA(insert OID = 3343 ( pg_stat_sql PGNSP PGUID 12 1 1000 0 0 f f f f t t v s 0 0 2249 "" "{25,20}" "{o,o}" "{tag,count}" _null_ _null_ pg_stat_sql _null_ _null_ _null_ ));
+DESCR("statistics: Show SQL statement statistics");
+
DATA(insert OID = 3788 ( pg_stat_get_snapshot_timestamp PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 1184 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_snapshot_timestamp _null_ _null_ _null_ ));
DESCR("statistics: timestamp of the current statistics snapshot");
DATA(insert OID = 2230 ( pg_stat_clear_snapshot PGNSP PGUID 12 1 0 0 0 f f f f f f v r 0 0 2278 "" _null_ _null_ _null_ _null_ _null_ pg_stat_clear_snapshot _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 1c9bf13..346ffef 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -64,7 +64,8 @@ typedef enum StatMsgType
PGSTAT_MTYPE_FUNCPURGE,
PGSTAT_MTYPE_RECOVERYCONFLICT,
PGSTAT_MTYPE_TEMPFILE,
- PGSTAT_MTYPE_DEADLOCK
+ PGSTAT_MTYPE_DEADLOCK,
+ PGSTAT_MTYPE_SQLSTMT
} StatMsgType;
/* ----------
@@ -119,7 +120,8 @@ typedef struct PgStat_TableCounts
typedef enum PgStat_Shared_Reset_Target
{
RESET_ARCHIVER,
- RESET_BGWRITER
+ RESET_BGWRITER,
+ RESET_SQLSTMT
} PgStat_Shared_Reset_Target;
/* Possible object types for resetting single counters */
@@ -530,6 +532,30 @@ typedef struct PgStat_MsgDeadlock
Oid m_databaseid;
} PgStat_MsgDeadlock;
+/* ----------
+ * PgStatSqlstmt
+ * ----------
+ */
+typedef struct PgStatSqlstmtEntry
+{
+ char tag[NAMEDATALEN];
+ PgStat_Counter count;
+} PgStatSqlstmtEntry;
+
+
+/* ----------
+ * PgStatMsgSqlstmt Sent by the sql to update statistics.
+ * ----------
+ */
+#define PGSTAT_NUM_SQLSTMTS \
+ ((PGSTAT_MSG_PAYLOAD - sizeof(int)) / sizeof(PgStatSqlstmtEntry))
+
+typedef struct PgStatMsgSqlstmt
+{
+ PgStat_MsgHdr m_hdr;
+ int m_nentries;
+ PgStatSqlstmtEntry m_entry[PGSTAT_NUM_SQLSTMTS];
+} PgStat_MsgSqlstmt;
/* ----------
* PgStat_Msg Union over all possible messages.
@@ -555,6 +581,7 @@ typedef union PgStat_Msg
PgStat_MsgFuncpurge msg_funcpurge;
PgStat_MsgRecoveryConflict msg_recoveryconflict;
PgStat_MsgDeadlock msg_deadlock;
+ PgStat_MsgSqlstmt msg_sqlstmt;
} PgStat_Msg;
@@ -566,7 +593,7 @@ typedef union PgStat_Msg
* ------------------------------------------------------------
*/
-#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9D
+#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9E
/* ----------
* PgStat_StatDBEntry The collector's data per database
@@ -994,6 +1021,7 @@ typedef struct PgStat_FunctionCallUsage
*/
extern bool pgstat_track_activities;
extern bool pgstat_track_counts;
+extern bool pgstat_track_sql;
extern int pgstat_track_functions;
extern PGDLLIMPORT int pgstat_track_activity_query_size;
extern char *pgstat_stat_directory;
@@ -1006,6 +1034,11 @@ extern char *pgstat_stat_filename;
extern PgStat_MsgBgWriter BgWriterStats;
/*
+ * SQL statement statistics counter Hash table
+ */
+extern HTAB *pgStatSql;
+
+/*
* Updated by pgstat_count_buffer_*_time macros
*/
extern PgStat_Counter pgStatBlockReadTime;
@@ -1199,6 +1232,8 @@ extern void pgstat_twophase_postabort(TransactionId xid, uint16 info,
extern void pgstat_send_archiver(const char *xlog, bool failed);
extern void pgstat_send_bgwriter(void);
+extern void pgstat_count_sqlstmt(const char *commandTag);
+
/* ----------
* Support functions for the SQL-callable functions to
* generate the pgstat* views.
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 00700f2..55934ca 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1795,6 +1795,9 @@ pg_stat_replication| SELECT s.pid,
FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, ssl, sslversion, sslcipher, sslbits, sslcompression, sslclientdn)
JOIN pg_stat_get_wal_senders() w(pid, state, sent_location, write_location, flush_location, replay_location, sync_priority, sync_state) ON ((s.pid = w.pid)))
LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
+pg_stat_sql| SELECT pg_stat_sql.tag,
+ pg_stat_sql.count
+ FROM pg_stat_sql() pg_stat_sql(tag, count);
pg_stat_ssl| SELECT s.pid,
s.ssl,
s.sslversion AS version,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index a811265..be6db13 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -4,6 +4,62 @@
-- Must be run after tenk2 has been created (by create_table),
-- populated (by create_misc) and indexed (by create_index).
--
+-- pg_stat_sql
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+-----+-------
+(0 rows)
+
+SET track_sql TO ON;
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+--------+-------
+ SELECT | 1
+(1 row)
+
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+-----+-------
+(0 rows)
+
-- conditio sine qua non
SHOW track_counts; -- must be on
track_counts
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index b3e2efa..34716d6 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -5,6 +5,30 @@
-- populated (by create_misc) and indexed (by create_index).
--
+-- pg_stat_sql
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
+SET track_sql TO ON;
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
-- conditio sine qua non
SHOW track_counts; -- must be on
On 2016/10/12 12:21, Haribabu Kommi wrote:
On Thu, Sep 29, 2016 at 3:45 PM, Haribabu Kommi
<kommi.haribabu@gmail.com <mailto:kommi.haribabu@gmail.com>> wrote:On Thu, Sep 22, 2016 at 3:05 AM, Alvaro Herrera
<alvherre@2ndquadrant.com <mailto:alvherre@2ndquadrant.com>> wrote:Peter Eisentraut wrote:
How about having the tag not be a column name but a row
entry. So you'd
do something like
SELECT * FROM pg_stat_sql WHERE tag = 'ALTER VIEW';
That way, we don't have to keep updating (and re-debating)
this when new
command types or subtypes are added. And queries written
for future
versions will not fail when run against old servers.
Yeah, good idea.
Yes, Having it as a row entry is good.
Let's also discuss the interface from the stats collector.
Currently we
have some 20 new SQL functions, all alike, each loading the
whole data
and returning a single counter, and then the view invokes each
function
separately. That doesn't seem great to me. How about having
a single C
function that returns the whole thing as a SRF instead, and
the view is
just a single function invocation -- something like pg_lock_status
filling pg_locks in one go.Another consideration is that the present patch lumps together
all ALTER
cases in a single counter. This isn't great, but at the same
time we
don't want to bloat the stat files by having hundreds of
counters per
database, do we?Currently, The SQL stats is a fixed size counter to track the all
the ALTER
cases as single counter. So while sending the stats from the
backend to
stats collector at the end of the transaction, the cost is same,
because of
it's fixed size. This approach adds overhead to send and read the
stats
is minimal.With the following approach, I feel it is possible to support the
counter at
command tag level.Add a Global and local Hash to keep track of the counters by using the
command tag as the key, this hash table increases dynamically whenever
a new type of SQL command gets executed. The Local Hash data is passed
to stats collector whenever the transaction gets committed.The problem I am thinking is that, Sending data from Hash and
populating
the Hash from stats file for all the command tags adds some overhead.I tried changing the pg_stat_sql into row mode and ran the regress
suite to
add different type of SQL commands to the view and ran the pgbench test
on my laptop to find out any performance impact with this patch.HEAD PATCH
pgbench - select 828 816Here I attached the pg_stat_sql patch to keep track of all SQL commands
based on the commandTag and their counts. I attached the result of this
view that is run on the database after "make installcheck" just for
reference.
Thank you for the patch.
Test: Commands with uppercase and lowercase
====
If the tag='select' then it returns the 0 rows but count is actually
increment by 1.
tag='select' vs tag='SELECT'
postgres=# SET track_sql TO ON;
SET
postgres=# SELECT * FROM pg_stat_sql where tag='SELECT';
tag | count
--------+-------
SELECT | 12
(1 row)
postgres=# SELECT * FROM pg_stat_sql where tag=*'SELECT'*;
tag | count
--------+-------
* SELECT | 13*
(1 row)
postgres=# SELECT * FROM pg_stat_sql where tag=*'select'*;
tag | count
-----+-------
*(0 rows)*
postgres=# SELECT * FROM pg_stat_sql where tag=*'SELECT'*;
tag | count
--------+-------
* SELECT | 15*
(1 row)
I think all command works same as above.
Regards,
Vinayak Pokale
NTT Open Source Software Center
On 2016/10/12 12:21, Haribabu Kommi wrote:
On Thu, Sep 29, 2016 at 3:45 PM, Haribabu Kommi
<kommi.haribabu@gmail.com <mailto:kommi.haribabu@gmail.com>> wrote:On Thu, Sep 22, 2016 at 3:05 AM, Alvaro Herrera
<alvherre@2ndquadrant.com <mailto:alvherre@2ndquadrant.com>> wrote:Peter Eisentraut wrote:
How about having the tag not be a column name but a row
entry. So you'd
do something like
SELECT * FROM pg_stat_sql WHERE tag = 'ALTER VIEW';
That way, we don't have to keep updating (and re-debating)
this when new
command types or subtypes are added. And queries written
for future
versions will not fail when run against old servers.
Yeah, good idea.
Yes, Having it as a row entry is good.
Let's also discuss the interface from the stats collector.
Currently we
have some 20 new SQL functions, all alike, each loading the
whole data
and returning a single counter, and then the view invokes each
function
separately. That doesn't seem great to me. How about having
a single C
function that returns the whole thing as a SRF instead, and
the view is
just a single function invocation -- something like pg_lock_status
filling pg_locks in one go.Another consideration is that the present patch lumps together
all ALTER
cases in a single counter. This isn't great, but at the same
time we
don't want to bloat the stat files by having hundreds of
counters per
database, do we?Currently, The SQL stats is a fixed size counter to track the all
the ALTER
cases as single counter. So while sending the stats from the
backend to
stats collector at the end of the transaction, the cost is same,
because of
it's fixed size. This approach adds overhead to send and read the
stats
is minimal.With the following approach, I feel it is possible to support the
counter at
command tag level.Add a Global and local Hash to keep track of the counters by using the
command tag as the key, this hash table increases dynamically whenever
a new type of SQL command gets executed. The Local Hash data is passed
to stats collector whenever the transaction gets committed.The problem I am thinking is that, Sending data from Hash and
populating
the Hash from stats file for all the command tags adds some overhead.I tried changing the pg_stat_sql into row mode and ran the regress
suite to
add different type of SQL commands to the view and ran the pgbench test
on my laptop to find out any performance impact with this patch.HEAD PATCH
pgbench - select 828 816Here I attached the pg_stat_sql patch to keep track of all SQL commands
based on the commandTag and their counts. I attached the result of this
view that is run on the database after "make installcheck" just for
reference.
Some comments:
I think we can use pgstat_* instead of pgstat* for code consistency.
+static HTAB *pgStatBackendSql = NULL;
How about *pgstat_backend_sql
+HTAB *pgStatSql = NULL;
How about *pgstat_sql
+static void pgstat_recv_sqlstmt(PgStat_MsgSqlstmt * msg, int len);
How about PgStat_MsgSqlstmt *msg instead of PgStat_MsgSqlstmt * msg
+typedef struct PgStatSqlstmtEntry
How about PgStat_SqlstmtEntry
+typedef struct PgStatMsgSqlstmt
How about PgStat_MsgSqlstmt
I have observed below behavior.
I have SET track_sql to ON and then execute the SELECT command and it
return 0 rows.
It start counting from the second command.
postgres=# SET track_sql TO ON;
SET
postgres=# SELECT * FROM pg_stat_sql where tag='SELECT';
tag | count
-----+-------
(0 rows)
postgres=# SELECT * FROM pg_stat_sql where tag='SELECT';
tag | count
--------+-------
SELECT | 1
(1 row)
Is this a correct behavior?
Regards,
Vinayak Pokale
NTT Open Source Software Center
On Wed, Oct 12, 2016 at 4:06 PM, vinayak <Pokale_Vinayak_q3@lab.ntt.co.jp>
wrote:
Thank you for the patch.
Test: Commands with uppercase and lowercase
====
If the tag='select' then it returns the 0 rows but count is actually
increment by 1.tag='select' vs tag='SELECT'
postgres=# SET track_sql TO ON;
SET
postgres=# SELECT * FROM pg_stat_sql where tag='SELECT';
tag | count
--------+-------
SELECT | 12
(1 row)postgres=# SELECT * FROM pg_stat_sql where tag=*'SELECT'*;
tag | count
--------+-------
* SELECT | 13*
(1 row)postgres=# SELECT * FROM pg_stat_sql where tag=*'select'*;
tag | count
-----+-------
*(0 rows)*postgres=# SELECT * FROM pg_stat_sql where tag=*'SELECT'*;
tag | count
--------+-------
* SELECT | 15*
(1 row)I think all command works same as above.
Thanks for checking the patch.
Yes, that's correct. Currently the CAPS letters are used as tag names
that are getting displayed whenever any SQL command is executed.
So I used the same names as entries to store the details of stats of
SQL statements.
If anyone feels the other way is better, I am fine for it.
Regards,
Hari Babu
Fujitsu Australia
On Fri, Oct 14, 2016 at 7:48 PM, vinayak <Pokale_Vinayak_q3@lab.ntt.co.jp>
wrote:
On 2016/10/12 12:21, Haribabu Kommi wrote:
I tried changing the pg_stat_sql into row mode and ran the regress suite
to
add different type of SQL commands to the view and ran the pgbench test
on my laptop to find out any performance impact with this patch.HEAD PATCH
pgbench - select 828 816Here I attached the pg_stat_sql patch to keep track of all SQL commands
based on the commandTag and their counts. I attached the result of this
view that is run on the database after "make installcheck" just for
reference.Some comments:
I think we can use pgstat_* instead of pgstat* for code consistency.+static HTAB *pgStatBackendSql = NULL;
How about *pgstat_backend_sql+HTAB *pgStatSql = NULL;
How about *pgstat_sql
Changed.
+static void pgstat_recv_sqlstmt(PgStat_MsgSqlstmt * msg, int len);
How about PgStat_MsgSqlstmt *msg instead of PgStat_MsgSqlstmt * msg
Added the typdef into typdef.list file so this problem never occurs.
+typedef struct PgStatSqlstmtEntry
How about PgStat_SqlstmtEntry+typedef struct PgStatMsgSqlstmt
How about PgStat_MsgSqlstmt
changed.
I have observed below behavior.
I have SET track_sql to ON and then execute the SELECT command and it
return 0 rows.
It start counting from the second command.
postgres=# SET track_sql TO ON;
SET
postgres=# SELECT * FROM pg_stat_sql where tag='SELECT';
tag | count
-----+-------
(0 rows)postgres=# SELECT * FROM pg_stat_sql where tag='SELECT';
tag | count
--------+-------
SELECT | 1
(1 row)
Is this a correct behavior?
Yes. This is because the stats of the SQL statements that are collected are
sent to
the stats collector at the end of the SQL statement execution. The current
SQL statement
is not counted in the result.
Updated patch is attached.
Regards,
Hari Babu
Fujitsu Australia
Attachments:
pg_stat_sql_row_mode_2.patchapplication/octet-stream; name=pg_stat_sql_row_mode_2.patchDownload
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e826c19..a55327c 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5558,6 +5558,21 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
</listitem>
</varlistentry>
+ <varlistentry id="guc-track-sql" xreflabel="track_sql">
+ <term><varname>track_sql</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>track_sql</> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Enables collection of different SQL statement statistics that are
+ executed on the instance. This parameter is off by default. Only
+ superusers can change this setting.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-stats-temp-directory" xreflabel="stats_temp_directory">
<term><varname>stats_temp_directory</varname> (<type>string</type>)
<indexterm>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3de489e..5a55ce7 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -513,6 +513,13 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
<command>VACUUM</>, showing current progress.
See <xref linkend='vacuum-progress-reporting'>.</entry>
</row>
+
+ <row>
+ <entry><structname>pg_stat_sql</><indexterm><primary>pg_stat_sql</primary></indexterm></entry>
+ <entry>Shows statistics about the SQL statements that are
+ executed on the instance.
+ See <xref linkend="pg-stat-sql-view"> for details.</entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -2391,6 +2398,40 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
controls exactly which functions are tracked.
</para>
+ <table id="pg-stat-sql-view" xreflabel="pg_stat_sql">
+ <title><structname>pg_stat_sql</structname> View</title>
+ <tgroup cols="3">
+ <thead>
+ <row>
+ <entry>Column</entry>
+ <entry>Type</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry><structfield>tag</></entry>
+ <entry><type>text</></entry>
+ <entry>Name of the SQL statement</entry>
+ </row>
+ <row>
+ <entry><structfield>count</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of times the SQL statement is executed</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The <structname>pg_stat_sql</structname> view will contain statistics
+ about number of SQL statements that are executed on the instance.
+ The <xref linkend="guc-track-sql"> parameter controls the SQL statement
+ execution statistics.
+ </para>
+
+
</sect2>
<sect2 id="monitoring-stats-functions">
@@ -2486,6 +2527,8 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
counters shown in the <structname>pg_stat_bgwriter</> view.
Calling <literal>pg_stat_reset_shared('archiver')</> will zero all the
counters shown in the <structname>pg_stat_archiver</> view.
+ Calling <literal>pg_stat_reset_shared('sqlstmt')</> will zero all the
+ counters shown in the <structname>pg_stat_sql</> view.
</entry>
</row>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ada2142..0a177b4 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -842,6 +842,11 @@ CREATE VIEW pg_replication_origin_status AS
REVOKE ALL ON pg_replication_origin_status FROM public;
+CREATE VIEW pg_stat_sql AS
+ SELECT * FROM pg_stat_sql();
+
+REVOKE EXECUTE ON FUNCTION pg_stat_sql() FROM public;
+
--
-- We have a few function definitions in here, too.
-- At some point there might be enough to justify breaking them out into
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 5112d6d..bdf5057 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -100,6 +100,7 @@
#define PGSTAT_DB_HASH_SIZE 16
#define PGSTAT_TAB_HASH_SIZE 512
#define PGSTAT_FUNCTION_HASH_SIZE 512
+#define PGSTAT_SQLSTMT_HASH_SIZE 256
/* ----------
@@ -108,6 +109,7 @@
*/
bool pgstat_track_activities = false;
bool pgstat_track_counts = false;
+bool pgstat_track_sql = false;
int pgstat_track_functions = TRACK_FUNC_OFF;
int pgstat_track_activity_query_size = 1024;
@@ -126,6 +128,19 @@ char *pgstat_stat_tmpname = NULL;
*/
PgStat_MsgBgWriter BgWriterStats;
+/*
+ * Backend specific SQL statement information stored in this hash table,
+ * before sent it to the stats collector.
+ */
+static HTAB *pgstat_backend_sql = NULL;
+
+/*
+ * Global SQL statement stats information gathered in the collector are stored in
+ * this hash table.
+ */
+HTAB *pgstat_sql = NULL;
+
+
/* ----------
* Local data
* ----------
@@ -270,6 +285,7 @@ static bool pgstat_db_requested(Oid databaseid);
static void pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg);
static void pgstat_send_funcstats(void);
+static void pgstat_send_sqlstmt(void);
static HTAB *pgstat_collect_oids(Oid catalogid);
static PgStat_TableStatus *get_tabstat_entry(Oid rel_id, bool isshared);
@@ -301,6 +317,7 @@ static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len);
static void pgstat_recv_recoveryconflict(PgStat_MsgRecoveryConflict *msg, int len);
static void pgstat_recv_deadlock(PgStat_MsgDeadlock *msg, int len);
static void pgstat_recv_tempfile(PgStat_MsgTempFile *msg, int len);
+static void pgstat_recv_sqlstmt(PgStat_MsgSqlstmt *msg, int len);
/* ------------------------------------------------------------
* Public functions called from postmaster follow
@@ -834,6 +851,10 @@ pgstat_report_stat(bool force)
/* Now, send function statistics */
pgstat_send_funcstats();
+
+ /* Now, send sql statistics */
+ if (pgstat_track_sql)
+ pgstat_send_sqlstmt();
}
/*
@@ -1260,11 +1281,13 @@ pgstat_reset_shared_counters(const char *target)
msg.m_resettarget = RESET_ARCHIVER;
else if (strcmp(target, "bgwriter") == 0)
msg.m_resettarget = RESET_BGWRITER;
+ else if (strcmp(target, "sqlstmt") == 0)
+ msg.m_resettarget = RESET_SQLSTMT;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized reset target: \"%s\"", target),
- errhint("Target must be \"archiver\" or \"bgwriter\".")));
+ errhint("Target must be \"archiver\" or \"bgwriter\" or \"sqlstmt\".")));
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSHAREDCOUNTER);
pgstat_send(&msg, sizeof(msg));
@@ -3682,6 +3705,89 @@ pgstat_send_bgwriter(void)
MemSet(&BgWriterStats, 0, sizeof(BgWriterStats));
}
+/* ----------
+ * pgstat_send_sqlstmt(void)
+ *
+ * Send SQL statement statistics to the collector
+ * ----------
+ */
+static void
+pgstat_send_sqlstmt(void)
+{
+ PgStat_MsgSqlstmt msg;
+ PgStat_SqlstmtEntry *entry;
+ HASH_SEQ_STATUS hstat;
+
+ if (pgstat_backend_sql == NULL)
+ return;
+
+ pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_SQLSTMT);
+ msg.m_nentries = 0;
+
+ hash_seq_init(&hstat, pgstat_backend_sql);
+ while ((entry = (PgStat_SqlstmtEntry *) hash_seq_search(&hstat)) != NULL)
+ {
+ PgStat_SqlstmtEntry *m_ent;
+
+ /* Skip it if no counts accumulated since last time */
+ if (entry->count == 0)
+ continue;
+
+ /* need to convert format of time accumulators */
+ m_ent = &msg.m_entry[msg.m_nentries];
+ memcpy(m_ent, entry, sizeof(PgStat_SqlstmtEntry));
+
+ if (++msg.m_nentries >= PGSTAT_NUM_SQLSTMTS)
+ {
+ pgstat_send(&msg, offsetof(PgStat_MsgSqlstmt, m_entry[0]) +
+ msg.m_nentries * sizeof(PgStat_SqlstmtEntry));
+ msg.m_nentries = 0;
+ }
+
+ /* reset the entry's count */
+ entry->count = 0;
+ }
+
+ if (msg.m_nentries > 0)
+ pgstat_send(&msg, offsetof(PgStat_MsgSqlstmt, m_entry[0]) +
+ msg.m_nentries * sizeof(PgStat_SqlstmtEntry));
+}
+
+/*
+ * Count SQL statement for pg_stat_sql view
+ */
+void
+pgstat_count_sqlstmt(const char *commandTag)
+{
+ PgStat_SqlstmtEntry *htabent;
+ bool found;
+
+ if (!pgstat_track_sql)
+ return;
+
+ if (!pgstat_backend_sql)
+ {
+ /* First time through - initialize SQL statement stat table */
+ HASHCTL hash_ctl;
+
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = NAMEDATALEN;
+ hash_ctl.entrysize = sizeof(PgStat_SqlstmtEntry);
+ pgstat_backend_sql = hash_create("SQL statement stat entries",
+ PGSTAT_SQLSTMT_HASH_SIZE,
+ &hash_ctl,
+ HASH_ELEM | HASH_BLOBS);
+ }
+
+ /* Get the stats entry for this SQL statement, create if necessary */
+ htabent = hash_search(pgstat_backend_sql, commandTag,
+ HASH_ENTER, &found);
+ if (!found)
+ htabent->count = 1;
+ else
+ htabent->count++;
+}
+
/* ----------
* PgstatCollectorMain() -
@@ -3898,6 +4004,9 @@ PgstatCollectorMain(int argc, char *argv[])
pgstat_recv_tempfile((PgStat_MsgTempFile *) &msg, len);
break;
+ case PGSTAT_MTYPE_SQLSTMT:
+ pgstat_recv_sqlstmt((PgStat_MsgSqlstmt *) &msg, len);
+ break;
default:
break;
}
@@ -4118,6 +4227,7 @@ pgstat_write_statsfiles(bool permanent, bool allDbs)
{
HASH_SEQ_STATUS hstat;
PgStat_StatDBEntry *dbentry;
+ PgStat_SqlstmtEntry *sqlstmtentry;
FILE *fpout;
int32 format_id;
const char *tmpfile = permanent ? PGSTAT_STAT_PERMANENT_TMPFILE : pgstat_stat_tmpname;
@@ -4190,6 +4300,15 @@ pgstat_write_statsfiles(bool permanent, bool allDbs)
(void) rc; /* we'll check for error with ferror */
}
+ /* Walk through the SQL statement Stats Hash table. */
+ hash_seq_init(&hstat, pgstat_sql);
+ while ((sqlstmtentry = (PgStat_SqlstmtEntry *) hash_seq_search(&hstat)) != NULL)
+ {
+ fputc('S', fpout);
+ rc = fwrite(sqlstmtentry, sizeof(PgStat_SqlstmtEntry), 1, fpout);
+ (void) rc; /* we'll check for error with ferror */
+ }
+
/*
* No more output to be done. Close the temp file and replace the old
* pgstat.stat with it. The ferror() check replaces testing for error
@@ -4414,6 +4533,14 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep)
dbhash = hash_create("Databases hash", PGSTAT_DB_HASH_SIZE, &hash_ctl,
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+ /* Create SQL statement counter Stats HashTable */
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = NAMEDATALEN;
+ hash_ctl.entrysize = sizeof(PgStat_SqlstmtEntry);
+ pgstat_sql = hash_create("SQL statement counter",
+ PGSTAT_SQLSTMT_HASH_SIZE, &hash_ctl,
+ HASH_ELEM);
+
/*
* Clear out global and archiver statistics so they start from zero in
* case we can't load an existing statsfile.
@@ -4559,6 +4686,38 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep)
break;
+ case 'S':
+ {
+ PgStat_SqlstmtEntry stats;
+ PgStat_SqlstmtEntry *lstats;
+
+ if (fread(&stats, 1, sizeof(PgStat_SqlstmtEntry), fpin) != sizeof(PgStat_SqlstmtEntry))
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"",
+ statfile)));
+ goto done;
+ }
+
+ /*
+ * Add to the SQL statement counter hash
+ */
+ lstats = (PgStat_SqlstmtEntry *) hash_search(pgstat_sql,
+ (void *) &(stats.tag),
+ HASH_ENTER,
+ &found);
+ if (found)
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"",
+ statfile)));
+ goto done;
+ }
+
+ memcpy(lstats, &stats, sizeof(PgStat_SqlstmtEntry));
+
+ break;
+ }
case 'E':
goto done;
@@ -4858,6 +5017,21 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool permanent,
break;
+ case 'S':
+ {
+ PgStat_SqlstmtEntry stats;
+
+ if (fread(&stats, 1, sizeof(PgStat_SqlstmtEntry), fpin) != sizeof(PgStat_SqlstmtEntry))
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"",
+ statfile)));
+ goto done;
+ }
+
+ break;
+ }
+
case 'E':
goto done;
@@ -5383,6 +5557,21 @@ pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter *msg, int len)
memset(&archiverStats, 0, sizeof(archiverStats));
archiverStats.stat_reset_timestamp = GetCurrentTimestamp();
}
+ else if (msg->m_resettarget == RESET_SQLSTMT)
+ {
+ HASHCTL hash_ctl;
+
+ /* Destroy the SQL statement counter stats HashTable */
+ hash_destroy(pgstat_sql);
+
+ /* Create SQL statement counter Stats HashTable */
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = NAMEDATALEN;
+ hash_ctl.entrysize = sizeof(PgStat_SqlstmtEntry);
+ pgstat_sql = hash_create("SQL statement counter",
+ PGSTAT_SQLSTMT_HASH_SIZE, &hash_ctl,
+ HASH_ELEM);
+ }
/*
* Presumably the sender of this message validated the target, don't
@@ -5563,6 +5752,37 @@ pgstat_recv_bgwriter(PgStat_MsgBgWriter *msg, int len)
}
/* ----------
+ * pgstat_recv_sqlstmt() -
+ *
+ * Process a SQL statement statistics message.
+ * ----------
+ */
+static void
+pgstat_recv_sqlstmt(PgStat_MsgSqlstmt *msg, int len)
+{
+ PgStat_SqlstmtEntry *entry;
+ bool found;
+ int i;
+
+ for (i = 0; i < msg->m_nentries; i++)
+ {
+ /* Get the stats entry for this SQL statement, create if necessary */
+ entry = (PgStat_SqlstmtEntry *) hash_search(pgstat_sql, (void *) &(msg->m_entry[i].tag),
+ HASH_ENTER, &found);
+
+ if (found)
+ {
+ entry->count += msg->m_entry[i].count;
+ }
+ else
+ {
+ memcpy(entry->tag, msg->m_entry[i].tag, NAMEDATALEN);
+ entry->count = msg->m_entry[i].count;
+ }
+ }
+}
+
+/* ----------
* pgstat_recv_recoveryconflict() -
*
* Process a RECOVERYCONFLICT message.
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 2347f1b..4a168ca 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1102,6 +1102,12 @@ exec_simple_query(const char *query_string)
PortalDrop(portal, false);
+ /*
+ * Count SQL statement for pg_stat_sql view
+ */
+ if (pgstat_track_sql)
+ pgstat_count_sqlstmt(commandTag);
+
if (IsA(parsetree, TransactionStmt))
{
/*
@@ -1983,6 +1989,29 @@ exec_execute_message(const char *portal_name, long max_rows)
(*receiver->rDestroy) (receiver);
+ /*
+ * Count SQL Statement for pgx_stat_sql
+ */
+ if (pgstat_track_sql)
+ {
+ CachedPlanSource *psrc = NULL;
+
+ if (portal->prepStmtName)
+ {
+ PreparedStatement *pstmt;
+
+ pstmt = FetchPreparedStatement(portal->prepStmtName, false);
+ if (pstmt)
+ psrc = pstmt->plansource;
+ }
+ else
+ psrc = unnamed_stmt_psrc;
+
+ /* psrc should not be NULL here */
+ if (psrc && psrc->commandTag && !execute_is_fetch)
+ pgstat_count_sqlstmt(psrc->commandTag);
+ }
+
if (completed)
{
if (is_xact_command)
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 2d3cf9e..a1d2415 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -95,6 +95,7 @@ extern Datum pg_stat_get_db_blk_read_time(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_db_blk_write_time(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_archiver(PG_FUNCTION_ARGS);
+extern Datum pg_stat_sql(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_bgwriter_timed_checkpoints(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_bgwriter_requested_checkpoints(PG_FUNCTION_ARGS);
@@ -1931,3 +1932,77 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(HeapTupleGetDatum(
heap_form_tuple(tupdesc, values, nulls)));
}
+
+Datum
+pg_stat_sql(PG_FUNCTION_ARGS)
+{
+ TupleDesc tupdesc;
+ Datum values[2];
+ bool nulls[2];
+ ReturnSetInfo *rsi;
+ MemoryContext old_cxt;
+ Tuplestorestate *tuple_store;
+ PgStat_SqlstmtEntry *sqlstmtentry;
+ HASH_SEQ_STATUS hstat;
+
+ /* Function call to let the backend read the stats file */
+ pgstat_fetch_global();
+
+ /* Initialize values and nulls arrays */
+ MemSet(values, 0, sizeof(values));
+ MemSet(nulls, 0, sizeof(nulls));
+
+ /*
+ * We must use the Materialize mode to be more efficient than checking the
+ * HASH every time.
+ */
+ rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ (rsi->allowedModes & SFRM_Materialize) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ rsi->returnMode = SFRM_Materialize;
+
+ /*
+ * Create the tupledesc and tuplestore in the per_query context as
+ * required for SFRM_Materialize.
+ */
+ old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+ /* Initialize attributes information in the tuple descriptor */
+ tupdesc = CreateTemplateTupleDesc(2, false);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "tag",
+ TEXTOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 2, "count",
+ INT8OID, -1, 0);
+
+ BlessTupleDesc(tupdesc);
+
+ tuple_store =
+ tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ false, work_mem);
+
+ MemoryContextSwitchTo(old_cxt);
+
+ /* Walk through the SQL statement Stats Hash table. */
+ hash_seq_init(&hstat, pgstat_sql);
+ while ((sqlstmtentry = (PgStat_SqlstmtEntry *) hash_seq_search(&hstat)) != NULL)
+ {
+ HeapTuple tuple;
+
+ /* Fill values and NULLs */
+ values[0] = CStringGetTextDatum(sqlstmtentry->tag);
+ values[1] = Int64GetDatum(sqlstmtentry->count);
+
+ tuple = heap_form_tuple(tupdesc, values, nulls);
+ tuplestore_puttuple(tuple_store, tuple);
+ }
+
+ rsi->setDesc = tupdesc;
+ rsi->setResult = tuple_store;
+
+ PG_RETURN_NULL();
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 622279b..8eb6c85 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1222,6 +1222,16 @@ static struct config_bool ConfigureNamesBool[] =
},
{
+ {"track_sql", PGC_SUSET, STATS_COLLECTOR,
+ gettext_noop("Collects timing statistics for database I/O activity."),
+ NULL
+ },
+ &pgstat_track_sql,
+ false,
+ NULL, NULL, NULL
+ },
+
+ {
{"update_process_title", PGC_SUSET, PROCESS_TITLE,
gettext_noop("Updates the process title to show the active SQL command."),
gettext_noop("Enables updating of the process title every time a new SQL command is received by the server.")
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 05b1373..4b720a6 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -475,6 +475,7 @@
#track_activities = on
#track_counts = on
+#track_sql = off
#track_io_timing = off
#track_functions = none # none, pl, all
#track_activity_query_size = 1024 # (change requires restart)
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 17ec71d..4d31fd6 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2893,6 +2893,9 @@ DESCR("statistics: total execution time of function in current transaction, in m
DATA(insert OID = 3048 ( pg_stat_get_xact_function_self_time PGNSP PGUID 12 1 0 0 0 f f f f t f v r 1 0 701 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_xact_function_self_time _null_ _null_ _null_ ));
DESCR("statistics: self execution time of function in current transaction, in msec");
+DATA(insert OID = 3343 ( pg_stat_sql PGNSP PGUID 12 1 1000 0 0 f f f f t t v s 0 0 2249 "" "{25,20}" "{o,o}" "{tag,count}" _null_ _null_ pg_stat_sql _null_ _null_ _null_ ));
+DESCR("statistics: Show SQL statement statistics");
+
DATA(insert OID = 3788 ( pg_stat_get_snapshot_timestamp PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 1184 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_snapshot_timestamp _null_ _null_ _null_ ));
DESCR("statistics: timestamp of the current statistics snapshot");
DATA(insert OID = 2230 ( pg_stat_clear_snapshot PGNSP PGUID 12 1 0 0 0 f f f f f f v r 0 0 2278 "" _null_ _null_ _null_ _null_ _null_ pg_stat_clear_snapshot _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 1c9bf13..ceaa4ba 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -64,7 +64,8 @@ typedef enum StatMsgType
PGSTAT_MTYPE_FUNCPURGE,
PGSTAT_MTYPE_RECOVERYCONFLICT,
PGSTAT_MTYPE_TEMPFILE,
- PGSTAT_MTYPE_DEADLOCK
+ PGSTAT_MTYPE_DEADLOCK,
+ PGSTAT_MTYPE_SQLSTMT
} StatMsgType;
/* ----------
@@ -119,7 +120,8 @@ typedef struct PgStat_TableCounts
typedef enum PgStat_Shared_Reset_Target
{
RESET_ARCHIVER,
- RESET_BGWRITER
+ RESET_BGWRITER,
+ RESET_SQLSTMT
} PgStat_Shared_Reset_Target;
/* Possible object types for resetting single counters */
@@ -530,6 +532,30 @@ typedef struct PgStat_MsgDeadlock
Oid m_databaseid;
} PgStat_MsgDeadlock;
+/* ----------
+ * PgStatSqlstmt
+ * ----------
+ */
+typedef struct PgStat_SqlstmtEntry
+{
+ char tag[NAMEDATALEN];
+ PgStat_Counter count;
+} PgStat_SqlstmtEntry;
+
+
+/* ----------
+ * PgStatMsgSqlstmt Sent by the sql to update statistics.
+ * ----------
+ */
+#define PGSTAT_NUM_SQLSTMTS \
+ ((PGSTAT_MSG_PAYLOAD - sizeof(int)) / sizeof(PgStatSqlstmtEntry))
+
+typedef struct PgStat_MsgSqlstmt
+{
+ PgStat_MsgHdr m_hdr;
+ int m_nentries;
+ PgStat_SqlstmtEntry m_entry[PGSTAT_NUM_SQLSTMTS];
+} PgStat_MsgSqlstmt;
/* ----------
* PgStat_Msg Union over all possible messages.
@@ -555,6 +581,7 @@ typedef union PgStat_Msg
PgStat_MsgFuncpurge msg_funcpurge;
PgStat_MsgRecoveryConflict msg_recoveryconflict;
PgStat_MsgDeadlock msg_deadlock;
+ PgStat_MsgSqlstmt msg_sqlstmt;
} PgStat_Msg;
@@ -566,7 +593,7 @@ typedef union PgStat_Msg
* ------------------------------------------------------------
*/
-#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9D
+#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9E
/* ----------
* PgStat_StatDBEntry The collector's data per database
@@ -994,6 +1021,7 @@ typedef struct PgStat_FunctionCallUsage
*/
extern bool pgstat_track_activities;
extern bool pgstat_track_counts;
+extern bool pgstat_track_sql;
extern int pgstat_track_functions;
extern PGDLLIMPORT int pgstat_track_activity_query_size;
extern char *pgstat_stat_directory;
@@ -1006,6 +1034,11 @@ extern char *pgstat_stat_filename;
extern PgStat_MsgBgWriter BgWriterStats;
/*
+ * SQL statement statistics counter Hash table
+ */
+extern HTAB *pgstat_sql;
+
+/*
* Updated by pgstat_count_buffer_*_time macros
*/
extern PgStat_Counter pgStatBlockReadTime;
@@ -1199,6 +1232,8 @@ extern void pgstat_twophase_postabort(TransactionId xid, uint16 info,
extern void pgstat_send_archiver(const char *xlog, bool failed);
extern void pgstat_send_bgwriter(void);
+extern void pgstat_count_sqlstmt(const char *commandTag);
+
/* ----------
* Support functions for the SQL-callable functions to
* generate the pgstat* views.
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 00700f2..55934ca 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1795,6 +1795,9 @@ pg_stat_replication| SELECT s.pid,
FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, ssl, sslversion, sslcipher, sslbits, sslcompression, sslclientdn)
JOIN pg_stat_get_wal_senders() w(pid, state, sent_location, write_location, flush_location, replay_location, sync_priority, sync_state) ON ((s.pid = w.pid)))
LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
+pg_stat_sql| SELECT pg_stat_sql.tag,
+ pg_stat_sql.count
+ FROM pg_stat_sql() pg_stat_sql(tag, count);
pg_stat_ssl| SELECT s.pid,
s.ssl,
s.sslversion AS version,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index a811265..be6db13 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -4,6 +4,62 @@
-- Must be run after tenk2 has been created (by create_table),
-- populated (by create_misc) and indexed (by create_index).
--
+-- pg_stat_sql
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+-----+-------
+(0 rows)
+
+SET track_sql TO ON;
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+--------+-------
+ SELECT | 1
+(1 row)
+
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+-----+-------
+(0 rows)
+
-- conditio sine qua non
SHOW track_counts; -- must be on
track_counts
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index b3e2efa..34716d6 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -5,6 +5,30 @@
-- populated (by create_misc) and indexed (by create_index).
--
+-- pg_stat_sql
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
+SET track_sql TO ON;
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
-- conditio sine qua non
SHOW track_counts; -- must be on
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 6c6d519..7c2bcc1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1517,6 +1517,7 @@ PgStat_MsgRecoveryConflict
PgStat_MsgResetcounter
PgStat_MsgResetsharedcounter
PgStat_MsgResetsinglecounter
+PgStat_MsgSqlstmt
PgStat_MsgTabpurge
PgStat_MsgTabstat
PgStat_MsgTempFile
On 2016/10/17 10:22, Haribabu Kommi wrote:
On Fri, Oct 14, 2016 at 7:48 PM, vinayak
<Pokale_Vinayak_q3@lab.ntt.co.jp
<mailto:Pokale_Vinayak_q3@lab.ntt.co.jp>> wrote:On 2016/10/12 12:21, Haribabu Kommi wrote:
I tried changing the pg_stat_sql into row mode and ran the
regress suite to
add different type of SQL commands to the view and ran the
pgbench test
on my laptop to find out any performance impact with this patch.HEAD PATCH
pgbench - select 828 816Here I attached the pg_stat_sql patch to keep track of all SQL
commands
based on the commandTag and their counts. I attached the result
of this
view that is run on the database after "make installcheck" just
for reference.Some comments:
I think we can use pgstat_* instead of pgstat* for code consistency.+static HTAB *pgStatBackendSql = NULL;
How about *pgstat_backend_sql+HTAB *pgStatSql = NULL;
How about *pgstat_sqlChanged.
+static void pgstat_recv_sqlstmt(PgStat_MsgSqlstmt * msg, int len);
How about PgStat_MsgSqlstmt *msg instead of PgStat_MsgSqlstmt * msgAdded the typdef into typdef.list file so this problem never occurs.
+typedef struct PgStatSqlstmtEntry
How about PgStat_SqlstmtEntry+typedef struct PgStatMsgSqlstmt
How about PgStat_MsgSqlstmtchanged.
Updated patch is attached.
The updated patch gives following error.
Error:
../../../../src/include/pgstat.h:557: error: ‘PgStatSqlstmtEntry’
undeclared here (not in a function)
- ((PGSTAT_MSG_PAYLOAD - sizeof(int)) / sizeof(PgStatSqlstmtEntry))
+ ((PGSTAT_MSG_PAYLOAD - sizeof(int)) / sizeof(PgStat_SqlstmtEntry))
The attached patch fixed the error.
Regards,
Vinayak Pokale
Attachments:
pg_stat_sql_row_mode_2.patchapplication/x-download; name=pg_stat_sql_row_mode_2.patchDownload
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e826c19..a55327c 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5558,6 +5558,21 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
</listitem>
</varlistentry>
+ <varlistentry id="guc-track-sql" xreflabel="track_sql">
+ <term><varname>track_sql</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>track_sql</> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Enables collection of different SQL statement statistics that are
+ executed on the instance. This parameter is off by default. Only
+ superusers can change this setting.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-stats-temp-directory" xreflabel="stats_temp_directory">
<term><varname>stats_temp_directory</varname> (<type>string</type>)
<indexterm>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3de489e..5a55ce7 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -513,6 +513,13 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
<command>VACUUM</>, showing current progress.
See <xref linkend='vacuum-progress-reporting'>.</entry>
</row>
+
+ <row>
+ <entry><structname>pg_stat_sql</><indexterm><primary>pg_stat_sql</primary></indexterm></entry>
+ <entry>Shows statistics about the SQL statements that are
+ executed on the instance.
+ See <xref linkend="pg-stat-sql-view"> for details.</entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -2391,6 +2398,40 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
controls exactly which functions are tracked.
</para>
+ <table id="pg-stat-sql-view" xreflabel="pg_stat_sql">
+ <title><structname>pg_stat_sql</structname> View</title>
+ <tgroup cols="3">
+ <thead>
+ <row>
+ <entry>Column</entry>
+ <entry>Type</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry><structfield>tag</></entry>
+ <entry><type>text</></entry>
+ <entry>Name of the SQL statement</entry>
+ </row>
+ <row>
+ <entry><structfield>count</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of times the SQL statement is executed</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The <structname>pg_stat_sql</structname> view will contain statistics
+ about number of SQL statements that are executed on the instance.
+ The <xref linkend="guc-track-sql"> parameter controls the SQL statement
+ execution statistics.
+ </para>
+
+
</sect2>
<sect2 id="monitoring-stats-functions">
@@ -2486,6 +2527,8 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
counters shown in the <structname>pg_stat_bgwriter</> view.
Calling <literal>pg_stat_reset_shared('archiver')</> will zero all the
counters shown in the <structname>pg_stat_archiver</> view.
+ Calling <literal>pg_stat_reset_shared('sqlstmt')</> will zero all the
+ counters shown in the <structname>pg_stat_sql</> view.
</entry>
</row>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ada2142..0a177b4 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -842,6 +842,11 @@ CREATE VIEW pg_replication_origin_status AS
REVOKE ALL ON pg_replication_origin_status FROM public;
+CREATE VIEW pg_stat_sql AS
+ SELECT * FROM pg_stat_sql();
+
+REVOKE EXECUTE ON FUNCTION pg_stat_sql() FROM public;
+
--
-- We have a few function definitions in here, too.
-- At some point there might be enough to justify breaking them out into
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 5112d6d..bdf5057 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -100,6 +100,7 @@
#define PGSTAT_DB_HASH_SIZE 16
#define PGSTAT_TAB_HASH_SIZE 512
#define PGSTAT_FUNCTION_HASH_SIZE 512
+#define PGSTAT_SQLSTMT_HASH_SIZE 256
/* ----------
@@ -108,6 +109,7 @@
*/
bool pgstat_track_activities = false;
bool pgstat_track_counts = false;
+bool pgstat_track_sql = false;
int pgstat_track_functions = TRACK_FUNC_OFF;
int pgstat_track_activity_query_size = 1024;
@@ -126,6 +128,19 @@ char *pgstat_stat_tmpname = NULL;
*/
PgStat_MsgBgWriter BgWriterStats;
+/*
+ * Backend specific SQL statement information stored in this hash table,
+ * before sent it to the stats collector.
+ */
+static HTAB *pgstat_backend_sql = NULL;
+
+/*
+ * Global SQL statement stats information gathered in the collector are stored in
+ * this hash table.
+ */
+HTAB *pgstat_sql = NULL;
+
+
/* ----------
* Local data
* ----------
@@ -270,6 +285,7 @@ static bool pgstat_db_requested(Oid databaseid);
static void pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg);
static void pgstat_send_funcstats(void);
+static void pgstat_send_sqlstmt(void);
static HTAB *pgstat_collect_oids(Oid catalogid);
static PgStat_TableStatus *get_tabstat_entry(Oid rel_id, bool isshared);
@@ -301,6 +317,7 @@ static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len);
static void pgstat_recv_recoveryconflict(PgStat_MsgRecoveryConflict *msg, int len);
static void pgstat_recv_deadlock(PgStat_MsgDeadlock *msg, int len);
static void pgstat_recv_tempfile(PgStat_MsgTempFile *msg, int len);
+static void pgstat_recv_sqlstmt(PgStat_MsgSqlstmt *msg, int len);
/* ------------------------------------------------------------
* Public functions called from postmaster follow
@@ -834,6 +851,10 @@ pgstat_report_stat(bool force)
/* Now, send function statistics */
pgstat_send_funcstats();
+
+ /* Now, send sql statistics */
+ if (pgstat_track_sql)
+ pgstat_send_sqlstmt();
}
/*
@@ -1260,11 +1281,13 @@ pgstat_reset_shared_counters(const char *target)
msg.m_resettarget = RESET_ARCHIVER;
else if (strcmp(target, "bgwriter") == 0)
msg.m_resettarget = RESET_BGWRITER;
+ else if (strcmp(target, "sqlstmt") == 0)
+ msg.m_resettarget = RESET_SQLSTMT;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized reset target: \"%s\"", target),
- errhint("Target must be \"archiver\" or \"bgwriter\".")));
+ errhint("Target must be \"archiver\" or \"bgwriter\" or \"sqlstmt\".")));
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSHAREDCOUNTER);
pgstat_send(&msg, sizeof(msg));
@@ -3682,6 +3705,89 @@ pgstat_send_bgwriter(void)
MemSet(&BgWriterStats, 0, sizeof(BgWriterStats));
}
+/* ----------
+ * pgstat_send_sqlstmt(void)
+ *
+ * Send SQL statement statistics to the collector
+ * ----------
+ */
+static void
+pgstat_send_sqlstmt(void)
+{
+ PgStat_MsgSqlstmt msg;
+ PgStat_SqlstmtEntry *entry;
+ HASH_SEQ_STATUS hstat;
+
+ if (pgstat_backend_sql == NULL)
+ return;
+
+ pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_SQLSTMT);
+ msg.m_nentries = 0;
+
+ hash_seq_init(&hstat, pgstat_backend_sql);
+ while ((entry = (PgStat_SqlstmtEntry *) hash_seq_search(&hstat)) != NULL)
+ {
+ PgStat_SqlstmtEntry *m_ent;
+
+ /* Skip it if no counts accumulated since last time */
+ if (entry->count == 0)
+ continue;
+
+ /* need to convert format of time accumulators */
+ m_ent = &msg.m_entry[msg.m_nentries];
+ memcpy(m_ent, entry, sizeof(PgStat_SqlstmtEntry));
+
+ if (++msg.m_nentries >= PGSTAT_NUM_SQLSTMTS)
+ {
+ pgstat_send(&msg, offsetof(PgStat_MsgSqlstmt, m_entry[0]) +
+ msg.m_nentries * sizeof(PgStat_SqlstmtEntry));
+ msg.m_nentries = 0;
+ }
+
+ /* reset the entry's count */
+ entry->count = 0;
+ }
+
+ if (msg.m_nentries > 0)
+ pgstat_send(&msg, offsetof(PgStat_MsgSqlstmt, m_entry[0]) +
+ msg.m_nentries * sizeof(PgStat_SqlstmtEntry));
+}
+
+/*
+ * Count SQL statement for pg_stat_sql view
+ */
+void
+pgstat_count_sqlstmt(const char *commandTag)
+{
+ PgStat_SqlstmtEntry *htabent;
+ bool found;
+
+ if (!pgstat_track_sql)
+ return;
+
+ if (!pgstat_backend_sql)
+ {
+ /* First time through - initialize SQL statement stat table */
+ HASHCTL hash_ctl;
+
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = NAMEDATALEN;
+ hash_ctl.entrysize = sizeof(PgStat_SqlstmtEntry);
+ pgstat_backend_sql = hash_create("SQL statement stat entries",
+ PGSTAT_SQLSTMT_HASH_SIZE,
+ &hash_ctl,
+ HASH_ELEM | HASH_BLOBS);
+ }
+
+ /* Get the stats entry for this SQL statement, create if necessary */
+ htabent = hash_search(pgstat_backend_sql, commandTag,
+ HASH_ENTER, &found);
+ if (!found)
+ htabent->count = 1;
+ else
+ htabent->count++;
+}
+
/* ----------
* PgstatCollectorMain() -
@@ -3898,6 +4004,9 @@ PgstatCollectorMain(int argc, char *argv[])
pgstat_recv_tempfile((PgStat_MsgTempFile *) &msg, len);
break;
+ case PGSTAT_MTYPE_SQLSTMT:
+ pgstat_recv_sqlstmt((PgStat_MsgSqlstmt *) &msg, len);
+ break;
default:
break;
}
@@ -4118,6 +4227,7 @@ pgstat_write_statsfiles(bool permanent, bool allDbs)
{
HASH_SEQ_STATUS hstat;
PgStat_StatDBEntry *dbentry;
+ PgStat_SqlstmtEntry *sqlstmtentry;
FILE *fpout;
int32 format_id;
const char *tmpfile = permanent ? PGSTAT_STAT_PERMANENT_TMPFILE : pgstat_stat_tmpname;
@@ -4190,6 +4300,15 @@ pgstat_write_statsfiles(bool permanent, bool allDbs)
(void) rc; /* we'll check for error with ferror */
}
+ /* Walk through the SQL statement Stats Hash table. */
+ hash_seq_init(&hstat, pgstat_sql);
+ while ((sqlstmtentry = (PgStat_SqlstmtEntry *) hash_seq_search(&hstat)) != NULL)
+ {
+ fputc('S', fpout);
+ rc = fwrite(sqlstmtentry, sizeof(PgStat_SqlstmtEntry), 1, fpout);
+ (void) rc; /* we'll check for error with ferror */
+ }
+
/*
* No more output to be done. Close the temp file and replace the old
* pgstat.stat with it. The ferror() check replaces testing for error
@@ -4414,6 +4533,14 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep)
dbhash = hash_create("Databases hash", PGSTAT_DB_HASH_SIZE, &hash_ctl,
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+ /* Create SQL statement counter Stats HashTable */
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = NAMEDATALEN;
+ hash_ctl.entrysize = sizeof(PgStat_SqlstmtEntry);
+ pgstat_sql = hash_create("SQL statement counter",
+ PGSTAT_SQLSTMT_HASH_SIZE, &hash_ctl,
+ HASH_ELEM);
+
/*
* Clear out global and archiver statistics so they start from zero in
* case we can't load an existing statsfile.
@@ -4559,6 +4686,38 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep)
break;
+ case 'S':
+ {
+ PgStat_SqlstmtEntry stats;
+ PgStat_SqlstmtEntry *lstats;
+
+ if (fread(&stats, 1, sizeof(PgStat_SqlstmtEntry), fpin) != sizeof(PgStat_SqlstmtEntry))
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"",
+ statfile)));
+ goto done;
+ }
+
+ /*
+ * Add to the SQL statement counter hash
+ */
+ lstats = (PgStat_SqlstmtEntry *) hash_search(pgstat_sql,
+ (void *) &(stats.tag),
+ HASH_ENTER,
+ &found);
+ if (found)
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"",
+ statfile)));
+ goto done;
+ }
+
+ memcpy(lstats, &stats, sizeof(PgStat_SqlstmtEntry));
+
+ break;
+ }
case 'E':
goto done;
@@ -4858,6 +5017,21 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool permanent,
break;
+ case 'S':
+ {
+ PgStat_SqlstmtEntry stats;
+
+ if (fread(&stats, 1, sizeof(PgStat_SqlstmtEntry), fpin) != sizeof(PgStat_SqlstmtEntry))
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"",
+ statfile)));
+ goto done;
+ }
+
+ break;
+ }
+
case 'E':
goto done;
@@ -5383,6 +5557,21 @@ pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter *msg, int len)
memset(&archiverStats, 0, sizeof(archiverStats));
archiverStats.stat_reset_timestamp = GetCurrentTimestamp();
}
+ else if (msg->m_resettarget == RESET_SQLSTMT)
+ {
+ HASHCTL hash_ctl;
+
+ /* Destroy the SQL statement counter stats HashTable */
+ hash_destroy(pgstat_sql);
+
+ /* Create SQL statement counter Stats HashTable */
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = NAMEDATALEN;
+ hash_ctl.entrysize = sizeof(PgStat_SqlstmtEntry);
+ pgstat_sql = hash_create("SQL statement counter",
+ PGSTAT_SQLSTMT_HASH_SIZE, &hash_ctl,
+ HASH_ELEM);
+ }
/*
* Presumably the sender of this message validated the target, don't
@@ -5563,6 +5752,37 @@ pgstat_recv_bgwriter(PgStat_MsgBgWriter *msg, int len)
}
/* ----------
+ * pgstat_recv_sqlstmt() -
+ *
+ * Process a SQL statement statistics message.
+ * ----------
+ */
+static void
+pgstat_recv_sqlstmt(PgStat_MsgSqlstmt *msg, int len)
+{
+ PgStat_SqlstmtEntry *entry;
+ bool found;
+ int i;
+
+ for (i = 0; i < msg->m_nentries; i++)
+ {
+ /* Get the stats entry for this SQL statement, create if necessary */
+ entry = (PgStat_SqlstmtEntry *) hash_search(pgstat_sql, (void *) &(msg->m_entry[i].tag),
+ HASH_ENTER, &found);
+
+ if (found)
+ {
+ entry->count += msg->m_entry[i].count;
+ }
+ else
+ {
+ memcpy(entry->tag, msg->m_entry[i].tag, NAMEDATALEN);
+ entry->count = msg->m_entry[i].count;
+ }
+ }
+}
+
+/* ----------
* pgstat_recv_recoveryconflict() -
*
* Process a RECOVERYCONFLICT message.
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 2347f1b..4a168ca 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1102,6 +1102,12 @@ exec_simple_query(const char *query_string)
PortalDrop(portal, false);
+ /*
+ * Count SQL statement for pg_stat_sql view
+ */
+ if (pgstat_track_sql)
+ pgstat_count_sqlstmt(commandTag);
+
if (IsA(parsetree, TransactionStmt))
{
/*
@@ -1983,6 +1989,29 @@ exec_execute_message(const char *portal_name, long max_rows)
(*receiver->rDestroy) (receiver);
+ /*
+ * Count SQL Statement for pgx_stat_sql
+ */
+ if (pgstat_track_sql)
+ {
+ CachedPlanSource *psrc = NULL;
+
+ if (portal->prepStmtName)
+ {
+ PreparedStatement *pstmt;
+
+ pstmt = FetchPreparedStatement(portal->prepStmtName, false);
+ if (pstmt)
+ psrc = pstmt->plansource;
+ }
+ else
+ psrc = unnamed_stmt_psrc;
+
+ /* psrc should not be NULL here */
+ if (psrc && psrc->commandTag && !execute_is_fetch)
+ pgstat_count_sqlstmt(psrc->commandTag);
+ }
+
if (completed)
{
if (is_xact_command)
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 2d3cf9e..a1d2415 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -95,6 +95,7 @@ extern Datum pg_stat_get_db_blk_read_time(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_db_blk_write_time(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_archiver(PG_FUNCTION_ARGS);
+extern Datum pg_stat_sql(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_bgwriter_timed_checkpoints(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_bgwriter_requested_checkpoints(PG_FUNCTION_ARGS);
@@ -1931,3 +1932,77 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(HeapTupleGetDatum(
heap_form_tuple(tupdesc, values, nulls)));
}
+
+Datum
+pg_stat_sql(PG_FUNCTION_ARGS)
+{
+ TupleDesc tupdesc;
+ Datum values[2];
+ bool nulls[2];
+ ReturnSetInfo *rsi;
+ MemoryContext old_cxt;
+ Tuplestorestate *tuple_store;
+ PgStat_SqlstmtEntry *sqlstmtentry;
+ HASH_SEQ_STATUS hstat;
+
+ /* Function call to let the backend read the stats file */
+ pgstat_fetch_global();
+
+ /* Initialize values and nulls arrays */
+ MemSet(values, 0, sizeof(values));
+ MemSet(nulls, 0, sizeof(nulls));
+
+ /*
+ * We must use the Materialize mode to be more efficient than checking the
+ * HASH every time.
+ */
+ rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ (rsi->allowedModes & SFRM_Materialize) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ rsi->returnMode = SFRM_Materialize;
+
+ /*
+ * Create the tupledesc and tuplestore in the per_query context as
+ * required for SFRM_Materialize.
+ */
+ old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+ /* Initialize attributes information in the tuple descriptor */
+ tupdesc = CreateTemplateTupleDesc(2, false);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "tag",
+ TEXTOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 2, "count",
+ INT8OID, -1, 0);
+
+ BlessTupleDesc(tupdesc);
+
+ tuple_store =
+ tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ false, work_mem);
+
+ MemoryContextSwitchTo(old_cxt);
+
+ /* Walk through the SQL statement Stats Hash table. */
+ hash_seq_init(&hstat, pgstat_sql);
+ while ((sqlstmtentry = (PgStat_SqlstmtEntry *) hash_seq_search(&hstat)) != NULL)
+ {
+ HeapTuple tuple;
+
+ /* Fill values and NULLs */
+ values[0] = CStringGetTextDatum(sqlstmtentry->tag);
+ values[1] = Int64GetDatum(sqlstmtentry->count);
+
+ tuple = heap_form_tuple(tupdesc, values, nulls);
+ tuplestore_puttuple(tuple_store, tuple);
+ }
+
+ rsi->setDesc = tupdesc;
+ rsi->setResult = tuple_store;
+
+ PG_RETURN_NULL();
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 622279b..8eb6c85 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1222,6 +1222,16 @@ static struct config_bool ConfigureNamesBool[] =
},
{
+ {"track_sql", PGC_SUSET, STATS_COLLECTOR,
+ gettext_noop("Collects timing statistics for database I/O activity."),
+ NULL
+ },
+ &pgstat_track_sql,
+ false,
+ NULL, NULL, NULL
+ },
+
+ {
{"update_process_title", PGC_SUSET, PROCESS_TITLE,
gettext_noop("Updates the process title to show the active SQL command."),
gettext_noop("Enables updating of the process title every time a new SQL command is received by the server.")
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 05b1373..4b720a6 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -475,6 +475,7 @@
#track_activities = on
#track_counts = on
+#track_sql = off
#track_io_timing = off
#track_functions = none # none, pl, all
#track_activity_query_size = 1024 # (change requires restart)
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 17ec71d..4d31fd6 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2893,6 +2893,9 @@ DESCR("statistics: total execution time of function in current transaction, in m
DATA(insert OID = 3048 ( pg_stat_get_xact_function_self_time PGNSP PGUID 12 1 0 0 0 f f f f t f v r 1 0 701 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_xact_function_self_time _null_ _null_ _null_ ));
DESCR("statistics: self execution time of function in current transaction, in msec");
+DATA(insert OID = 3343 ( pg_stat_sql PGNSP PGUID 12 1 1000 0 0 f f f f t t v s 0 0 2249 "" "{25,20}" "{o,o}" "{tag,count}" _null_ _null_ pg_stat_sql _null_ _null_ _null_ ));
+DESCR("statistics: Show SQL statement statistics");
+
DATA(insert OID = 3788 ( pg_stat_get_snapshot_timestamp PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 1184 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_snapshot_timestamp _null_ _null_ _null_ ));
DESCR("statistics: timestamp of the current statistics snapshot");
DATA(insert OID = 2230 ( pg_stat_clear_snapshot PGNSP PGUID 12 1 0 0 0 f f f f f f v r 0 0 2278 "" _null_ _null_ _null_ _null_ _null_ pg_stat_clear_snapshot _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 1c9bf13..ceaa4ba 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -64,7 +64,8 @@ typedef enum StatMsgType
PGSTAT_MTYPE_FUNCPURGE,
PGSTAT_MTYPE_RECOVERYCONFLICT,
PGSTAT_MTYPE_TEMPFILE,
- PGSTAT_MTYPE_DEADLOCK
+ PGSTAT_MTYPE_DEADLOCK,
+ PGSTAT_MTYPE_SQLSTMT
} StatMsgType;
/* ----------
@@ -119,7 +120,8 @@ typedef struct PgStat_TableCounts
typedef enum PgStat_Shared_Reset_Target
{
RESET_ARCHIVER,
- RESET_BGWRITER
+ RESET_BGWRITER,
+ RESET_SQLSTMT
} PgStat_Shared_Reset_Target;
/* Possible object types for resetting single counters */
@@ -530,6 +532,30 @@ typedef struct PgStat_MsgDeadlock
Oid m_databaseid;
} PgStat_MsgDeadlock;
+/* ----------
+ * PgStatSqlstmt
+ * ----------
+ */
+typedef struct PgStat_SqlstmtEntry
+{
+ char tag[NAMEDATALEN];
+ PgStat_Counter count;
+} PgStat_SqlstmtEntry;
+
+
+/* ----------
+ * PgStatMsgSqlstmt Sent by the sql to update statistics.
+ * ----------
+ */
+#define PGSTAT_NUM_SQLSTMTS \
+ ((PGSTAT_MSG_PAYLOAD - sizeof(int)) / sizeof(PgStat_SqlstmtEntry))
+
+typedef struct PgStat_MsgSqlstmt
+{
+ PgStat_MsgHdr m_hdr;
+ int m_nentries;
+ PgStat_SqlstmtEntry m_entry[PGSTAT_NUM_SQLSTMTS];
+} PgStat_MsgSqlstmt;
/* ----------
* PgStat_Msg Union over all possible messages.
@@ -555,6 +581,7 @@ typedef union PgStat_Msg
PgStat_MsgFuncpurge msg_funcpurge;
PgStat_MsgRecoveryConflict msg_recoveryconflict;
PgStat_MsgDeadlock msg_deadlock;
+ PgStat_MsgSqlstmt msg_sqlstmt;
} PgStat_Msg;
@@ -566,7 +593,7 @@ typedef union PgStat_Msg
* ------------------------------------------------------------
*/
-#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9D
+#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9E
/* ----------
* PgStat_StatDBEntry The collector's data per database
@@ -994,6 +1021,7 @@ typedef struct PgStat_FunctionCallUsage
*/
extern bool pgstat_track_activities;
extern bool pgstat_track_counts;
+extern bool pgstat_track_sql;
extern int pgstat_track_functions;
extern PGDLLIMPORT int pgstat_track_activity_query_size;
extern char *pgstat_stat_directory;
@@ -1006,6 +1034,11 @@ extern char *pgstat_stat_filename;
extern PgStat_MsgBgWriter BgWriterStats;
/*
+ * SQL statement statistics counter Hash table
+ */
+extern HTAB *pgstat_sql;
+
+/*
* Updated by pgstat_count_buffer_*_time macros
*/
extern PgStat_Counter pgStatBlockReadTime;
@@ -1199,6 +1232,8 @@ extern void pgstat_twophase_postabort(TransactionId xid, uint16 info,
extern void pgstat_send_archiver(const char *xlog, bool failed);
extern void pgstat_send_bgwriter(void);
+extern void pgstat_count_sqlstmt(const char *commandTag);
+
/* ----------
* Support functions for the SQL-callable functions to
* generate the pgstat* views.
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 00700f2..55934ca 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1795,6 +1795,9 @@ pg_stat_replication| SELECT s.pid,
FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, ssl, sslversion, sslcipher, sslbits, sslcompression, sslclientdn)
JOIN pg_stat_get_wal_senders() w(pid, state, sent_location, write_location, flush_location, replay_location, sync_priority, sync_state) ON ((s.pid = w.pid)))
LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
+pg_stat_sql| SELECT pg_stat_sql.tag,
+ pg_stat_sql.count
+ FROM pg_stat_sql() pg_stat_sql(tag, count);
pg_stat_ssl| SELECT s.pid,
s.ssl,
s.sslversion AS version,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index a811265..be6db13 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -4,6 +4,62 @@
-- Must be run after tenk2 has been created (by create_table),
-- populated (by create_misc) and indexed (by create_index).
--
+-- pg_stat_sql
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+-----+-------
+(0 rows)
+
+SET track_sql TO ON;
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+--------+-------
+ SELECT | 1
+(1 row)
+
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+-----+-------
+(0 rows)
+
-- conditio sine qua non
SHOW track_counts; -- must be on
track_counts
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index b3e2efa..34716d6 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -5,6 +5,30 @@
-- populated (by create_misc) and indexed (by create_index).
--
+-- pg_stat_sql
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
+SET track_sql TO ON;
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
-- conditio sine qua non
SHOW track_counts; -- must be on
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 6c6d519..7c2bcc1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1517,6 +1517,7 @@ PgStat_MsgRecoveryConflict
PgStat_MsgResetcounter
PgStat_MsgResetsharedcounter
PgStat_MsgResetsinglecounter
+PgStat_MsgSqlstmt
PgStat_MsgTabpurge
PgStat_MsgTabstat
PgStat_MsgTempFile
On Thu, Sep 29, 2016 at 1:45 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:
Currently, The SQL stats is a fixed size counter to track the all the ALTER
cases as single counter. So while sending the stats from the backend to
stats collector at the end of the transaction, the cost is same, because of
it's fixed size. This approach adds overhead to send and read the stats
is minimal.With the following approach, I feel it is possible to support the counter at
command tag level.Add a Global and local Hash to keep track of the counters by using the
command tag as the key, this hash table increases dynamically whenever
a new type of SQL command gets executed. The Local Hash data is passed
to stats collector whenever the transaction gets committed.The problem I am thinking is that, Sending data from Hash and populating
the Hash from stats file for all the command tags adds some overhead.
Yeah, I'm not very excited about that overhead. This seems useful as
far as it goes, but I don't really want to incur measurable overhead
when it's in use. Having a hash table rather than a fixed array of
slots means that you have to pass this through the stats collector
rather than updating shared memory directly, which is fairly heavy
weight. If each backend could have its own copy of the slot array and
just update that, and readers added up the values across the whole
array, this could be done without any locking at all, and it would
generally be much lighter-weight than this approach.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Oct 19, 2016 at 5:11 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Thu, Sep 29, 2016 at 1:45 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:Currently, The SQL stats is a fixed size counter to track the all the
ALTER
cases as single counter. So while sending the stats from the backend to
stats collector at the end of the transaction, the cost is same, becauseof
it's fixed size. This approach adds overhead to send and read the stats
is minimal.With the following approach, I feel it is possible to support the
counter at
command tag level.
Add a Global and local Hash to keep track of the counters by using the
command tag as the key, this hash table increases dynamically whenever
a new type of SQL command gets executed. The Local Hash data is passed
to stats collector whenever the transaction gets committed.The problem I am thinking is that, Sending data from Hash and populating
the Hash from stats file for all the command tags adds some overhead.Yeah, I'm not very excited about that overhead. This seems useful as
far as it goes, but I don't really want to incur measurable overhead
when it's in use. Having a hash table rather than a fixed array of
slots means that you have to pass this through the stats collector
rather than updating shared memory directly, which is fairly heavy
weight. If each backend could have its own copy of the slot array and
just update that, and readers added up the values across the whole
array, this could be done without any locking at all, and it would
generally be much lighter-weight than this approach.
Using limited information like combining all ALTER XXX into Alter counter,
the fixed array logic works fine without having any problems. But people
are suggesting to provide more details like ALTER VIEW and etc, so I
checked the following ways,
1. Using of nodetag as an array of index to update the counter. But this
approach doesn't work for some cases like T_DropStmt where the tag
varies based on the removeType of the DropStmt. So I decide to drop
this approach.
2. Using of tag name for searching the fixed size array that is sorted with
all command tags that are possible in PostgreSQL. Using a binary search,
find out the location and update the counter. In this approach, first the
array
needs to be filed with TAG information in the sorted order.
3. Using of Hash table to store the counters information based on the TAG
name as key.
I choose the approach-3 as it can scale for any additional commands.
Any better alternatives with that directly it can point to the array index?
if we are going with ALTER XXX into single Alter counter is approach, then
I will change the code with a fixed array approach.
Regards,
Hari Babu
Fujitsu Australia
Hi Vinayak,
This is a gentle reminder.
you assigned as reviewer to the current patch in the 11-2016 commitfest.
If you have any more review comments / performance results regarding the
approach of the patch, please share it. Otherwise, you can mark the patch
as "Ready for committer" for committer feedback. This will help us in
smoother
operation of the commitfest.
Regards,
Hari Babu
Fujitsu Australia
On Tue, Nov 22, 2016 at 5:57 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:
Hi Vinayak,
This is a gentle reminder.
you assigned as reviewer to the current patch in the 11-2016 commitfest.
If you have any more review comments / performance results regarding the
approach of the patch, please share it. Otherwise, you can mark the patch
as "Ready for committer" for committer feedback. This will help us in
smoother
operation of the commitfest.
Moved to next CF with "needs review" status.
Regards,
Hari Babu
Fujitsu Australia
On Mon, Dec 5, 2016 at 8:01 AM, Haribabu Kommi <kommi.haribabu@gmail.com> wrote:
Moved to next CF with "needs review" status.
I have started reviewing the patch, Some initial comments.
$ git apply ../pg_stat_sql_row_mode_2.patch
error: patch failed: src/include/catalog/pg_proc.h:2893
error: src/include/catalog/pg_proc.h: patch does not apply
Patch is not applying on the head, I think it needs to be rebased.
+void
+pgstat_count_sqlstmt(const char *commandTag)
+{
+ PgStat_SqlstmtEntry *htabent;
+ bool found;
+
+ if (!pgstat_track_sql)
+ return
Callers of pgstat_count_sqlstmt are always ensuring that
pgstat_track_sql is true, seems it's redundant here.
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jan 11, 2017 at 9:17 AM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
On Mon, Dec 5, 2016 at 8:01 AM, Haribabu Kommi <kommi.haribabu@gmail.com> wrote:
Moved to next CF with "needs review" status.
I have started reviewing the patch, Some initial comments.
$ git apply ../pg_stat_sql_row_mode_2.patch
error: patch failed: src/include/catalog/pg_proc.h:2893
error: src/include/catalog/pg_proc.h: patch does not apply
I suggest using 'patch' directly. 'git apply' is far too picky.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jan 11, 2017 at 7:47 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
+void +pgstat_count_sqlstmt(const char *commandTag) +{ + PgStat_SqlstmtEntry *htabent; + bool found; + + if (!pgstat_track_sql) + returnCallers of pgstat_count_sqlstmt are always ensuring that
pgstat_track_sql is true, seems it's redundant here.
I have done testing of the feature, it's mostly working as per the expectation.
I have few comments/questions.
--------
If you execute the query with EXECUTE then commandTag will be EXECUTE
that way we will not show the actual query type, I mean all the
statements will get the common tag "EXECUTE".
You can try this.
PREPARE fooplan (int) AS INSERT INTO t VALUES($1);
EXECUTE fooplan(1);
------
+ /* Destroy the SQL statement counter stats HashTable */
+ hash_destroy(pgstat_sql);
+
+ /* Create SQL statement counter Stats HashTable */
I think in the patch we have used mixed of "Stats HashTable" and
"stats HashTable", I think better
to be consistent throughout the patch. Check other similar instances.
----------------
@@ -1102,6 +1102,12 @@ exec_simple_query(const char *query_string)
PortalDrop(portal, false);
+ /*
+ * Count SQL statement for pg_stat_sql view
+ */
+ if (pgstat_track_sql)
+ pgstat_count_sqlstmt(commandTag);
We are only counting the successful SQL statement, Is that intentional?
------
I have noticed that pgstat_count_sqlstmt is called from
exec_simple_query and exec_execute_message. Don't we want to track the
statement executed from functions?
-------
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Jan 14, 2017 at 12:58 AM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
On Wed, Jan 11, 2017 at 7:47 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
+void +pgstat_count_sqlstmt(const char *commandTag) +{ + PgStat_SqlstmtEntry *htabent; + bool found; + + if (!pgstat_track_sql) + returnCallers of pgstat_count_sqlstmt are always ensuring that
pgstat_track_sql is true, seems it's redundant here.I have done testing of the feature, it's mostly working as per the expectation.
I have few comments/questions.
--------
If you execute the query with EXECUTE then commandTag will be EXECUTE
that way we will not show the actual query type, I mean all the
statements will get the common tag "EXECUTE".You can try this.
PREPARE fooplan (int) AS INSERT INTO t VALUES($1);
EXECUTE fooplan(1);------
+ /* Destroy the SQL statement counter stats HashTable */ + hash_destroy(pgstat_sql); + + /* Create SQL statement counter Stats HashTable */I think in the patch we have used mixed of "Stats HashTable" and
"stats HashTable", I think better
to be consistent throughout the patch. Check other similar instances.----------------
@@ -1102,6 +1102,12 @@ exec_simple_query(const char *query_string)
PortalDrop(portal, false);
+ /* + * Count SQL statement for pg_stat_sql view + */ + if (pgstat_track_sql) + pgstat_count_sqlstmt(commandTag);We are only counting the successful SQL statement, Is that intentional?
------
I have noticed that pgstat_count_sqlstmt is called from
exec_simple_query and exec_execute_message. Don't we want to track the
statement executed from functions?
-------
The latest patch available has rotten, could you rebase please?
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jan 13, 2017 at 10:58 AM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
If you execute the query with EXECUTE then commandTag will be EXECUTE
that way we will not show the actual query type, I mean all the
statements will get the common tag "EXECUTE".
Somebody might say that's a feature, not a bug. But then again,
someone else might say the opposite.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Jan 14, 2017 at 2:58 AM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
On Wed, Jan 11, 2017 at 7:47 PM, Dilip Kumar <dilipbalaut@gmail.com>
wrote:+void +pgstat_count_sqlstmt(const char *commandTag) +{ + PgStat_SqlstmtEntry *htabent; + bool found; + + if (!pgstat_track_sql) + returnCallers of pgstat_count_sqlstmt are always ensuring that
pgstat_track_sql is true, seems it's redundant here.I have done testing of the feature, it's mostly working as per the
expectation.
Thanks for the review and test.
The use case for this view is to find out the number of different SQL
statements
that are getting executed successfully on an instance by the
application/user.
I have few comments/questions.
--------
If you execute the query with EXECUTE then commandTag will be EXECUTE
that way we will not show the actual query type, I mean all the
statements will get the common tag "EXECUTE".You can try this.
PREPARE fooplan (int) AS INSERT INTO t VALUES($1);
EXECUTE fooplan(1);------
Yes, that's correct. Currently the count is incremented based on the base
command,
because of this reason, the EXECUTE is increased, instead of the actual
operation.
+ /* Destroy the SQL statement counter stats HashTable */ + hash_destroy(pgstat_sql); + + /* Create SQL statement counter Stats HashTable */I think in the patch we have used mixed of "Stats HashTable" and
"stats HashTable", I think better
to be consistent throughout the patch. Check other similar instances.----------------
Corrected.
@@ -1102,6 +1102,12 @@ exec_simple_query(const char *query_string)
PortalDrop(portal, false);
+ /* + * Count SQL statement for pg_stat_sql view + */ + if (pgstat_track_sql) + pgstat_count_sqlstmt(commandTag);We are only counting the successful SQL statement, Is that intentional?
Yes, I thought of counting the successful SQL operations that changed the
database over a period of time. I am not sure whether there will be many
failure operations that can occur on an instance to be counted.
------
I have noticed that pgstat_count_sqlstmt is called from
exec_simple_query and exec_execute_message. Don't we want to track the
statement executed from functions?
-------
The view is currently designed to count user/application initiated SQL
statements,
but not the internal SQL queries that are getting executed from functions
and etc.
+void +pgstat_count_sqlstmt(const char *commandTag) +{ + PgStat_SqlstmtEntry *htabent; + bool found; + + if (!pgstat_track_sql) + returnCallers of pgstat_count_sqlstmt are always ensuring that
pgstat_track_sql is true, seems it's redundant here.
Removed the check in pgstat_count_sqlstmt statement and add it
exec_execute_message
function where the check is missed.
Updated patch attached.
Regards,
Hari Babu
Fujitsu Australia
Attachments:
pg_stat_sql_row_mode_4.patchapplication/octet-stream; name=pg_stat_sql_row_mode_4.patchDownload
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 07afa3c..095495a 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5638,6 +5638,21 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
</listitem>
</varlistentry>
+ <varlistentry id="guc-track-sql" xreflabel="track_sql">
+ <term><varname>track_sql</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>track_sql</> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Enables collection of different SQL statement statistics that are
+ executed on the instance. This parameter is off by default. Only
+ superusers can change this setting.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-stats-temp-directory" xreflabel="stats_temp_directory">
<term><varname>stats_temp_directory</varname> (<type>string</type>)
<indexterm>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 1545f03..7e38afd 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -513,6 +513,13 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
<command>VACUUM</>, showing current progress.
See <xref linkend='vacuum-progress-reporting'>.</entry>
</row>
+
+ <row>
+ <entry><structname>pg_stat_sql</><indexterm><primary>pg_stat_sql</primary></indexterm></entry>
+ <entry>Shows statistics about the SQL statements that are
+ executed on the instance.
+ See <xref linkend="pg-stat-sql-view"> for details.</entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -2443,6 +2450,40 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
controls exactly which functions are tracked.
</para>
+ <table id="pg-stat-sql-view" xreflabel="pg_stat_sql">
+ <title><structname>pg_stat_sql</structname> View</title>
+ <tgroup cols="3">
+ <thead>
+ <row>
+ <entry>Column</entry>
+ <entry>Type</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry><structfield>tag</></entry>
+ <entry><type>text</></entry>
+ <entry>Name of the SQL statement</entry>
+ </row>
+ <row>
+ <entry><structfield>count</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of times the SQL statement is executed</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The <structname>pg_stat_sql</structname> view will contain statistics
+ about number of SQL statements that are executed on the instance.
+ The <xref linkend="guc-track-sql"> parameter controls the SQL statement
+ execution statistics.
+ </para>
+
+
</sect2>
<sect2 id="monitoring-stats-functions">
@@ -2538,6 +2579,8 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
counters shown in the <structname>pg_stat_bgwriter</> view.
Calling <literal>pg_stat_reset_shared('archiver')</> will zero all the
counters shown in the <structname>pg_stat_archiver</> view.
+ Calling <literal>pg_stat_reset_shared('sqlstmt')</> will zero all the
+ counters shown in the <structname>pg_stat_sql</> view.
</entry>
</row>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 31aade1..8d27a41 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -866,6 +866,11 @@ CREATE VIEW pg_replication_origin_status AS
REVOKE ALL ON pg_replication_origin_status FROM public;
+CREATE VIEW pg_stat_sql AS
+ SELECT * FROM pg_stat_sql();
+
+REVOKE EXECUTE ON FUNCTION pg_stat_sql() FROM public;
+
--
-- We have a few function definitions in here, too.
-- At some point there might be enough to justify breaking them out into
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index f37a0bf..63010e6 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -100,6 +100,7 @@
#define PGSTAT_DB_HASH_SIZE 16
#define PGSTAT_TAB_HASH_SIZE 512
#define PGSTAT_FUNCTION_HASH_SIZE 512
+#define PGSTAT_SQLSTMT_HASH_SIZE 256
/* ----------
@@ -108,6 +109,7 @@
*/
bool pgstat_track_activities = false;
bool pgstat_track_counts = false;
+bool pgstat_track_sql = false;
int pgstat_track_functions = TRACK_FUNC_OFF;
int pgstat_track_activity_query_size = 1024;
@@ -126,6 +128,19 @@ char *pgstat_stat_tmpname = NULL;
*/
PgStat_MsgBgWriter BgWriterStats;
+/*
+ * Backend specific SQL statement information stored in this hash table,
+ * before sent it to the stats collector.
+ */
+static HTAB *pgstat_backend_sql = NULL;
+
+/*
+ * Global SQL statement stats information gathered in the collector are stored in
+ * this hash table.
+ */
+HTAB *pgstat_sql = NULL;
+
+
/* ----------
* Local data
* ----------
@@ -270,6 +285,7 @@ static bool pgstat_db_requested(Oid databaseid);
static void pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg);
static void pgstat_send_funcstats(void);
+static void pgstat_send_sqlstmt(void);
static HTAB *pgstat_collect_oids(Oid catalogid);
static PgStat_TableStatus *get_tabstat_entry(Oid rel_id, bool isshared);
@@ -301,6 +317,7 @@ static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len);
static void pgstat_recv_recoveryconflict(PgStat_MsgRecoveryConflict *msg, int len);
static void pgstat_recv_deadlock(PgStat_MsgDeadlock *msg, int len);
static void pgstat_recv_tempfile(PgStat_MsgTempFile *msg, int len);
+static void pgstat_recv_sqlstmt(PgStat_MsgSqlstmt *msg, int len);
/* ------------------------------------------------------------
* Public functions called from postmaster follow
@@ -834,6 +851,10 @@ pgstat_report_stat(bool force)
/* Now, send function statistics */
pgstat_send_funcstats();
+
+ /* Now, send sql statistics */
+ if (pgstat_track_sql)
+ pgstat_send_sqlstmt();
}
/*
@@ -1260,11 +1281,13 @@ pgstat_reset_shared_counters(const char *target)
msg.m_resettarget = RESET_ARCHIVER;
else if (strcmp(target, "bgwriter") == 0)
msg.m_resettarget = RESET_BGWRITER;
+ else if (strcmp(target, "sqlstmt") == 0)
+ msg.m_resettarget = RESET_SQLSTMT;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized reset target: \"%s\"", target),
- errhint("Target must be \"archiver\" or \"bgwriter\".")));
+ errhint("Target must be \"archiver\" or \"bgwriter\" or \"sqlstmt\".")));
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSHAREDCOUNTER);
pgstat_send(&msg, sizeof(msg));
@@ -3681,6 +3704,86 @@ pgstat_send_bgwriter(void)
MemSet(&BgWriterStats, 0, sizeof(BgWriterStats));
}
+/* ----------
+ * pgstat_send_sqlstmt(void)
+ *
+ * Send SQL statement statistics to the collector
+ * ----------
+ */
+static void
+pgstat_send_sqlstmt(void)
+{
+ PgStat_MsgSqlstmt msg;
+ PgStat_SqlstmtEntry *entry;
+ HASH_SEQ_STATUS hstat;
+
+ if (pgstat_backend_sql == NULL)
+ return;
+
+ pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_SQLSTMT);
+ msg.m_nentries = 0;
+
+ hash_seq_init(&hstat, pgstat_backend_sql);
+ while ((entry = (PgStat_SqlstmtEntry *) hash_seq_search(&hstat)) != NULL)
+ {
+ PgStat_SqlstmtEntry *m_ent;
+
+ /* Skip it if no counts accumulated since last time */
+ if (entry->count == 0)
+ continue;
+
+ /* need to convert format of time accumulators */
+ m_ent = &msg.m_entry[msg.m_nentries];
+ memcpy(m_ent, entry, sizeof(PgStat_SqlstmtEntry));
+
+ if (++msg.m_nentries >= PGSTAT_NUM_SQLSTMTS)
+ {
+ pgstat_send(&msg, offsetof(PgStat_MsgSqlstmt, m_entry[0]) +
+ msg.m_nentries * sizeof(PgStat_SqlstmtEntry));
+ msg.m_nentries = 0;
+ }
+
+ /* reset the entry's count */
+ entry->count = 0;
+ }
+
+ if (msg.m_nentries > 0)
+ pgstat_send(&msg, offsetof(PgStat_MsgSqlstmt, m_entry[0]) +
+ msg.m_nentries * sizeof(PgStat_SqlstmtEntry));
+}
+
+/*
+ * Count SQL statement for pg_stat_sql view
+ */
+void
+pgstat_count_sqlstmt(const char *commandTag)
+{
+ PgStat_SqlstmtEntry *htabent;
+ bool found;
+
+ if (!pgstat_backend_sql)
+ {
+ /* First time through - initialize SQL statement stat table */
+ HASHCTL hash_ctl;
+
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = NAMEDATALEN;
+ hash_ctl.entrysize = sizeof(PgStat_SqlstmtEntry);
+ pgstat_backend_sql = hash_create("SQL statement stat entries",
+ PGSTAT_SQLSTMT_HASH_SIZE,
+ &hash_ctl,
+ HASH_ELEM | HASH_BLOBS);
+ }
+
+ /* Get the stats entry for this SQL statement, create if necessary */
+ htabent = hash_search(pgstat_backend_sql, commandTag,
+ HASH_ENTER, &found);
+ if (!found)
+ htabent->count = 1;
+ else
+ htabent->count++;
+}
+
/* ----------
* PgstatCollectorMain() -
@@ -3897,6 +4000,9 @@ PgstatCollectorMain(int argc, char *argv[])
pgstat_recv_tempfile((PgStat_MsgTempFile *) &msg, len);
break;
+ case PGSTAT_MTYPE_SQLSTMT:
+ pgstat_recv_sqlstmt((PgStat_MsgSqlstmt *) &msg, len);
+ break;
default:
break;
}
@@ -4117,6 +4223,7 @@ pgstat_write_statsfiles(bool permanent, bool allDbs)
{
HASH_SEQ_STATUS hstat;
PgStat_StatDBEntry *dbentry;
+ PgStat_SqlstmtEntry *sqlstmtentry;
FILE *fpout;
int32 format_id;
const char *tmpfile = permanent ? PGSTAT_STAT_PERMANENT_TMPFILE : pgstat_stat_tmpname;
@@ -4189,6 +4296,15 @@ pgstat_write_statsfiles(bool permanent, bool allDbs)
(void) rc; /* we'll check for error with ferror */
}
+ /* Walk through the SQL statement Stats Hash table. */
+ hash_seq_init(&hstat, pgstat_sql);
+ while ((sqlstmtentry = (PgStat_SqlstmtEntry *) hash_seq_search(&hstat)) != NULL)
+ {
+ fputc('S', fpout);
+ rc = fwrite(sqlstmtentry, sizeof(PgStat_SqlstmtEntry), 1, fpout);
+ (void) rc; /* we'll check for error with ferror */
+ }
+
/*
* No more output to be done. Close the temp file and replace the old
* pgstat.stat with it. The ferror() check replaces testing for error
@@ -4413,6 +4529,14 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep)
dbhash = hash_create("Databases hash", PGSTAT_DB_HASH_SIZE, &hash_ctl,
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+ /* Create SQL statement counter Stats HashTable */
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = NAMEDATALEN;
+ hash_ctl.entrysize = sizeof(PgStat_SqlstmtEntry);
+ pgstat_sql = hash_create("SQL statement counter",
+ PGSTAT_SQLSTMT_HASH_SIZE, &hash_ctl,
+ HASH_ELEM);
+
/*
* Clear out global and archiver statistics so they start from zero in
* case we can't load an existing statsfile.
@@ -4558,6 +4682,38 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep)
break;
+ case 'S':
+ {
+ PgStat_SqlstmtEntry stats;
+ PgStat_SqlstmtEntry *lstats;
+
+ if (fread(&stats, 1, sizeof(PgStat_SqlstmtEntry), fpin) != sizeof(PgStat_SqlstmtEntry))
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"",
+ statfile)));
+ goto done;
+ }
+
+ /*
+ * Add to the SQL statement counter hash
+ */
+ lstats = (PgStat_SqlstmtEntry *) hash_search(pgstat_sql,
+ (void *) &(stats.tag),
+ HASH_ENTER,
+ &found);
+ if (found)
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"",
+ statfile)));
+ goto done;
+ }
+
+ memcpy(lstats, &stats, sizeof(PgStat_SqlstmtEntry));
+
+ break;
+ }
case 'E':
goto done;
@@ -4857,6 +5013,21 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool permanent,
break;
+ case 'S':
+ {
+ PgStat_SqlstmtEntry stats;
+
+ if (fread(&stats, 1, sizeof(PgStat_SqlstmtEntry), fpin) != sizeof(PgStat_SqlstmtEntry))
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"",
+ statfile)));
+ goto done;
+ }
+
+ break;
+ }
+
case 'E':
goto done;
@@ -5382,6 +5553,21 @@ pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter *msg, int len)
memset(&archiverStats, 0, sizeof(archiverStats));
archiverStats.stat_reset_timestamp = GetCurrentTimestamp();
}
+ else if (msg->m_resettarget == RESET_SQLSTMT)
+ {
+ HASHCTL hash_ctl;
+
+ /* Destroy the SQL statement counter Stats HashTable */
+ hash_destroy(pgstat_sql);
+
+ /* Create SQL statement counter Stats HashTable */
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = NAMEDATALEN;
+ hash_ctl.entrysize = sizeof(PgStat_SqlstmtEntry);
+ pgstat_sql = hash_create("SQL statement counter",
+ PGSTAT_SQLSTMT_HASH_SIZE, &hash_ctl,
+ HASH_ELEM);
+ }
/*
* Presumably the sender of this message validated the target, don't
@@ -5562,6 +5748,37 @@ pgstat_recv_bgwriter(PgStat_MsgBgWriter *msg, int len)
}
/* ----------
+ * pgstat_recv_sqlstmt() -
+ *
+ * Process a SQL statement statistics message.
+ * ----------
+ */
+static void
+pgstat_recv_sqlstmt(PgStat_MsgSqlstmt *msg, int len)
+{
+ PgStat_SqlstmtEntry *entry;
+ bool found;
+ int i;
+
+ for (i = 0; i < msg->m_nentries; i++)
+ {
+ /* Get the stats entry for this SQL statement, create if necessary */
+ entry = (PgStat_SqlstmtEntry *) hash_search(pgstat_sql, (void *) &(msg->m_entry[i].tag),
+ HASH_ENTER, &found);
+
+ if (found)
+ {
+ entry->count += msg->m_entry[i].count;
+ }
+ else
+ {
+ memcpy(entry->tag, msg->m_entry[i].tag, NAMEDATALEN);
+ entry->count = msg->m_entry[i].count;
+ }
+ }
+}
+
+/* ----------
* pgstat_recv_recoveryconflict() -
*
* Process a RECOVERYCONFLICT message.
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index bb89cce..fbda3c0 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1109,6 +1109,12 @@ exec_simple_query(const char *query_string)
PortalDrop(portal, false);
+ /*
+ * Count SQL statement for pg_stat_sql view
+ */
+ if (pgstat_track_sql)
+ pgstat_count_sqlstmt(commandTag);
+
if (IsA(parsetree->stmt, TransactionStmt))
{
/*
@@ -1990,6 +1996,29 @@ exec_execute_message(const char *portal_name, long max_rows)
(*receiver->rDestroy) (receiver);
+ /*
+ * Count SQL Statement for pgx_stat_sql
+ */
+ if (pgstat_track_sql)
+ {
+ CachedPlanSource *psrc = NULL;
+
+ if (portal->prepStmtName)
+ {
+ PreparedStatement *pstmt;
+
+ pstmt = FetchPreparedStatement(portal->prepStmtName, false);
+ if (pstmt)
+ psrc = pstmt->plansource;
+ }
+ else
+ psrc = unnamed_stmt_psrc;
+
+ /* psrc should not be NULL here */
+ if (psrc && psrc->commandTag && !execute_is_fetch && pgstat_track_sql)
+ pgstat_count_sqlstmt(psrc->commandTag);
+ }
+
if (completed)
{
if (is_xact_command)
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index a987d0d..ad7f499 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1831,3 +1831,73 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(HeapTupleGetDatum(
heap_form_tuple(tupdesc, values, nulls)));
}
+
+Datum
+pg_stat_sql(PG_FUNCTION_ARGS)
+{
+ TupleDesc tupdesc;
+ Datum values[2];
+ bool nulls[2];
+ ReturnSetInfo *rsi;
+ MemoryContext old_cxt;
+ Tuplestorestate *tuple_store;
+ PgStat_SqlstmtEntry *sqlstmtentry;
+ HASH_SEQ_STATUS hstat;
+
+ /* Function call to let the backend read the stats file */
+ pgstat_fetch_global();
+
+ /* Initialize values and nulls arrays */
+ MemSet(values, 0, sizeof(values));
+ MemSet(nulls, 0, sizeof(nulls));
+
+ /*
+ * We must use the Materialize mode to be more efficient than checking the
+ * HASH every time.
+ */
+ rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* Check to see if caller supports us returning a tuplestore */
+ if (rsi == NULL || !IsA(rsi, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsi->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ rsi->returnMode = SFRM_Materialize;
+
+ /* 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");
+
+ /* Build tuplestore to hold the result rows */
+ old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+ tuple_store =
+ tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ false, work_mem);
+ rsi->setDesc = tupdesc;
+ rsi->setResult = tuple_store;
+
+ MemoryContextSwitchTo(old_cxt);
+
+ /* Walk through the SQL statement Stats Hash table. */
+ hash_seq_init(&hstat, pgstat_sql);
+ while ((sqlstmtentry = (PgStat_SqlstmtEntry *) hash_seq_search(&hstat)) != NULL)
+ {
+ HeapTuple tuple;
+
+ /* Fill values and NULLs */
+ values[0] = CStringGetTextDatum(sqlstmtentry->tag);
+ values[1] = Int64GetDatum(sqlstmtentry->count);
+
+ tuple = heap_form_tuple(tupdesc, values, nulls);
+ tuplestore_puttuple(tuple_store, tuple);
+ }
+
+ PG_RETURN_NULL();
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 4e2bd4c..f4cc4a3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1221,6 +1221,16 @@ static struct config_bool ConfigureNamesBool[] =
},
{
+ {"track_sql", PGC_SUSET, STATS_COLLECTOR,
+ gettext_noop("Collects timing statistics for database I/O activity."),
+ NULL
+ },
+ &pgstat_track_sql,
+ false,
+ NULL, NULL, NULL
+ },
+
+ {
{"update_process_title", PGC_SUSET, PROCESS_TITLE,
gettext_noop("Updates the process title to show the active SQL command."),
gettext_noop("Enables updating of the process title every time a new SQL command is received by the server.")
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 15669b8..f80970f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -474,6 +474,7 @@
#track_activities = on
#track_counts = on
+#track_sql = off
#track_io_timing = off
#track_functions = none # none, pl, all
#track_activity_query_size = 1024 # (change requires restart)
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 42f3689..002bd15 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2901,6 +2901,9 @@ DESCR("statistics: total execution time of function in current transaction, in m
DATA(insert OID = 3048 ( pg_stat_get_xact_function_self_time PGNSP PGUID 12 1 0 0 0 f f f f t f v r 1 0 701 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_xact_function_self_time _null_ _null_ _null_ ));
DESCR("statistics: self execution time of function in current transaction, in milliseconds");
+DATA(insert OID = 3398 ( pg_stat_sql PGNSP PGUID 12 1 1000 0 0 f f f f t t v s 0 0 2249 "" "{25,20}" "{o,o}" "{tag,count}" _null_ _null_ pg_stat_sql _null_ _null_ _null_ ));
+DESCR("statistics: Show SQL statement statistics");
+
DATA(insert OID = 3788 ( pg_stat_get_snapshot_timestamp PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 1184 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_snapshot_timestamp _null_ _null_ _null_ ));
DESCR("statistics: timestamp of the current statistics snapshot");
DATA(insert OID = 2230 ( pg_stat_clear_snapshot PGNSP PGUID 12 1 0 0 0 f f f f f f v r 0 0 2278 "" _null_ _null_ _null_ _null_ _null_ pg_stat_clear_snapshot _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 5b37894..ccb5e6a 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -64,7 +64,8 @@ typedef enum StatMsgType
PGSTAT_MTYPE_FUNCPURGE,
PGSTAT_MTYPE_RECOVERYCONFLICT,
PGSTAT_MTYPE_TEMPFILE,
- PGSTAT_MTYPE_DEADLOCK
+ PGSTAT_MTYPE_DEADLOCK,
+ PGSTAT_MTYPE_SQLSTMT
} StatMsgType;
/* ----------
@@ -119,7 +120,8 @@ typedef struct PgStat_TableCounts
typedef enum PgStat_Shared_Reset_Target
{
RESET_ARCHIVER,
- RESET_BGWRITER
+ RESET_BGWRITER,
+ RESET_SQLSTMT
} PgStat_Shared_Reset_Target;
/* Possible object types for resetting single counters */
@@ -530,6 +532,30 @@ typedef struct PgStat_MsgDeadlock
Oid m_databaseid;
} PgStat_MsgDeadlock;
+/* ----------
+ * PgStatSqlstmt
+ * ----------
+ */
+typedef struct PgStat_SqlstmtEntry
+{
+ char tag[NAMEDATALEN];
+ PgStat_Counter count;
+} PgStat_SqlstmtEntry;
+
+
+/* ----------
+ * PgStatMsgSqlstmt Sent by the sql to update statistics.
+ * ----------
+ */
+#define PGSTAT_NUM_SQLSTMTS \
+ ((PGSTAT_MSG_PAYLOAD - sizeof(int)) / sizeof(PgStat_SqlstmtEntry))
+
+typedef struct PgStat_MsgSqlstmt
+{
+ PgStat_MsgHdr m_hdr;
+ int m_nentries;
+ PgStat_SqlstmtEntry m_entry[PGSTAT_NUM_SQLSTMTS];
+} PgStat_MsgSqlstmt;
/* ----------
* PgStat_Msg Union over all possible messages.
@@ -555,6 +581,7 @@ typedef union PgStat_Msg
PgStat_MsgFuncpurge msg_funcpurge;
PgStat_MsgRecoveryConflict msg_recoveryconflict;
PgStat_MsgDeadlock msg_deadlock;
+ PgStat_MsgSqlstmt msg_sqlstmt;
} PgStat_Msg;
@@ -566,7 +593,7 @@ typedef union PgStat_Msg
* ------------------------------------------------------------
*/
-#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9D
+#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9E
/* ----------
* PgStat_StatDBEntry The collector's data per database
@@ -994,6 +1021,7 @@ typedef struct PgStat_FunctionCallUsage
*/
extern bool pgstat_track_activities;
extern bool pgstat_track_counts;
+extern bool pgstat_track_sql;
extern int pgstat_track_functions;
extern PGDLLIMPORT int pgstat_track_activity_query_size;
extern char *pgstat_stat_directory;
@@ -1006,6 +1034,11 @@ extern char *pgstat_stat_filename;
extern PgStat_MsgBgWriter BgWriterStats;
/*
+ * SQL statement statistics counter Hash table
+ */
+extern HTAB *pgstat_sql;
+
+/*
* Updated by pgstat_count_buffer_*_time macros
*/
extern PgStat_Counter pgStatBlockReadTime;
@@ -1199,6 +1232,8 @@ extern void pgstat_twophase_postabort(TransactionId xid, uint16 info,
extern void pgstat_send_archiver(const char *xlog, bool failed);
extern void pgstat_send_bgwriter(void);
+extern void pgstat_count_sqlstmt(const char *commandTag);
+
/* ----------
* Support functions for the SQL-callable functions to
* generate the pgstat* views.
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e9cfadb..54c8362 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1814,6 +1814,9 @@ pg_stat_replication| SELECT s.pid,
FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, ssl, sslversion, sslcipher, sslbits, sslcompression, sslclientdn)
JOIN pg_stat_get_wal_senders() w(pid, state, sent_location, write_location, flush_location, replay_location, sync_priority, sync_state) ON ((s.pid = w.pid)))
LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
+pg_stat_sql| SELECT pg_stat_sql.tag,
+ pg_stat_sql.count
+ FROM pg_stat_sql() pg_stat_sql(tag, count);
pg_stat_ssl| SELECT s.pid,
s.ssl,
s.sslversion AS version,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index a811265..be6db13 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -4,6 +4,62 @@
-- Must be run after tenk2 has been created (by create_table),
-- populated (by create_misc) and indexed (by create_index).
--
+-- pg_stat_sql
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+-----+-------
+(0 rows)
+
+SET track_sql TO ON;
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+--------+-------
+ SELECT | 1
+(1 row)
+
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+-----+-------
+(0 rows)
+
-- conditio sine qua non
SHOW track_counts; -- must be on
track_counts
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index b3e2efa..34716d6 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -5,6 +5,30 @@
-- populated (by create_misc) and indexed (by create_index).
--
+-- pg_stat_sql
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
+SET track_sql TO ON;
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
-- conditio sine qua non
SHOW track_counts; -- must be on
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 993880d..55ec10b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1547,6 +1547,7 @@ PgStat_MsgRecoveryConflict
PgStat_MsgResetcounter
PgStat_MsgResetsharedcounter
PgStat_MsgResetsinglecounter
+PgStat_MsgSqlstmt
PgStat_MsgTabpurge
PgStat_MsgTabstat
PgStat_MsgTempFile
On 2017/01/18 14:15, Haribabu Kommi wrote:
On Sat, Jan 14, 2017 at 2:58 AM, Dilip Kumar <dilipbalaut@gmail.com
<mailto:dilipbalaut@gmail.com>> wrote:On Wed, Jan 11, 2017 at 7:47 PM, Dilip Kumar
<dilipbalaut@gmail.com <mailto:dilipbalaut@gmail.com>> wrote:+void +pgstat_count_sqlstmt(const char *commandTag) +{ + PgStat_SqlstmtEntry *htabent; + bool found; + + if (!pgstat_track_sql) + returnCallers of pgstat_count_sqlstmt are always ensuring that
pgstat_track_sql is true, seems it's redundant here.I have done testing of the feature, it's mostly working as per the
expectation.Thanks for the review and test.
The use case for this view is to find out the number of different SQL
statements
that are getting executed successfully on an instance by the
application/user.I have few comments/questions.
--------
If you execute the query with EXECUTE then commandTag will be EXECUTE
that way we will not show the actual query type, I mean all the
statements will get the common tag "EXECUTE".You can try this.
PREPARE fooplan (int) AS INSERT INTO t VALUES($1);
EXECUTE fooplan(1);------
Yes, that's correct. Currently the count is incremented based on the
base command,
because of this reason, the EXECUTE is increased, instead of the
actual operation.+ /* Destroy the SQL statement counter stats HashTable */ + hash_destroy(pgstat_sql); + + /* Create SQL statement counter Stats HashTable */I think in the patch we have used mixed of "Stats HashTable" and
"stats HashTable", I think better
to be consistent throughout the patch. Check other similar instances.----------------
Corrected.
@@ -1102,6 +1102,12 @@ exec_simple_query(const char *query_string)
PortalDrop(portal, false);
+ /* + * Count SQL statement for pg_stat_sql view + */ + if (pgstat_track_sql) + pgstat_count_sqlstmt(commandTag);We are only counting the successful SQL statement, Is that
intentional?Yes, I thought of counting the successful SQL operations that changed the
database over a period of time. I am not sure whether there will be many
failure operations that can occur on an instance to be counted.------
I have noticed that pgstat_count_sqlstmt is called from
exec_simple_query and exec_execute_message. Don't we want to track the
statement executed from functions?
-------The view is currently designed to count user/application initiated SQL
statements,
but not the internal SQL queries that are getting executed from
functions and etc.+void +pgstat_count_sqlstmt(const char *commandTag) +{ + PgStat_SqlstmtEntry *htabent; + bool found; + + if (!pgstat_track_sql) + returnCallers of pgstat_count_sqlstmt are always ensuring that
pgstat_track_sql is true, seems it's redundant here.Removed the check in pgstat_count_sqlstmt statement and add it
exec_execute_message
function where the check is missed.Updated patch attached.
I have reviewed the patch. All the test cases works fine.
Regards,
Vinayak Pokale
NTT Open Source Software Center
On Wed, Jan 18, 2017 at 10:45 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:
The use case for this view is to find out the number of different SQL
statements
that are getting executed successfully on an instance by the
application/user.I have few comments/questions.
--------
If you execute the query with EXECUTE then commandTag will be EXECUTE
that way we will not show the actual query type, I mean all the
statements will get the common tag "EXECUTE".You can try this.
PREPARE fooplan (int) AS INSERT INTO t VALUES($1);
EXECUTE fooplan(1);------
Yes, that's correct. Currently the count is incremented based on the base
command,
because of this reason, the EXECUTE is increased, instead of the actual
operation.
Okay, As per your explaination, and Robert also pointed out that it
can be a feature for someone and bug for others. I would have
preferred it shows actual SQL statement. But I have no objection to
what you are doing.
+ /* Destroy the SQL statement counter stats HashTable */ + hash_destroy(pgstat_sql); + + /* Create SQL statement counter Stats HashTable */I think in the patch we have used mixed of "Stats HashTable" and
"stats HashTable", I think better
to be consistent throughout the patch. Check other similar instances.----------------
Corrected.
@@ -1102,6 +1102,12 @@ exec_simple_query(const char *query_string)
PortalDrop(portal, false);
+ /* + * Count SQL statement for pg_stat_sql view + */ + if (pgstat_track_sql) + pgstat_count_sqlstmt(commandTag);We are only counting the successful SQL statement, Is that intentional?
Yes, I thought of counting the successful SQL operations that changed the
database over a period of time. I am not sure whether there will be many
failure operations that can occur on an instance to be counted.
Okay..
------
I have noticed that pgstat_count_sqlstmt is called from
exec_simple_query and exec_execute_message. Don't we want to track the
statement executed from functions?
-------The view is currently designed to count user/application initiated SQL
statements,
but not the internal SQL queries that are getting executed from functions
and etc.
Okay..
+void +pgstat_count_sqlstmt(const char *commandTag) +{ + PgStat_SqlstmtEntry *htabent; + bool found; + + if (!pgstat_track_sql) + returnCallers of pgstat_count_sqlstmt are always ensuring that
pgstat_track_sql is true, seems it's redundant here.Removed the check in pgstat_count_sqlstmt statement and add it
exec_execute_message
function where the check is missed.Updated patch attached.
Overall patch looks fine to me and marking it "ready for committer".
There is two design decision, which I leave it for committer's decision.
1. EXECUTE statement should show only as EXECUTE count, not the
actual SQL query.
2. should we count statement execute from Functions or only statement
what is executed directly by the user as it's doing now?
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jan 24, 2017 at 3:59 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
Overall patch looks fine to me and marking it "ready for committer".
There is two design decision, which I leave it for committer's decision.
1. EXECUTE statement should show only as EXECUTE count, not the
actual SQL query.
2. should we count statement execute from Functions or only statement
what is executed directly by the user as it's doing now?
Thanks for the review.
Let's wait for the committer's opinion.
Regards,
Hari Babu
Fujitsu Australia
On Fri, Jan 27, 2017 at 10:26 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:
Thanks for the review.
Let's wait for the committer's opinion.
I have moved this patch to CF 2017-03 to wait for this to happen.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Feb 1, 2017 at 3:13 PM, Michael Paquier <michael.paquier@gmail.com>
wrote:
On Fri, Jan 27, 2017 at 10:26 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:Thanks for the review.
Let's wait for the committer's opinion.I have moved this patch to CF 2017-03 to wait for this to happen.
Attached a rebased patch to resolve the OID conflict.
Regards,
Hari Babu
Fujitsu Australia
Attachments:
pg_stat_sql_row_mode_5.patchapplication/octet-stream; name=pg_stat_sql_row_mode_5.patchDownload
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 1881236..9c0ec08 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5727,6 +5727,21 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
</listitem>
</varlistentry>
+ <varlistentry id="guc-track-sql" xreflabel="track_sql">
+ <term><varname>track_sql</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>track_sql</> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Enables collection of different SQL statement statistics that are
+ executed on the instance. This parameter is off by default. Only
+ superusers can change this setting.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-stats-temp-directory" xreflabel="stats_temp_directory">
<term><varname>stats_temp_directory</varname> (<type>string</type>)
<indexterm>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index fad5cb0..237ae42 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -521,6 +521,13 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
<command>VACUUM</>, showing current progress.
See <xref linkend='vacuum-progress-reporting'>.</entry>
</row>
+
+ <row>
+ <entry><structname>pg_stat_sql</><indexterm><primary>pg_stat_sql</primary></indexterm></entry>
+ <entry>Shows statistics about the SQL statements that are
+ executed on the instance.
+ See <xref linkend="pg-stat-sql-view"> for details.</entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -2521,6 +2528,40 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
controls exactly which functions are tracked.
</para>
+ <table id="pg-stat-sql-view" xreflabel="pg_stat_sql">
+ <title><structname>pg_stat_sql</structname> View</title>
+ <tgroup cols="3">
+ <thead>
+ <row>
+ <entry>Column</entry>
+ <entry>Type</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry><structfield>tag</></entry>
+ <entry><type>text</></entry>
+ <entry>Name of the SQL statement</entry>
+ </row>
+ <row>
+ <entry><structfield>count</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of times the SQL statement is executed</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The <structname>pg_stat_sql</structname> view will contain statistics
+ about number of SQL statements that are executed on the instance.
+ The <xref linkend="guc-track-sql"> parameter controls the SQL statement
+ execution statistics.
+ </para>
+
+
</sect2>
<sect2 id="monitoring-stats-functions">
@@ -2616,6 +2657,8 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
counters shown in the <structname>pg_stat_bgwriter</> view.
Calling <literal>pg_stat_reset_shared('archiver')</> will zero all the
counters shown in the <structname>pg_stat_archiver</> view.
+ Calling <literal>pg_stat_reset_shared('sqlstmt')</> will zero all the
+ counters shown in the <structname>pg_stat_sql</> view.
</entry>
</row>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ba980de..7824b49 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -906,6 +906,11 @@ GRANT SELECT (subdbid, subname, subowner, subenabled, subslotname, subpublicatio
ON pg_subscription TO public;
+CREATE VIEW pg_stat_sql AS
+ SELECT * FROM pg_stat_sql();
+
+REVOKE EXECUTE ON FUNCTION pg_stat_sql() FROM public;
+
--
-- We have a few function definitions in here, too.
-- At some point there might be enough to justify breaking them out into
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 2fb9a8b..439ffcd 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -100,6 +100,7 @@
#define PGSTAT_DB_HASH_SIZE 16
#define PGSTAT_TAB_HASH_SIZE 512
#define PGSTAT_FUNCTION_HASH_SIZE 512
+#define PGSTAT_SQLSTMT_HASH_SIZE 256
/* ----------
@@ -108,6 +109,7 @@
*/
bool pgstat_track_activities = false;
bool pgstat_track_counts = false;
+bool pgstat_track_sql = false;
int pgstat_track_functions = TRACK_FUNC_OFF;
int pgstat_track_activity_query_size = 1024;
@@ -126,6 +128,19 @@ char *pgstat_stat_tmpname = NULL;
*/
PgStat_MsgBgWriter BgWriterStats;
+/*
+ * Backend specific SQL statement information stored in this hash table,
+ * before sent it to the stats collector.
+ */
+static HTAB *pgstat_backend_sql = NULL;
+
+/*
+ * Global SQL statement stats information gathered in the collector are stored in
+ * this hash table.
+ */
+HTAB *pgstat_sql = NULL;
+
+
/* ----------
* Local data
* ----------
@@ -270,6 +285,7 @@ static bool pgstat_db_requested(Oid databaseid);
static void pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg);
static void pgstat_send_funcstats(void);
+static void pgstat_send_sqlstmt(void);
static HTAB *pgstat_collect_oids(Oid catalogid);
static PgStat_TableStatus *get_tabstat_entry(Oid rel_id, bool isshared);
@@ -301,6 +317,7 @@ static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len);
static void pgstat_recv_recoveryconflict(PgStat_MsgRecoveryConflict *msg, int len);
static void pgstat_recv_deadlock(PgStat_MsgDeadlock *msg, int len);
static void pgstat_recv_tempfile(PgStat_MsgTempFile *msg, int len);
+static void pgstat_recv_sqlstmt(PgStat_MsgSqlstmt *msg, int len);
/* ------------------------------------------------------------
* Public functions called from postmaster follow
@@ -834,6 +851,10 @@ pgstat_report_stat(bool force)
/* Now, send function statistics */
pgstat_send_funcstats();
+
+ /* Now, send sql statistics */
+ if (pgstat_track_sql)
+ pgstat_send_sqlstmt();
}
/*
@@ -1260,11 +1281,13 @@ pgstat_reset_shared_counters(const char *target)
msg.m_resettarget = RESET_ARCHIVER;
else if (strcmp(target, "bgwriter") == 0)
msg.m_resettarget = RESET_BGWRITER;
+ else if (strcmp(target, "sqlstmt") == 0)
+ msg.m_resettarget = RESET_SQLSTMT;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized reset target: \"%s\"", target),
- errhint("Target must be \"archiver\" or \"bgwriter\".")));
+ errhint("Target must be \"archiver\" or \"bgwriter\" or \"sqlstmt\".")));
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSHAREDCOUNTER);
pgstat_send(&msg, sizeof(msg));
@@ -3690,6 +3713,86 @@ pgstat_send_bgwriter(void)
MemSet(&BgWriterStats, 0, sizeof(BgWriterStats));
}
+/* ----------
+ * pgstat_send_sqlstmt(void)
+ *
+ * Send SQL statement statistics to the collector
+ * ----------
+ */
+static void
+pgstat_send_sqlstmt(void)
+{
+ PgStat_MsgSqlstmt msg;
+ PgStat_SqlstmtEntry *entry;
+ HASH_SEQ_STATUS hstat;
+
+ if (pgstat_backend_sql == NULL)
+ return;
+
+ pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_SQLSTMT);
+ msg.m_nentries = 0;
+
+ hash_seq_init(&hstat, pgstat_backend_sql);
+ while ((entry = (PgStat_SqlstmtEntry *) hash_seq_search(&hstat)) != NULL)
+ {
+ PgStat_SqlstmtEntry *m_ent;
+
+ /* Skip it if no counts accumulated since last time */
+ if (entry->count == 0)
+ continue;
+
+ /* need to convert format of time accumulators */
+ m_ent = &msg.m_entry[msg.m_nentries];
+ memcpy(m_ent, entry, sizeof(PgStat_SqlstmtEntry));
+
+ if (++msg.m_nentries >= PGSTAT_NUM_SQLSTMTS)
+ {
+ pgstat_send(&msg, offsetof(PgStat_MsgSqlstmt, m_entry[0]) +
+ msg.m_nentries * sizeof(PgStat_SqlstmtEntry));
+ msg.m_nentries = 0;
+ }
+
+ /* reset the entry's count */
+ entry->count = 0;
+ }
+
+ if (msg.m_nentries > 0)
+ pgstat_send(&msg, offsetof(PgStat_MsgSqlstmt, m_entry[0]) +
+ msg.m_nentries * sizeof(PgStat_SqlstmtEntry));
+}
+
+/*
+ * Count SQL statement for pg_stat_sql view
+ */
+void
+pgstat_count_sqlstmt(const char *commandTag)
+{
+ PgStat_SqlstmtEntry *htabent;
+ bool found;
+
+ if (!pgstat_backend_sql)
+ {
+ /* First time through - initialize SQL statement stat table */
+ HASHCTL hash_ctl;
+
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = NAMEDATALEN;
+ hash_ctl.entrysize = sizeof(PgStat_SqlstmtEntry);
+ pgstat_backend_sql = hash_create("SQL statement stat entries",
+ PGSTAT_SQLSTMT_HASH_SIZE,
+ &hash_ctl,
+ HASH_ELEM | HASH_BLOBS);
+ }
+
+ /* Get the stats entry for this SQL statement, create if necessary */
+ htabent = hash_search(pgstat_backend_sql, commandTag,
+ HASH_ENTER, &found);
+ if (!found)
+ htabent->count = 1;
+ else
+ htabent->count++;
+}
+
/* ----------
* PgstatCollectorMain() -
@@ -3906,6 +4009,9 @@ PgstatCollectorMain(int argc, char *argv[])
pgstat_recv_tempfile((PgStat_MsgTempFile *) &msg, len);
break;
+ case PGSTAT_MTYPE_SQLSTMT:
+ pgstat_recv_sqlstmt((PgStat_MsgSqlstmt *) &msg, len);
+ break;
default:
break;
}
@@ -4126,6 +4232,7 @@ pgstat_write_statsfiles(bool permanent, bool allDbs)
{
HASH_SEQ_STATUS hstat;
PgStat_StatDBEntry *dbentry;
+ PgStat_SqlstmtEntry *sqlstmtentry;
FILE *fpout;
int32 format_id;
const char *tmpfile = permanent ? PGSTAT_STAT_PERMANENT_TMPFILE : pgstat_stat_tmpname;
@@ -4198,6 +4305,15 @@ pgstat_write_statsfiles(bool permanent, bool allDbs)
(void) rc; /* we'll check for error with ferror */
}
+ /* Walk through the SQL statement Stats Hash table. */
+ hash_seq_init(&hstat, pgstat_sql);
+ while ((sqlstmtentry = (PgStat_SqlstmtEntry *) hash_seq_search(&hstat)) != NULL)
+ {
+ fputc('S', fpout);
+ rc = fwrite(sqlstmtentry, sizeof(PgStat_SqlstmtEntry), 1, fpout);
+ (void) rc; /* we'll check for error with ferror */
+ }
+
/*
* No more output to be done. Close the temp file and replace the old
* pgstat.stat with it. The ferror() check replaces testing for error
@@ -4422,6 +4538,14 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep)
dbhash = hash_create("Databases hash", PGSTAT_DB_HASH_SIZE, &hash_ctl,
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+ /* Create SQL statement counter Stats HashTable */
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = NAMEDATALEN;
+ hash_ctl.entrysize = sizeof(PgStat_SqlstmtEntry);
+ pgstat_sql = hash_create("SQL statement counter",
+ PGSTAT_SQLSTMT_HASH_SIZE, &hash_ctl,
+ HASH_ELEM);
+
/*
* Clear out global and archiver statistics so they start from zero in
* case we can't load an existing statsfile.
@@ -4567,6 +4691,38 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep)
break;
+ case 'S':
+ {
+ PgStat_SqlstmtEntry stats;
+ PgStat_SqlstmtEntry *lstats;
+
+ if (fread(&stats, 1, sizeof(PgStat_SqlstmtEntry), fpin) != sizeof(PgStat_SqlstmtEntry))
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"",
+ statfile)));
+ goto done;
+ }
+
+ /*
+ * Add to the SQL statement counter hash
+ */
+ lstats = (PgStat_SqlstmtEntry *) hash_search(pgstat_sql,
+ (void *) &(stats.tag),
+ HASH_ENTER,
+ &found);
+ if (found)
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"",
+ statfile)));
+ goto done;
+ }
+
+ memcpy(lstats, &stats, sizeof(PgStat_SqlstmtEntry));
+
+ break;
+ }
case 'E':
goto done;
@@ -4866,6 +5022,21 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool permanent,
break;
+ case 'S':
+ {
+ PgStat_SqlstmtEntry stats;
+
+ if (fread(&stats, 1, sizeof(PgStat_SqlstmtEntry), fpin) != sizeof(PgStat_SqlstmtEntry))
+ {
+ ereport(pgStatRunningInCollector ? LOG : WARNING,
+ (errmsg("corrupted statistics file \"%s\"",
+ statfile)));
+ goto done;
+ }
+
+ break;
+ }
+
case 'E':
goto done;
@@ -5391,6 +5562,21 @@ pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter *msg, int len)
memset(&archiverStats, 0, sizeof(archiverStats));
archiverStats.stat_reset_timestamp = GetCurrentTimestamp();
}
+ else if (msg->m_resettarget == RESET_SQLSTMT)
+ {
+ HASHCTL hash_ctl;
+
+ /* Destroy the SQL statement counter Stats HashTable */
+ hash_destroy(pgstat_sql);
+
+ /* Create SQL statement counter Stats HashTable */
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = NAMEDATALEN;
+ hash_ctl.entrysize = sizeof(PgStat_SqlstmtEntry);
+ pgstat_sql = hash_create("SQL statement counter",
+ PGSTAT_SQLSTMT_HASH_SIZE, &hash_ctl,
+ HASH_ELEM);
+ }
/*
* Presumably the sender of this message validated the target, don't
@@ -5571,6 +5757,37 @@ pgstat_recv_bgwriter(PgStat_MsgBgWriter *msg, int len)
}
/* ----------
+ * pgstat_recv_sqlstmt() -
+ *
+ * Process a SQL statement statistics message.
+ * ----------
+ */
+static void
+pgstat_recv_sqlstmt(PgStat_MsgSqlstmt *msg, int len)
+{
+ PgStat_SqlstmtEntry *entry;
+ bool found;
+ int i;
+
+ for (i = 0; i < msg->m_nentries; i++)
+ {
+ /* Get the stats entry for this SQL statement, create if necessary */
+ entry = (PgStat_SqlstmtEntry *) hash_search(pgstat_sql, (void *) &(msg->m_entry[i].tag),
+ HASH_ENTER, &found);
+
+ if (found)
+ {
+ entry->count += msg->m_entry[i].count;
+ }
+ else
+ {
+ memcpy(entry->tag, msg->m_entry[i].tag, NAMEDATALEN);
+ entry->count = msg->m_entry[i].count;
+ }
+ }
+}
+
+/* ----------
* pgstat_recv_recoveryconflict() -
*
* Process a RECOVERYCONFLICT message.
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index b07d6c6..e888231 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1109,6 +1109,12 @@ exec_simple_query(const char *query_string)
PortalDrop(portal, false);
+ /*
+ * Count SQL statement for pg_stat_sql view
+ */
+ if (pgstat_track_sql)
+ pgstat_count_sqlstmt(commandTag);
+
if (IsA(parsetree->stmt, TransactionStmt))
{
/*
@@ -1991,6 +1997,29 @@ exec_execute_message(const char *portal_name, long max_rows)
(*receiver->rDestroy) (receiver);
+ /*
+ * Count SQL Statement for pgx_stat_sql
+ */
+ if (pgstat_track_sql)
+ {
+ CachedPlanSource *psrc = NULL;
+
+ if (portal->prepStmtName)
+ {
+ PreparedStatement *pstmt;
+
+ pstmt = FetchPreparedStatement(portal->prepStmtName, false);
+ if (pstmt)
+ psrc = pstmt->plansource;
+ }
+ else
+ psrc = unnamed_stmt_psrc;
+
+ /* psrc should not be NULL here */
+ if (psrc && psrc->commandTag && !execute_is_fetch && pgstat_track_sql)
+ pgstat_count_sqlstmt(psrc->commandTag);
+ }
+
if (completed)
{
if (is_xact_command)
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index a987d0d..ad7f499 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1831,3 +1831,73 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(HeapTupleGetDatum(
heap_form_tuple(tupdesc, values, nulls)));
}
+
+Datum
+pg_stat_sql(PG_FUNCTION_ARGS)
+{
+ TupleDesc tupdesc;
+ Datum values[2];
+ bool nulls[2];
+ ReturnSetInfo *rsi;
+ MemoryContext old_cxt;
+ Tuplestorestate *tuple_store;
+ PgStat_SqlstmtEntry *sqlstmtentry;
+ HASH_SEQ_STATUS hstat;
+
+ /* Function call to let the backend read the stats file */
+ pgstat_fetch_global();
+
+ /* Initialize values and nulls arrays */
+ MemSet(values, 0, sizeof(values));
+ MemSet(nulls, 0, sizeof(nulls));
+
+ /*
+ * We must use the Materialize mode to be more efficient than checking the
+ * HASH every time.
+ */
+ rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* Check to see if caller supports us returning a tuplestore */
+ if (rsi == NULL || !IsA(rsi, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsi->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ rsi->returnMode = SFRM_Materialize;
+
+ /* 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");
+
+ /* Build tuplestore to hold the result rows */
+ old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+ tuple_store =
+ tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ false, work_mem);
+ rsi->setDesc = tupdesc;
+ rsi->setResult = tuple_store;
+
+ MemoryContextSwitchTo(old_cxt);
+
+ /* Walk through the SQL statement Stats Hash table. */
+ hash_seq_init(&hstat, pgstat_sql);
+ while ((sqlstmtentry = (PgStat_SqlstmtEntry *) hash_seq_search(&hstat)) != NULL)
+ {
+ HeapTuple tuple;
+
+ /* Fill values and NULLs */
+ values[0] = CStringGetTextDatum(sqlstmtentry->tag);
+ values[1] = Int64GetDatum(sqlstmtentry->count);
+
+ tuple = heap_form_tuple(tupdesc, values, nulls);
+ tuplestore_puttuple(tuple_store, tuple);
+ }
+
+ PG_RETURN_NULL();
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index f8b073d..6f7564e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1230,6 +1230,16 @@ static struct config_bool ConfigureNamesBool[] =
},
{
+ {"track_sql", PGC_SUSET, STATS_COLLECTOR,
+ gettext_noop("Collects timing statistics for database I/O activity."),
+ NULL
+ },
+ &pgstat_track_sql,
+ false,
+ NULL, NULL, NULL
+ },
+
+ {
{"update_process_title", PGC_SUSET, PROCESS_TITLE,
gettext_noop("Updates the process title to show the active SQL command."),
gettext_noop("Enables updating of the process title every time a new SQL command is received by the server.")
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index a02b154..95299ee 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -477,6 +477,7 @@
#track_activities = on
#track_counts = on
+#track_sql = off
#track_io_timing = off
#track_functions = none # none, pl, all
#track_activity_query_size = 1024 # (change requires restart)
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ec4aedb..74db62e 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2903,6 +2903,9 @@ DESCR("statistics: total execution time of function in current transaction, in m
DATA(insert OID = 3048 ( pg_stat_get_xact_function_self_time PGNSP PGUID 12 1 0 0 0 f f f f t f v r 1 0 701 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_xact_function_self_time _null_ _null_ _null_ ));
DESCR("statistics: self execution time of function in current transaction, in milliseconds");
+DATA(insert OID = 3381 ( pg_stat_sql PGNSP PGUID 12 1 1000 0 0 f f f f t t v s 0 0 2249 "" "{25,20}" "{o,o}" "{tag,count}" _null_ _null_ pg_stat_sql _null_ _null_ _null_ ));
+DESCR("statistics: Show SQL statement statistics");
+
DATA(insert OID = 3788 ( pg_stat_get_snapshot_timestamp PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 1184 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_snapshot_timestamp _null_ _null_ _null_ ));
DESCR("statistics: timestamp of the current statistics snapshot");
DATA(insert OID = 2230 ( pg_stat_clear_snapshot PGNSP PGUID 12 1 0 0 0 f f f f f f v r 0 0 2278 "" _null_ _null_ _null_ _null_ _null_ pg_stat_clear_snapshot _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 0062fb8..1845034 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -64,7 +64,8 @@ typedef enum StatMsgType
PGSTAT_MTYPE_FUNCPURGE,
PGSTAT_MTYPE_RECOVERYCONFLICT,
PGSTAT_MTYPE_TEMPFILE,
- PGSTAT_MTYPE_DEADLOCK
+ PGSTAT_MTYPE_DEADLOCK,
+ PGSTAT_MTYPE_SQLSTMT
} StatMsgType;
/* ----------
@@ -119,7 +120,8 @@ typedef struct PgStat_TableCounts
typedef enum PgStat_Shared_Reset_Target
{
RESET_ARCHIVER,
- RESET_BGWRITER
+ RESET_BGWRITER,
+ RESET_SQLSTMT
} PgStat_Shared_Reset_Target;
/* Possible object types for resetting single counters */
@@ -530,6 +532,30 @@ typedef struct PgStat_MsgDeadlock
Oid m_databaseid;
} PgStat_MsgDeadlock;
+/* ----------
+ * PgStatSqlstmt
+ * ----------
+ */
+typedef struct PgStat_SqlstmtEntry
+{
+ char tag[NAMEDATALEN];
+ PgStat_Counter count;
+} PgStat_SqlstmtEntry;
+
+
+/* ----------
+ * PgStatMsgSqlstmt Sent by the sql to update statistics.
+ * ----------
+ */
+#define PGSTAT_NUM_SQLSTMTS \
+ ((PGSTAT_MSG_PAYLOAD - sizeof(int)) / sizeof(PgStat_SqlstmtEntry))
+
+typedef struct PgStat_MsgSqlstmt
+{
+ PgStat_MsgHdr m_hdr;
+ int m_nentries;
+ PgStat_SqlstmtEntry m_entry[PGSTAT_NUM_SQLSTMTS];
+} PgStat_MsgSqlstmt;
/* ----------
* PgStat_Msg Union over all possible messages.
@@ -555,6 +581,7 @@ typedef union PgStat_Msg
PgStat_MsgFuncpurge msg_funcpurge;
PgStat_MsgRecoveryConflict msg_recoveryconflict;
PgStat_MsgDeadlock msg_deadlock;
+ PgStat_MsgSqlstmt msg_sqlstmt;
} PgStat_Msg;
@@ -566,7 +593,7 @@ typedef union PgStat_Msg
* ------------------------------------------------------------
*/
-#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9D
+#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9E
/* ----------
* PgStat_StatDBEntry The collector's data per database
@@ -997,6 +1024,7 @@ typedef struct PgStat_FunctionCallUsage
*/
extern bool pgstat_track_activities;
extern bool pgstat_track_counts;
+extern bool pgstat_track_sql;
extern int pgstat_track_functions;
extern PGDLLIMPORT int pgstat_track_activity_query_size;
extern char *pgstat_stat_directory;
@@ -1009,6 +1037,11 @@ extern char *pgstat_stat_filename;
extern PgStat_MsgBgWriter BgWriterStats;
/*
+ * SQL statement statistics counter Hash table
+ */
+extern HTAB *pgstat_sql;
+
+/*
* Updated by pgstat_count_buffer_*_time macros
*/
extern PgStat_Counter pgStatBlockReadTime;
@@ -1202,6 +1235,8 @@ extern void pgstat_twophase_postabort(TransactionId xid, uint16 info,
extern void pgstat_send_archiver(const char *xlog, bool failed);
extern void pgstat_send_bgwriter(void);
+extern void pgstat_count_sqlstmt(const char *commandTag);
+
/* ----------
* Support functions for the SQL-callable functions to
* generate the pgstat* views.
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index c661f1d..65f1b09 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1836,6 +1836,9 @@ pg_stat_replication| SELECT s.pid,
FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, ssl, sslversion, sslcipher, sslbits, sslcompression, sslclientdn)
JOIN pg_stat_get_wal_senders() w(pid, state, sent_location, write_location, flush_location, replay_location, sync_priority, sync_state) ON ((s.pid = w.pid)))
LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
+pg_stat_sql| SELECT pg_stat_sql.tag,
+ pg_stat_sql.count
+ FROM pg_stat_sql() pg_stat_sql(tag, count);
pg_stat_ssl| SELECT s.pid,
s.ssl,
s.sslversion AS version,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index a811265..be6db13 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -4,6 +4,62 @@
-- Must be run after tenk2 has been created (by create_table),
-- populated (by create_misc) and indexed (by create_index).
--
+-- pg_stat_sql
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+-----+-------
+(0 rows)
+
+SET track_sql TO ON;
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+--------+-------
+ SELECT | 1
+(1 row)
+
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+-----+-------
+(0 rows)
+
-- conditio sine qua non
SHOW track_counts; -- must be on
track_counts
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index b3e2efa..34716d6 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -5,6 +5,30 @@
-- populated (by create_misc) and indexed (by create_index).
--
+-- pg_stat_sql
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
+SET track_sql TO ON;
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
+SET track_sql TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+
+-- force the rate-limiting logic in pgstat_report_stat() to time out
+-- and send a message
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
-- conditio sine qua non
SHOW track_counts; -- must be on
Hi,
I'm somewhat inclined to think that this really would be better done in
an extension like pg_stat_statements.
On 2017-03-08 14:39:23 +1100, Haribabu Kommi wrote:
On Wed, Feb 1, 2017 at 3:13 PM, Michael Paquier <michael.paquier@gmail.com>
+ <varlistentry id="guc-track-sql" xreflabel="track_sql"> + <term><varname>track_sql</varname> (<type>boolean</type>) + <indexterm> + <primary><varname>track_sql</> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Enables collection of different SQL statement statistics that are + executed on the instance. This parameter is off by default. Only + superusers can change this setting. + </para> + </listitem> + </varlistentry> +
I don't like this name much, seems a bit too generic to
me. 'track_activities', 'track_io_timings' are at least a bit clearer.
How about track_statement_statistics + corresponding view/function renaming?
+ <table id="pg-stat-sql-view" xreflabel="pg_stat_sql"> + <title><structname>pg_stat_sql</structname> View</title> + <tgroup cols="3"> + <thead> + <row> + <entry>Column</entry> + <entry>Type</entry> + <entry>Description</entry> + </row> + </thead> + + <tbody> + <row> + <entry><structfield>tag</></entry> + <entry><type>text</></entry> + <entry>Name of the SQL statement</entry> + </row>
It's not really the "name" of a statement. Internally and IIRC elsewhere
in the docs we describe something like this as tag?
+/* ---------- + * pgstat_send_sqlstmt(void) + * + * Send SQL statement statistics to the collector + * ---------- + */ +static void +pgstat_send_sqlstmt(void) +{ + PgStat_MsgSqlstmt msg; + PgStat_SqlstmtEntry *entry; + HASH_SEQ_STATUS hstat; + + if (pgstat_backend_sql == NULL) + return; + + pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_SQLSTMT); + msg.m_nentries = 0; + + hash_seq_init(&hstat, pgstat_backend_sql); + while ((entry = (PgStat_SqlstmtEntry *) hash_seq_search(&hstat)) != NULL) + { + PgStat_SqlstmtEntry *m_ent; + + /* Skip it if no counts accumulated since last time */ + if (entry->count == 0) + continue; + + /* need to convert format of time accumulators */ + m_ent = &msg.m_entry[msg.m_nentries]; + memcpy(m_ent, entry, sizeof(PgStat_SqlstmtEntry)); + + if (++msg.m_nentries >= PGSTAT_NUM_SQLSTMTS) + { + pgstat_send(&msg, offsetof(PgStat_MsgSqlstmt, m_entry[0]) + + msg.m_nentries * sizeof(PgStat_SqlstmtEntry)); + msg.m_nentries = 0; + } + + /* reset the entry's count */ + entry->count = 0; + } + + if (msg.m_nentries > 0) + pgstat_send(&msg, offsetof(PgStat_MsgSqlstmt, m_entry[0]) + + msg.m_nentries * sizeof(PgStat_SqlstmtEntry));
Hm. So pgstat_backend_sql is never deleted, which'll mean that if a
backend lives long we'll continually iterate over a lot of 0 entries. I
think performance evaluation of this feature should take that into
account.
+} + +/* + * Count SQL statement for pg_stat_sql view + */ +void +pgstat_count_sqlstmt(const char *commandTag) +{ + PgStat_SqlstmtEntry *htabent; + bool found; + + if (!pgstat_backend_sql) + { + /* First time through - initialize SQL statement stat table */ + HASHCTL hash_ctl; + + memset(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = NAMEDATALEN; + hash_ctl.entrysize = sizeof(PgStat_SqlstmtEntry); + pgstat_backend_sql = hash_create("SQL statement stat entries", + PGSTAT_SQLSTMT_HASH_SIZE, + &hash_ctl, + HASH_ELEM | HASH_BLOBS); + } + + /* Get the stats entry for this SQL statement, create if necessary */ + htabent = hash_search(pgstat_backend_sql, commandTag, + HASH_ENTER, &found); + if (!found) + htabent->count = 1; + else + htabent->count++; +}
That's not really something in this patch, but a lot of this would be
better if we didn't internally have command tags as strings, but as an
enum. We should really only convert to a string with needed. That
we're doing string comparisons on Portal->commandTag is just plain bad.
If so, we could also make this whole patch a lot cheaper - have a fixed
size array that has an entry for every possible tag (possibly even in
shared memory, and then use atomics there).
+++ b/src/backend/tcop/postgres.c @@ -1109,6 +1109,12 @@ exec_simple_query(const char *query_string)PortalDrop(portal, false);
+ /* + * Count SQL statement for pg_stat_sql view + */ + if (pgstat_track_sql) + pgstat_count_sqlstmt(commandTag); +
Isn't doing this at the message level quite wrong? What about
statements executed from functions and such? Shouldn't this integrate
at the executor level instead?
if (IsA(parsetree->stmt, TransactionStmt))
{
/*
@@ -1991,6 +1997,29 @@ exec_execute_message(const char *portal_name, long max_rows)(*receiver->rDestroy) (receiver);
+ /* + * Count SQL Statement for pgx_stat_sql + */ + if (pgstat_track_sql) + { + CachedPlanSource *psrc = NULL; + + if (portal->prepStmtName) + { + PreparedStatement *pstmt; + + pstmt = FetchPreparedStatement(portal->prepStmtName, false); + if (pstmt) + psrc = pstmt->plansource; + } + else + psrc = unnamed_stmt_psrc; + + /* psrc should not be NULL here */ + if (psrc && psrc->commandTag && !execute_is_fetch && pgstat_track_sql) + pgstat_count_sqlstmt(psrc->commandTag);
Wait, we're re-fetching the statement here? That doesn't sound
alright.
+Datum +pg_stat_sql(PG_FUNCTION_ARGS) +{ + TupleDesc tupdesc; + Datum values[2]; + bool nulls[2]; + ReturnSetInfo *rsi; + MemoryContext old_cxt; + Tuplestorestate *tuple_store; + PgStat_SqlstmtEntry *sqlstmtentry; + HASH_SEQ_STATUS hstat; + + /* Function call to let the backend read the stats file */ + pgstat_fetch_global(); + + /* Initialize values and nulls arrays */ + MemSet(values, 0, sizeof(values)); + MemSet(nulls, 0, sizeof(nulls));
why not instead declare values[2] = {0}? Obviously not important.
+ tuple_store = + tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random, + false, work_mem);
Huh, why do we need random?
Greetings,
Andres Freund
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Apr 6, 2017 at 5:17 AM, Andres Freund <andres@anarazel.de> wrote:
Hi,
I'm somewhat inclined to think that this really would be better done in
an extension like pg_stat_statements.
Thanks for the review.
+} + +/* + * Count SQL statement for pg_stat_sql view + */ +void +pgstat_count_sqlstmt(const char *commandTag) +{ + PgStat_SqlstmtEntry *htabent; + bool found; + + if (!pgstat_backend_sql) + { + /* First time through - initialize SQL statement stattable */
+ HASHCTL hash_ctl; + + memset(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = NAMEDATALEN; + hash_ctl.entrysize = sizeof(PgStat_SqlstmtEntry); + pgstat_backend_sql = hash_create("SQL statement statentries",
+
PGSTAT_SQLSTMT_HASH_SIZE,
+
&hash_ctl,
+
HASH_ELEM | HASH_BLOBS);
+ } + + /* Get the stats entry for this SQL statement, create if necessary*/
+ htabent = hash_search(pgstat_backend_sql, commandTag, + HASH_ENTER, &found); + if (!found) + htabent->count = 1; + else + htabent->count++; +}That's not really something in this patch, but a lot of this would be
better if we didn't internally have command tags as strings, but as an
enum. We should really only convert to a string with needed. That
we're doing string comparisons on Portal->commandTag is just plain bad.If so, we could also make this whole patch a lot cheaper - have a fixed
size array that has an entry for every possible tag (possibly even in
shared memory, and then use atomics there).
During the development, I thought of using an array of all command tags
and update the counters using the tag name, but not like the enum values.
Now I have to create a mapping array with enums and tag names for easier
counter updates. The problem in this approach is, whenever any addition
of new commands, this mapping array needs to be updated with both
new enum and new tag name, whereas with hash table approach, it works
future command additions also, but there is some performance overhead
compared to the array approach.
I will modify the patch to use the array approach and provide it to the next
commitfest.
+++ b/src/backend/tcop/postgres.c @@ -1109,6 +1109,12 @@ exec_simple_query(const char *query_string)PortalDrop(portal, false);
+ /* + * Count SQL statement for pg_stat_sql view + */ + if (pgstat_track_sql) + pgstat_count_sqlstmt(commandTag); +Isn't doing this at the message level quite wrong? What about
statements executed from functions and such? Shouldn't this integrate
at the executor level instead?
Currently the patch is designed to find out only the user executed
statements
that are successfully finished, (no internal statements that are executed
from
functions and etc).
The same question was asked by dilip also in earlier mails, may be now it is
the time that we can decide the approach of statement counts.
Regards,
Hari Babu
Fujitsu Australia
Dear Hackers,
We have resurrected this 2 year old "SQL statements statistics counter" proposal from Hari.
The attached patch addresses the previous review issues.
The commit-fest entry has been moved forward to the next commit-fest
https://commitfest.postgresql.org/25/790/
Please review again, and consider if this is OK for "Ready for Committer" status.
Kind Regards
--
Peter Smith
Fujitsu Australia
-----Original Message-----
From: pgsql-hackers-owner@postgresql.org <pgsql-hackers-owner@postgresql.org> On Behalf Of Andres Freund
Sent: Thursday, 6 April 2017 5:18 AM
To: Haribabu Kommi <kommi.haribabu@gmail.com>
Cc: Michael Paquier <michael.paquier@gmail.com>; Dilip Kumar <dilipbalaut@gmail.com>; vinayak <Pokale_Vinayak_q3@lab.ntt.co.jp>; pgsql-hackers@postgresql.org
Subject: Re: New SQL counter statistics view (pg_stat_sql)
Hi,
I'm somewhat inclined to think that this really would be better done in an extension like pg_stat_statements.
On 2017-03-08 14:39:23 +1100, Haribabu Kommi wrote:
On Wed, Feb 1, 2017 at 3:13 PM, Michael Paquier
<michael.paquier@gmail.com>
+ <varlistentry id="guc-track-sql" xreflabel="track_sql"> + <term><varname>track_sql</varname> (<type>boolean</type>) + <indexterm> + <primary><varname>track_sql</> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Enables collection of different SQL statement statistics that are + executed on the instance. This parameter is off by default. Only + superusers can change this setting. + </para> + </listitem> + </varlistentry> +
I don't like this name much, seems a bit too generic to me. 'track_activities', 'track_io_timings' are at least a bit clearer.
How about track_statement_statistics + corresponding view/function renaming?
+ <table id="pg-stat-sql-view" xreflabel="pg_stat_sql"> + <title><structname>pg_stat_sql</structname> View</title> + <tgroup cols="3"> + <thead> + <row> + <entry>Column</entry> + <entry>Type</entry> + <entry>Description</entry> + </row> + </thead> + + <tbody> + <row> + <entry><structfield>tag</></entry> + <entry><type>text</></entry> + <entry>Name of the SQL statement</entry> + </row>
It's not really the "name" of a statement. Internally and IIRC elsewhere in the docs we describe something like this as tag?
+/* ---------- + * pgstat_send_sqlstmt(void) + * + * Send SQL statement statistics to the collector + * ---------- + */ +static void +pgstat_send_sqlstmt(void) +{ + PgStat_MsgSqlstmt msg; + PgStat_SqlstmtEntry *entry; + HASH_SEQ_STATUS hstat; + + if (pgstat_backend_sql == NULL) + return; + + pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_SQLSTMT); + msg.m_nentries = 0; + + hash_seq_init(&hstat, pgstat_backend_sql); + while ((entry = (PgStat_SqlstmtEntry *) hash_seq_search(&hstat)) != NULL) + { + PgStat_SqlstmtEntry *m_ent; + + /* Skip it if no counts accumulated since last time */ + if (entry->count == 0) + continue; + + /* need to convert format of time accumulators */ + m_ent = &msg.m_entry[msg.m_nentries]; + memcpy(m_ent, entry, sizeof(PgStat_SqlstmtEntry)); + + if (++msg.m_nentries >= PGSTAT_NUM_SQLSTMTS) + { + pgstat_send(&msg, offsetof(PgStat_MsgSqlstmt, m_entry[0]) + + msg.m_nentries * sizeof(PgStat_SqlstmtEntry)); + msg.m_nentries = 0; + } + + /* reset the entry's count */ + entry->count = 0; + } + + if (msg.m_nentries > 0) + pgstat_send(&msg, offsetof(PgStat_MsgSqlstmt, m_entry[0]) + + msg.m_nentries * sizeof(PgStat_SqlstmtEntry));
Hm. So pgstat_backend_sql is never deleted, which'll mean that if a backend lives long we'll continually iterate over a lot of 0 entries. I think performance evaluation of this feature should take that into account.
+} + +/* + * Count SQL statement for pg_stat_sql view */ void +pgstat_count_sqlstmt(const char *commandTag) { + PgStat_SqlstmtEntry *htabent; + bool found; + + if (!pgstat_backend_sql) + { + /* First time through - initialize SQL statement stat table */ + HASHCTL hash_ctl; + + memset(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = NAMEDATALEN; + hash_ctl.entrysize = sizeof(PgStat_SqlstmtEntry); + pgstat_backend_sql = hash_create("SQL statement stat entries", + PGSTAT_SQLSTMT_HASH_SIZE, + &hash_ctl, + HASH_ELEM | HASH_BLOBS); + } + + /* Get the stats entry for this SQL statement, create if necessary */ + htabent = hash_search(pgstat_backend_sql, commandTag, + HASH_ENTER, &found); + if (!found) + htabent->count = 1; + else + htabent->count++; +}
That's not really something in this patch, but a lot of this would be better if we didn't internally have command tags as strings, but as an enum. We should really only convert to a string with needed. That we're doing string comparisons on Portal->commandTag is just plain bad.
If so, we could also make this whole patch a lot cheaper - have a fixed size array that has an entry for every possible tag (possibly even in shared memory, and then use atomics there).
+++ b/src/backend/tcop/postgres.c @@ -1109,6 +1109,12 @@ exec_simple_query(const char *query_string)PortalDrop(portal, false);
+ /* + * Count SQL statement for pg_stat_sql view + */ + if (pgstat_track_sql) + pgstat_count_sqlstmt(commandTag); +
Isn't doing this at the message level quite wrong? What about statements executed from functions and such? Shouldn't this integrate at the executor level instead?
if (IsA(parsetree->stmt, TransactionStmt))
{
/*
@@ -1991,6 +1997,29 @@ exec_execute_message(const char *portal_name,
long max_rows)(*receiver->rDestroy) (receiver);
+ /* + * Count SQL Statement for pgx_stat_sql + */ + if (pgstat_track_sql) + { + CachedPlanSource *psrc = NULL; + + if (portal->prepStmtName) + { + PreparedStatement *pstmt; + + pstmt = FetchPreparedStatement(portal->prepStmtName, false); + if (pstmt) + psrc = pstmt->plansource; + } + else + psrc = unnamed_stmt_psrc; + + /* psrc should not be NULL here */ + if (psrc && psrc->commandTag && !execute_is_fetch && pgstat_track_sql) + pgstat_count_sqlstmt(psrc->commandTag);
Wait, we're re-fetching the statement here? That doesn't sound alright.
+Datum +pg_stat_sql(PG_FUNCTION_ARGS) +{ + TupleDesc tupdesc; + Datum values[2]; + bool nulls[2]; + ReturnSetInfo *rsi; + MemoryContext old_cxt; + Tuplestorestate *tuple_store; + PgStat_SqlstmtEntry *sqlstmtentry; + HASH_SEQ_STATUS hstat; + + /* Function call to let the backend read the stats file */ + pgstat_fetch_global(); + + /* Initialize values and nulls arrays */ + MemSet(values, 0, sizeof(values)); + MemSet(nulls, 0, sizeof(nulls));
why not instead declare values[2] = {0}? Obviously not important.
+ tuple_store = + tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random, + false, work_mem);
Huh, why do we need random?
Greetings,
Andres Freund
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
0001-pg_stat_sql.patchapplication/octet-stream; name=0001-pg_stat_sql.patchDownload
From 81e12dbf439bb8611794df9b63018649dec669d3 Mon Sep 17 00:00:00 2001
From: Alex Zhao <alexyuzhao@gmail.com>
Date: Tue, 20 Aug 2019 15:00:47 +1000
Subject: [PATCH] pg_stat_sql
---
doc/src/sgml/config.sgml | 15 +
doc/src/sgml/monitoring.sgml | 42 +++
src/backend/catalog/system_views.sql | 4 +
src/backend/postmaster/pgstat.c | 57 ++-
src/backend/storage/ipc/ipci.c | 2 +
src/backend/storage/lmgr/lwlocknames.txt | 1 +
src/backend/tcop/postgres.c | 18 +-
src/backend/tcop/utility.c | 521 ++++++++++++++++++++++++--
src/backend/utils/adt/pgstatfuncs.c | 61 +++
src/backend/utils/misc/guc.c | 10 +-
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/catalog/pg_proc.dat | 9 +-
src/include/pgstat.h | 16 +-
src/include/tcop/utility.h | 201 ++++++++++
src/test/regress/expected/rules.out | 3 +
src/test/regress/expected/stats.out | 64 ++++
src/test/regress/sql/stats.sql | 24 ++
17 files changed, 1019 insertions(+), 30 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 47b12c6..d1c225c 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -6819,6 +6819,21 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
</listitem>
</varlistentry>
+ <varlistentry id="guc-track-statement-statistics" xreflabel="track_statement_statistics">
+ <term><varname>track_statement_statistics</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>track_statement_statistics</> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Enables collection of different SQL statement statistics that are
+ executed on the instance. This parameter is off by default. Only
+ superusers can change this setting.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-stats-temp-directory" xreflabel="stats_temp_directory">
<term><varname>stats_temp_directory</varname> (<type>string</type>)
<indexterm>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 828e908..5311aa3 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -361,6 +361,13 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
</row>
<row>
+ <entry><structname>pg_stat_sql</><indexterm><primary>pg_stat_sql</primary></indexterm></entry>
+ <entry>Shows statistics about the SQL statements that are
+ executed on the instance.
+ See <xref linkend="pg-stat-sql-view"> for details.</entry>
+ </row>
+
+ <row>
<entry><structname>pg_stat_progress_cluster</structname><indexterm><primary>pg_stat_progress_cluster</primary></indexterm></entry>
<entry>One row for each backend running
<command>CLUSTER</command> or <command>VACUUM FULL</command>, showing current progress.
@@ -3184,6 +3191,39 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
controls exactly which functions are tracked.
</para>
+ <table id="pg-stat-sql-view" xreflabel="pg_stat_sql">
+ <title><structname>pg_stat_sql</structname> View</title>
+ <tgroup cols="3">
+ <thead>
+ <row>
+ <entry>Column</entry>
+ <entry>Type</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry><structfield>tag</></entry>
+ <entry><type>text</></entry>
+ <entry>Tag of the SQL statement</entry>
+ </row>
+ <row>
+ <entry><structfield>count</></entry>
+ <entry><type>bigint</></entry>
+ <entry>Number of times the SQL statement is executed</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The <structname>pg_stat_sql</structname> view will contain statistics
+ about number of SQL statements that are executed on the instance.
+ The <xref linkend="guc-track-sql"> parameter controls the SQL statement
+ execution statistics.
+ </para>
+
</sect2>
<sect2 id="monitoring-stats-functions">
@@ -3279,6 +3319,8 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
counters shown in the <structname>pg_stat_bgwriter</structname> view.
Calling <literal>pg_stat_reset_shared('archiver')</literal> will zero all the
counters shown in the <structname>pg_stat_archiver</structname> view.
+ Calling <literal>pg_stat_reset_shared('sqlstmt')</> will zero all the
+ counters shown in the <structname>pg_stat_sql</> view.
</entry>
</row>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 9fe4a47..ed5ee05 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1061,6 +1061,10 @@ REVOKE ALL ON pg_subscription FROM public;
GRANT SELECT (subdbid, subname, subowner, subenabled, subslotname, subpublications)
ON pg_subscription TO public;
+CREATE VIEW pg_stat_sql AS
+ SELECT * FROM pg_stat_sql();
+
+REVOKE EXECUTE ON FUNCTION pg_stat_sql() FROM public;
--
-- We have a few function definitions in here, too.
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 011076c..041aeb3 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -124,6 +124,7 @@
*/
bool pgstat_track_activities = false;
bool pgstat_track_counts = false;
+bool pgstat_track_statement_statistics = false;
int pgstat_track_functions = TRACK_FUNC_OFF;
int pgstat_track_activity_query_size = 1024;
@@ -142,6 +143,13 @@ char *pgstat_stat_tmpname = NULL;
*/
PgStat_MsgBgWriter BgWriterStats;
+
+/*
+ * Global SQL statement counters gathered that are stored in
+ * shared memory.
+ */
+uint64* pgstat_sql_counts = NULL;
+
/* ----------
* Local data
* ----------
@@ -631,6 +639,32 @@ startup_failed:
SetConfigOption("track_counts", "off", PGC_INTERNAL, PGC_S_OVERRIDE);
}
+
+/*
+ * Initialization of shared memory for pgstat_sql_count
+ */
+Size
+pgstat_sql_shmem_size(void)
+{
+ return sizeof(uint64) * PGSTAT_SQLSTMT_SIZE;
+}
+
+void
+pgstat_sql_shmem_init(void)
+{
+ bool foundWALWrites;
+
+ pgstat_sql_counts = (uint64 *)
+ ShmemInitStruct("pgstat sql counter", pgstat_sql_shmem_size(), &foundWALWrites);
+
+ if (!foundWALWrites)
+ {
+ MemSet(pgstat_sql_counts, 0, pgstat_sql_shmem_size());
+ }
+}
+
+
+
/*
* subroutine for pgstat_reset_all
*/
@@ -1341,11 +1375,21 @@ pgstat_reset_shared_counters(const char *target)
msg.m_resettarget = RESET_ARCHIVER;
else if (strcmp(target, "bgwriter") == 0)
msg.m_resettarget = RESET_BGWRITER;
+ else if (strcmp(target, "sqlstmt") == 0)
+ {
+ /*
+ * Reset the pgstat sql counters. These statistics are not reset
+ * by the stats collector because they reside in shared memory.
+ */
+ LWLockAcquire(WALWriteLock, LW_EXCLUSIVE);
+ memset(pgstat_sql_counts, 0, pgstat_sql_shmem_size());
+ LWLockRelease(WALWriteLock);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized reset target: \"%s\"", target),
- errhint("Target must be \"archiver\" or \"bgwriter\".")));
+ errhint("Target must be \"archiver\" or \"bgwriter\" or \"sqlstmt\".")));
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSHAREDCOUNTER);
pgstat_send(&msg, sizeof(msg));
@@ -4415,6 +4459,17 @@ pgstat_send_bgwriter(void)
}
+/*
+ * Count SQL statement for pg_stat_sql view
+ */
+void
+pgstat_count_sqlstmt(StatSqlType sqlType)
+{
+ LWLockAcquire(PGSTATSqlLock, LW_EXCLUSIVE);
+ pgstat_sql_counts[sqlType] += 1;
+ LWLockRelease(PGSTATSqlLock);
+}
+
/* ----------
* PgstatCollectorMain() -
*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 8853706..a92deed 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -147,6 +147,7 @@ CreateSharedMemoryAndSemaphores(void)
size = add_size(size, BTreeShmemSize());
size = add_size(size, SyncScanShmemSize());
size = add_size(size, AsyncShmemSize());
+ size = add_size(size, pgstat_sql_shmem_size());
#ifdef EXEC_BACKEND
size = add_size(size, ShmemBackendArraySize());
#endif
@@ -212,6 +213,7 @@ CreateSharedMemoryAndSemaphores(void)
* Set up xlog, clog, and buffers
*/
XLOGShmemInit();
+ pgstat_sql_shmem_init();
CLOGShmemInit();
CommitTsShmemInit();
SUBTRANSShmemInit();
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index db47843..50214d6 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -49,3 +49,4 @@ MultiXactTruncationLock 41
OldSnapshotTimeMapLock 42
LogicalRepWorkerLock 43
CLogTruncationLock 44
+PGSTATSqlLock 45
\ No newline at end of file
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index e8d8e6f..ee0aaf6 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1069,6 +1069,7 @@ exec_simple_query(const char *query_string)
RawStmt *parsetree = lfirst_node(RawStmt, parsetree_item);
bool snapshot_set = false;
const char *commandTag;
+ StatSqlType eSqlType;
char completionTag[COMPLETION_TAG_BUFSIZE];
MemoryContext per_parsetree_context = NULL;
List *querytree_list,
@@ -1083,8 +1084,7 @@ exec_simple_query(const char *query_string)
* do any special start-of-SQL-command processing needed by the
* destination.
*/
- commandTag = CreateCommandTag(parsetree->stmt);
-
+ commandTag = CreateCommandTagType(parsetree->stmt, &eSqlType);
set_ps_display(commandTag, false);
BeginCommand(commandTag, dest);
@@ -1240,6 +1240,12 @@ exec_simple_query(const char *query_string)
PortalDrop(portal, false);
+ /*
+ * Count SQL statement for pg_stat_sql view
+ */
+ if (pgstat_track_statement_statistics)
+ pgstat_count_sqlstmt(eSqlType);
+
if (lnext(parsetree_list, parsetree_item) == NULL)
{
/*
@@ -2117,6 +2123,14 @@ exec_execute_message(const char *portal_name, long max_rows)
receiver->rDestroy(receiver);
+ /*
+ * Count SQL Statement for pgx_stat_sql
+ */
+ if (pgstat_track_statement_statistics && !execute_is_fetch)
+ {
+ pgstat_count_sqlstmt(T_Stat_EXECUTE);
+ }
+
if (completed)
{
if (is_xact_command)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c6faa66..c2faf3a 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -86,6 +86,193 @@ static void ProcessUtilitySlow(ParseState *pstate,
char *completionTag);
static void ExecDropStmt(DropStmt *stmt, bool isTopLevel);
+const char* g_str_sql_type[] =
+{
+ [T_Stat_INSERT] = "INSERT",
+ [T_Stat_DELETE] = "DELETE",
+ [T_Stat_UPDATE] = "UPDATE",
+ [T_Stat_SELECT] = "SELECT",
+ [T_Stat_BEGIN] = "BEGIN",
+ [T_Stat_START_TRANSACTION] = "START TRANSACTION",
+ [T_Stat_COMMIT] = "COMMIT",
+ [T_Stat_ROLLBACK] = "ROLLBACK",
+ [T_Stat_SAVEPOINT] = "SAVEPOINT",
+ [T_Stat_RELEASE] = "RELEASE",
+ [T_Stat_PREPARE_TRANSACTION] = "PREPARE TRANSACTION",
+ [T_Stat_COMMIT_PREPARED] = "COMMIT PREPARED",
+ [T_Stat_ROLLBACK_PREPARED] = "ROLLBACK PREPARED",
+ [T_Stat_DECLARE_CURSOR] = "DECLARE CURSOR",
+ [T_Stat_CLOSE_CURSOR_ALL] = "CLOSE CURSOR ALL",
+ [T_Stat_CLOSE_CURSOR] = "CLOSE CURSOR",
+ [T_Stat_MOVE] = "MOVE",
+ [T_Stat_FETCH] = "FETCH",
+ [T_Stat_CREATE_DOMAIN] = "CREATE DOMAIN",
+ [T_Stat_CREATE_SCHEMA] = "CREATE SCHEMA",
+ [T_Stat_CREATE_TABLE] = "CREATE TABLE",
+ [T_Stat_CREATE_TABLESPACE] = "CREATE TABLESPACE",
+ [T_Stat_DROP_TABLESPACE] = "DROP TABLESPACE",
+ [T_Stat_ALTER_TABLESPACE] = "ALTER TABLESPACE",
+ [T_Stat_CREATE_EXTENSION] = "CREATE EXTENSION",
+ [T_Stat_ALTER_EXTENSION] = "ALTER EXTENSION",
+ [T_Stat_CREATE_FOREIGN_DATA_WRAPPER] = "CREATE FOREIGN DATA WRAPPER",
+ [T_Stat_ALTER_FOREIGN_DATA_WRAPPER] = "ALTER FOREIGN DATA WRAPPER",
+ [T_Stat_CREATE_SERVER] = "CREATE SERVER",
+ [T_Stat_ALTER_SERVER] = "ALTER SERVER",
+ [T_Stat_CREATE_USER_MAPPING] = "CREATE USER MAPPING",
+ [T_Stat_ALTER_USER_MAPPING] = "ALTER USER MAPPING",
+ [T_Stat_DROP_USER_MAPPING] = "DROP USER MAPPING",
+ [T_Stat_CREATE_FOREIGN_TABLE] = "CREATE FOREIGN TABLE",
+ [T_Stat_IMPORT_FOREIGN_SCHEMA] = "IMPORT FOREIGN SCHEMA",
+ [T_Stat_DROP_TABLE] = "DROP TABLE",
+ [T_Stat_DROP_SEQUENCE] = "DROP SEQUENCE",
+ [T_Stat_DROP_VIEW] = "DROP VIEW",
+ [T_Stat_DROP_MATERIALIZED_VIEW] = "DROP MATERIALIZED VIEW",
+ [T_Stat_DROP_INDEX] = "DROP INDEX",
+ [T_Stat_DROP_TYPE] = "DROP TYPE",
+ [T_Stat_DROP_DOMAIN] = "DROP DOMAIN",
+ [T_Stat_DROP_COLLATION] = "DROP COLLATION",
+ [T_Stat_DROP_CONVERSION] = "DROP CONVERSION",
+ [T_Stat_DROP_SCHEMA] = "DROP SCHEMA",
+ [T_Stat_DROP_TEXT_SEARCH_PARSER] = "DROP TEXT SEARCH PARSER",
+ [T_Stat_DROP_TEXT_SEARCH_DICTIONARY] = "DROP TEXT SEARCH DICTIONARY",
+ [T_Stat_DROP_TEXT_SEARCH_TEMPLATE] = "DROP TEXT SEARCH TEMPLATE",
+ [T_Stat_DROP_DROP_TEXT_SEARCH_CONFIGURATION] = "DROP TEXT SEARCH CONFIGURATION",
+ [T_Stat_DROP_DROP_FOREIGN_TABLE] = "DROP FOREIGN TABLE",
+ [T_Stat_DROP_EXTENSION] = "DROP EXTENSION",
+ [T_Stat_DROP_FUNCTION] = "DROP FUNCTION",
+ [T_Stat_DROP_PROCEDURE] = "DROP PROCEDURE",
+ [T_Stat_DROP_ROUTINE] = "DROP ROUTINE",
+ [T_Stat_DROP_AGGREGATE] = "DROP AGGREGATE",
+ [T_Stat_DROP_OPERATOR] = "DROP OPERATOR",
+ [T_Stat_DROP_LANGUAGE] = "DROP LANGUAGE",
+ [T_Stat_DROP_CAST] = "DROP CAST",
+ [T_Stat_DROP_TRIGGER] = "DROP TRIGGER",
+ [T_Stat_DROP_EVENT_TRIGGER] = "DROP EVENT TRIGGER",
+ [T_Stat_DROP_RULE] = "DROP RULE",
+ [T_Stat_DROP_FOREIGN_DATA_WRAPPER] = "DROP FOREIGN DATA WRAPPER",
+ [T_Stat_DROP_SERVER] = "DROP SERVER",
+ [T_Stat_DROP_OPERATOR_CLASS] = "DROP OPERATOR CLASS",
+ [T_Stat_DROP_OPERATOR_FAMILY] = "DROP OPERATOR FAMILY",
+ [T_Stat_DROP_POLICY] = "DROP POLICY",
+ [T_Stat_DROP_TRANSFORM] = "DROP TRANSFORM",
+ [T_Stat_DROP_ACCESS_METHOD] = "DROP ACCESS METHOD",
+ [T_Stat_DROP_PUBLICATION] = "DROP PUBLICATION",
+ [T_Stat_DROP_STATISTICS] = "DROP STATISTICS",
+ [T_Stat_TRUNCATE_TABLE] = "TRUNCATE TABLE",
+ [T_Stat_COMMENT] = "COMMENT",
+ [T_Stat_SECURITY_LABEL] = "SECURITY LABEL",
+ [T_Stat_COPY] = "COPY",
+ [T_Stat_ALTER_AGGREGATE] = "ALTER AGGREGATE",
+ [T_Stat_ALTER_TYPE] = "ALTER TYPE",
+ [T_Stat_ALTER_CAST] = "ALTER CAST",
+ [T_Stat_ALTER_COLLATION] = "ALTER COLLATION",
+ [T_Stat_ALTER_TABLE] = "ALTER TABLE",
+ [T_Stat_ALTER_CONVERSION] = "ALTER CONVERSION",
+ [T_Stat_ALTER_DATABASE] = "ALTER DATABASE",
+ [T_Stat_ALTER_DOMAIN] = "ALTER DOMAIN",
+ [T_Stat_ALTER_FOREIGN_TABLE] = "ALTER FOREIGN TABLE",
+ [T_Stat_ALTER_FUNCTION] = "ALTER FUNCTION",
+ [T_Stat_ALTER_INDEX] = "ALTER INDEX",
+ [T_Stat_ALTER_LANGUAGE] = "ALTER LANGUAGE",
+ [T_Stat_ALTER_LARGE_OBJECT] = "ALTER LARGE OBJECT",
+ [T_Stat_ALTER_OPERATOR_CLASS] = "ALTER OPERATOR CLASS",
+ [T_Stat_ALTER_OPERATOR] = "ALTER OPERATOR",
+ [T_Stat_ALTER_OPERATOR_FAMILY] = "ALTER OPERATOR FAMILY",
+ [T_Stat_ALTER_POLICY] = "ALTER POLICY",
+ [T_Stat_ALTER_PROCEDURE] = "ALTER PROCEDURE",
+ [T_Stat_ALTER_ROLE] = "ALTER ROLE",
+ [T_Stat_ALTER_ROUTINE] = "ALTER ROUTINE",
+ [T_Stat_ALTER_RULE] = "ALTER RULE",
+ [T_Stat_ALTER_SCHEMA] = "ALTER SCHEMA",
+ [T_Stat_ALTER_SEQUENCE] = "ALTER SEQUENCE",
+ [T_Stat_ALTER_TRIGGER] = "ALTER TRIGGER",
+ [T_Stat_ALTER_EVENT_TRIGGER] = "ALTER EVENT TRIGGER",
+ [T_Stat_ALTER_TEXT_SEARCH_CONFIGURATION] = "ALTER TEXT SEARCH CONFIGURATION",
+ [T_Stat_ALTER_TEXT_SEARCH_DICTIONARY] = "ALTER TEXT SEARCH DICTIONARY",
+ [T_Stat_ALTER_TEXT_SEARCH_PARSER] = "ALTER TEXT SEARCH PARSER",
+ [T_Stat_ALTER_TEXT_SEARCH_TEMPLATE] = "ALTER TEXT SEARCH TEMPLATE",
+ [T_Stat_ALTER_VIEW] = "ALTER VIEW",
+ [T_Stat_ALTER_MATERIALIZED_VIEW] = "ALTER MATERIALIZED VIEW",
+ [T_Stat_ALTER_PUBLICATION] = "ALTER PUBLICATION",
+ [T_Stat_ALTER_SUBSCRIPTION] = "ALTER SUBSCRIPTION",
+ [T_Stat_ALTER_STATISTICS] = "ALTER STATISTICS",
+ [T_Stat_GRANT] = "GRANT",
+ [T_Stat_REVOKE] = "REVOKE",
+ [T_Stat_GRANT_ROLE] = "GRANT ROLE",
+ [T_Stat_REVOKE_ROLE] = "REVOKE ROLE",
+ [T_Stat_ALTER_DEFAULT_PRIVILEGES] = "ALTER DEFAULT PRIVILEGES",
+ [T_Stat_CREATE_AGGREGATE] = "CREATE AGGREGATE",
+ [T_Stat_CREATE_OPERATOR] = "CREATE OPERATOR",
+ [T_Stat_CREATE_TYPE] = "CREATE TYPE",
+ [T_Stat_CREATE_TEXT_SEARCH_PARSER] = "CREATE TEXT SEARCH PARSER",
+ [T_Stat_CREATE_TEXT_SEARCH_DICTIONARY] = "CREATE TEXT SEARCH DICTIONARY",
+ [T_Stat_CREATE_TEXT_SEARCH_TEMPLATE] = "CREATE TEXT SEARCH TEMPLATE",
+ [T_Stat_CREATE_TEXT_SEARCH_CONFIGURATION] = "CREATE TEXT SEARCH CONFIGURATION",
+ [T_Stat_CREATE_COLLATION] = "CREATE COLLATION",
+ [T_Stat_CREATE_ACCESS_METHOD] = "CREATE ACCESS METHOD",
+ [T_Stat_CREATE_VIEW] = "CREATE VIEW",
+ [T_Stat_CREATE_PROCEDURE] = "CREATE PROCEDURE",
+ [T_Stat_CREATE_FUNCTION] = "CREATE FUNCTION",
+ [T_Stat_CREATE_INDEX] = "CREATE INDEX",
+ [T_Stat_CREATE_RULE] = "CREATE RULE",
+ [T_Stat_CREATE_SEQUENCE] = "CREATE SEQUENCE",
+ [T_Stat_DO] = "DO",
+ [T_Stat_CREATE_DATABASE] = "CREATE DATABASE",
+ [T_Stat_DROP_DATABASE] = "DROP DATABASE",
+ [T_Stat_NOTIFY] = "NOTIFY",
+ [T_Stat_LISTEN] = "LISTEN",
+ [T_Stat_UNLISTEN] = "UNLISTEN",
+ [T_Stat_LOAD] = "LOAD",
+ [T_Stat_CALL] = "CALL",
+ [T_Stat_CLUSTER] = "CLUSTER",
+ [T_Stat_VACUUM] = "VACUUM",
+ [T_Stat_ANALYZE] = "ANALYZE",
+ [T_Stat_EXPLAIN] = "EXPLAIN",
+ [T_Stat_SELECT_INTO] = "SELECT INTO",
+ [T_Stat_CREATE_TABLE_AS] = "CREATE TABLE AS",
+ [T_Stat_CREATE_MATERIALIZED_VIEW] = "CREATE MATERIALIZED VIEW",
+ [T_Stat_REFRESH_MATERIALIZED_VIEW] = "REFRESH MATERIALIZED VIEW",
+ [T_Stat_ALTER_SYSTEM] = "ALTER SYSTEM",
+ [T_Stat_SET] = "SET",
+ [T_Stat_RESET] = "RESET",
+ [T_Stat_SHOW] = "SHOW",
+ [T_Stat_DISCARD_ALL] = "DISCARD ALL",
+ [T_Stat_DISCARD_PLANS] = "DISCARD PLANS",
+ [T_Stat_DISCARD_TEMP] = "DISCARD TEMP",
+ [T_Stat_DISCARD_SEQUENCES] = "DISCARD SEQUENCES",
+ [T_Stat_CREATE_TRANSFORM] = "CREATE TRANSFORM",
+ [T_Stat_CREATE_TRIGGER] = "CREATE TRIGGER",
+ [T_Stat_CREATE_EVENT_TRIGGER] = "CREATE EVENT TRIGGER",
+ [T_Stat_CREATE_LANGUAGE] = "CREATE LANGUAGE",
+ [T_Stat_CREATE_ROLE] = "CREATE ROLE",
+ [T_Stat_DROP_ROLE] = "DROP ROLE",
+ [T_Stat_DROP_OWNED] = "DROP OWNED",
+ [T_Stat_REASSIGN_OWNED] = "REASSIGN OWNED",
+ [T_Stat_LOCK_TABLE] = "LOCK TABLE",
+ [T_Stat_SET_CONSTRAINTS] = "SET CONSTRAINTS",
+ [T_Stat_CHECKPOINT] = "CHECKPOINT",
+ [T_Stat_REINDEX] = "REINDEX",
+ [T_Stat_CREATE_CONVERSION] = "CREATE CONVERSION",
+ [T_Stat_CREATE_CAST] = "CREATE CAST",
+ [T_Stat_CREATE_OPERATOR_CLASS] = "CREATE OPERATOR CLASS",
+ [T_Stat_CREATE_OPERATOR_FAMILY] = "CREATE OPERATOR FAMILY",
+ [T_Stat_CREATE_POLICY] = "CREATE POLICY",
+ [T_Stat_CREATE_PUBLICATION] = "CREATE PUBLICATION",
+ [T_Stat_CREATE_SUBSCRIPTION] = "CREATE SUBSCRIPTION",
+ [T_Stat_DROP_SUBSCRIPTION] = "DROP SUBSCRIPTION",
+ [T_Stat_PREPARE] = "PREPARE",
+ [T_Stat_EXECUTE] = "EXECUTE",
+ [T_Stat_CREATE_STATISTICS] = "CREATE STATISTICS",
+ [T_Stat_DEALLOCATE_ALL] = "DEALLOCATE ALL",
+ [T_Stat_DEALLOCATE] = "DEALLOCATE",
+ [T_Stat_SELECT_FOR_KEY_SHARE] = "SELECT FOR KEY SHARE",
+ [T_Stat_SELECT_FOR_SHARE] = "SELECT FOR SHARE",
+ [T_Stat_SELECT_FOR_NO_KEY_UPDATE] = "SELECT FOR NO KEY UPDATE",
+ [T_Stat_SELECT_FOR_UPDATE] = "SELECT FOR UPDATE",
+ [T_Stat_UNKNOWN] = "???"
+};
+
+
/*
* CommandIsReadOnly: is an executable query read-only?
@@ -1946,13 +2133,13 @@ UtilityContainsQuery(Node *parsetree)
/*
- * AlterObjectTypeCommandTag
- * helper function for CreateCommandTag
+ * AlterObjectTypeCommandTagType
+ * helper function for CreateCommandTagType
*
* This covers most cases where ALTER is used with an ObjectType enum.
*/
static const char *
-AlterObjectTypeCommandTag(ObjectType objtype)
+AlterObjectTypeCommandTagType(ObjectType objtype, StatSqlType* pESqlType)
{
const char *tag;
@@ -1960,128 +2147,169 @@ AlterObjectTypeCommandTag(ObjectType objtype)
{
case OBJECT_AGGREGATE:
tag = "ALTER AGGREGATE";
+ *pESqlType = T_Stat_ALTER_AGGREGATE;
break;
case OBJECT_ATTRIBUTE:
tag = "ALTER TYPE";
+ *pESqlType = T_Stat_ALTER_TYPE;
break;
case OBJECT_CAST:
tag = "ALTER CAST";
+ *pESqlType = T_Stat_ALTER_CAST;
break;
case OBJECT_COLLATION:
tag = "ALTER COLLATION";
+ *pESqlType = T_Stat_ALTER_COLLATION;
break;
case OBJECT_COLUMN:
tag = "ALTER TABLE";
+ *pESqlType = T_Stat_ALTER_TABLE;
break;
case OBJECT_CONVERSION:
tag = "ALTER CONVERSION";
+ *pESqlType = T_Stat_ALTER_CONVERSION;
break;
case OBJECT_DATABASE:
tag = "ALTER DATABASE";
+ *pESqlType = T_Stat_ALTER_DATABASE;
break;
case OBJECT_DOMAIN:
case OBJECT_DOMCONSTRAINT:
tag = "ALTER DOMAIN";
+ *pESqlType = T_Stat_ALTER_DOMAIN;
break;
case OBJECT_EXTENSION:
tag = "ALTER EXTENSION";
+ *pESqlType = T_Stat_ALTER_EXTENSION;
break;
case OBJECT_FDW:
tag = "ALTER FOREIGN DATA WRAPPER";
+ *pESqlType = T_Stat_ALTER_FOREIGN_DATA_WRAPPER;
break;
case OBJECT_FOREIGN_SERVER:
tag = "ALTER SERVER";
+ *pESqlType = T_Stat_ALTER_SERVER;
break;
case OBJECT_FOREIGN_TABLE:
tag = "ALTER FOREIGN TABLE";
+ *pESqlType = T_Stat_ALTER_FOREIGN_TABLE;
break;
case OBJECT_FUNCTION:
tag = "ALTER FUNCTION";
+ *pESqlType = T_Stat_ALTER_FUNCTION;
break;
case OBJECT_INDEX:
tag = "ALTER INDEX";
+ *pESqlType = T_Stat_ALTER_INDEX;
break;
case OBJECT_LANGUAGE:
tag = "ALTER LANGUAGE";
+ *pESqlType = T_Stat_ALTER_LANGUAGE;
break;
case OBJECT_LARGEOBJECT:
tag = "ALTER LARGE OBJECT";
+ *pESqlType = T_Stat_ALTER_LARGE_OBJECT;
break;
case OBJECT_OPCLASS:
tag = "ALTER OPERATOR CLASS";
+ *pESqlType = T_Stat_ALTER_OPERATOR_CLASS;
break;
case OBJECT_OPERATOR:
tag = "ALTER OPERATOR";
+ *pESqlType = T_Stat_ALTER_OPERATOR;
break;
case OBJECT_OPFAMILY:
tag = "ALTER OPERATOR FAMILY";
+ *pESqlType = T_Stat_ALTER_OPERATOR_FAMILY;
break;
case OBJECT_POLICY:
tag = "ALTER POLICY";
+ *pESqlType = T_Stat_ALTER_POLICY;
break;
case OBJECT_PROCEDURE:
tag = "ALTER PROCEDURE";
+ *pESqlType = T_Stat_ALTER_PROCEDURE;
break;
case OBJECT_ROLE:
tag = "ALTER ROLE";
+ *pESqlType = T_Stat_ALTER_ROLE;
break;
case OBJECT_ROUTINE:
tag = "ALTER ROUTINE";
+ *pESqlType = T_Stat_ALTER_ROUTINE;
break;
case OBJECT_RULE:
tag = "ALTER RULE";
+ *pESqlType = T_Stat_ALTER_RULE;
break;
case OBJECT_SCHEMA:
tag = "ALTER SCHEMA";
+ *pESqlType = T_Stat_ALTER_SCHEMA;
break;
case OBJECT_SEQUENCE:
tag = "ALTER SEQUENCE";
+ *pESqlType = T_Stat_ALTER_SEQUENCE;
break;
case OBJECT_TABLE:
case OBJECT_TABCONSTRAINT:
tag = "ALTER TABLE";
+ *pESqlType = T_Stat_ALTER_TABLE;
break;
case OBJECT_TABLESPACE:
tag = "ALTER TABLESPACE";
+ *pESqlType = T_Stat_ALTER_TABLESPACE;
break;
case OBJECT_TRIGGER:
tag = "ALTER TRIGGER";
+ *pESqlType = T_Stat_ALTER_TRIGGER;
break;
case OBJECT_EVENT_TRIGGER:
tag = "ALTER EVENT TRIGGER";
+ *pESqlType = T_Stat_ALTER_EVENT_TRIGGER;
break;
case OBJECT_TSCONFIGURATION:
tag = "ALTER TEXT SEARCH CONFIGURATION";
+ *pESqlType = T_Stat_ALTER_TEXT_SEARCH_CONFIGURATION;
break;
case OBJECT_TSDICTIONARY:
tag = "ALTER TEXT SEARCH DICTIONARY";
+ *pESqlType = T_Stat_ALTER_TEXT_SEARCH_DICTIONARY;
break;
case OBJECT_TSPARSER:
tag = "ALTER TEXT SEARCH PARSER";
+ *pESqlType = T_Stat_ALTER_TEXT_SEARCH_PARSER;
break;
case OBJECT_TSTEMPLATE:
tag = "ALTER TEXT SEARCH TEMPLATE";
+ *pESqlType = T_Stat_ALTER_TEXT_SEARCH_TEMPLATE;
break;
case OBJECT_TYPE:
tag = "ALTER TYPE";
+ *pESqlType = T_Stat_ALTER_TYPE;
break;
case OBJECT_VIEW:
tag = "ALTER VIEW";
+ *pESqlType = T_Stat_ALTER_VIEW;
break;
case OBJECT_MATVIEW:
tag = "ALTER MATERIALIZED VIEW";
+ *pESqlType = T_Stat_ALTER_MATERIALIZED_VIEW;
break;
case OBJECT_PUBLICATION:
tag = "ALTER PUBLICATION";
+ *pESqlType = T_Stat_ALTER_PUBLICATION;
break;
case OBJECT_SUBSCRIPTION:
tag = "ALTER SUBSCRIPTION";
+ *pESqlType = T_Stat_ALTER_SUBSCRIPTION;
break;
case OBJECT_STATISTIC_EXT:
tag = "ALTER STATISTICS";
+ *pESqlType = T_Stat_ALTER_STATISTICS;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
break;
}
@@ -2089,19 +2317,15 @@ AlterObjectTypeCommandTag(ObjectType objtype)
}
/*
- * CreateCommandTag
- * utility to get a string representation of the command operation,
+ * CreateCommandType
+ * utility to get a StatSqlType representation of the command operation,
* given either a raw (un-analyzed) parsetree, an analyzed Query,
* or a PlannedStmt.
- *
- * This must handle all command types, but since the vast majority
- * of 'em are utility commands, it seems sensible to keep it here.
- *
- * NB: all result strings must be shorter than COMPLETION_TAG_BUFSIZE.
- * Also, the result must point at a true constant (permanent storage).
+ * This function is copy-and-modify from CreateCommandTag,
+ * so if CreateCommandTag changed, this function should be changed accordingly.
*/
-const char *
-CreateCommandTag(Node *parsetree)
+const char*
+CreateCommandTagType(Node *parsetree, StatSqlType* pESqlType)
{
const char *tag;
@@ -2109,24 +2333,28 @@ CreateCommandTag(Node *parsetree)
{
/* recurse if we're given a RawStmt */
case T_RawStmt:
- tag = CreateCommandTag(((RawStmt *) parsetree)->stmt);
+ tag = CreateCommandTagType(((RawStmt *) parsetree)->stmt, pESqlType);
break;
/* raw plannable queries */
case T_InsertStmt:
tag = "INSERT";
+ *pESqlType = T_Stat_INSERT;
break;
case T_DeleteStmt:
tag = "DELETE";
+ *pESqlType = T_Stat_DELETE;
break;
case T_UpdateStmt:
tag = "UPDATE";
+ *pESqlType = T_Stat_UPDATE;
break;
case T_SelectStmt:
tag = "SELECT";
+ *pESqlType = T_Stat_SELECT;
break;
/* utility statements --- same whether raw or cooked */
@@ -2138,43 +2366,53 @@ CreateCommandTag(Node *parsetree)
{
case TRANS_STMT_BEGIN:
tag = "BEGIN";
+ *pESqlType = T_Stat_BEGIN;
break;
case TRANS_STMT_START:
tag = "START TRANSACTION";
+ *pESqlType = T_Stat_START_TRANSACTION;
break;
case TRANS_STMT_COMMIT:
tag = "COMMIT";
+ *pESqlType = T_Stat_COMMIT;
break;
case TRANS_STMT_ROLLBACK:
case TRANS_STMT_ROLLBACK_TO:
tag = "ROLLBACK";
+ *pESqlType = T_Stat_ROLLBACK;
break;
case TRANS_STMT_SAVEPOINT:
tag = "SAVEPOINT";
+ *pESqlType = T_Stat_SAVEPOINT;
break;
case TRANS_STMT_RELEASE:
tag = "RELEASE";
+ *pESqlType = T_Stat_RELEASE;
break;
case TRANS_STMT_PREPARE:
tag = "PREPARE TRANSACTION";
+ *pESqlType = T_Stat_PREPARE_TRANSACTION;
break;
case TRANS_STMT_COMMIT_PREPARED:
tag = "COMMIT PREPARED";
+ *pESqlType = T_Stat_COMMIT_PREPARED;
break;
case TRANS_STMT_ROLLBACK_PREPARED:
tag = "ROLLBACK PREPARED";
+ *pESqlType = T_Stat_ROLLBACK_PREPARED;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
break;
}
}
@@ -2182,6 +2420,7 @@ CreateCommandTag(Node *parsetree)
case T_DeclareCursorStmt:
tag = "DECLARE CURSOR";
+ *pESqlType = T_Stat_DECLARE_CURSOR;
break;
case T_ClosePortalStmt:
@@ -2189,90 +2428,114 @@ CreateCommandTag(Node *parsetree)
ClosePortalStmt *stmt = (ClosePortalStmt *) parsetree;
if (stmt->portalname == NULL)
+ {
tag = "CLOSE CURSOR ALL";
+ *pESqlType = T_Stat_CLOSE_CURSOR_ALL;
+ }
else
+ {
tag = "CLOSE CURSOR";
+ *pESqlType = T_Stat_CLOSE_CURSOR;
+ }
}
break;
case T_FetchStmt:
{
FetchStmt *stmt = (FetchStmt *) parsetree;
-
tag = (stmt->ismove) ? "MOVE" : "FETCH";
+ *pESqlType = (stmt->ismove) ? T_Stat_MOVE : T_Stat_FETCH;
}
break;
case T_CreateDomainStmt:
tag = "CREATE DOMAIN";
+ *pESqlType = T_Stat_CREATE_DOMAIN;
break;
case T_CreateSchemaStmt:
tag = "CREATE SCHEMA";
+ *pESqlType = T_Stat_CREATE_SCHEMA;
break;
case T_CreateStmt:
tag = "CREATE TABLE";
+ *pESqlType = T_Stat_CREATE_TABLE;
break;
case T_CreateTableSpaceStmt:
tag = "CREATE TABLESPACE";
+ *pESqlType = T_Stat_CREATE_TABLESPACE;
break;
case T_DropTableSpaceStmt:
tag = "DROP TABLESPACE";
+ *pESqlType = T_Stat_DROP_TABLESPACE;
break;
case T_AlterTableSpaceOptionsStmt:
tag = "ALTER TABLESPACE";
+ *pESqlType = T_Stat_ALTER_TABLESPACE;
break;
case T_CreateExtensionStmt:
tag = "CREATE EXTENSION";
+ *pESqlType = T_Stat_CREATE_EXTENSION;
break;
case T_AlterExtensionStmt:
tag = "ALTER EXTENSION";
+ *pESqlType = T_Stat_ALTER_EXTENSION;
break;
case T_AlterExtensionContentsStmt:
tag = "ALTER EXTENSION";
+ *pESqlType = T_Stat_ALTER_EXTENSION;
break;
case T_CreateFdwStmt:
tag = "CREATE FOREIGN DATA WRAPPER";
+ *pESqlType = T_Stat_CREATE_FOREIGN_DATA_WRAPPER;
break;
case T_AlterFdwStmt:
tag = "ALTER FOREIGN DATA WRAPPER";
+ *pESqlType = T_Stat_ALTER_FOREIGN_DATA_WRAPPER;
break;
case T_CreateForeignServerStmt:
tag = "CREATE SERVER";
+ *pESqlType = T_Stat_CREATE_SERVER;
break;
case T_AlterForeignServerStmt:
tag = "ALTER SERVER";
+ *pESqlType = T_Stat_ALTER_SERVER;
break;
case T_CreateUserMappingStmt:
tag = "CREATE USER MAPPING";
+ *pESqlType = T_Stat_CREATE_USER_MAPPING;
break;
case T_AlterUserMappingStmt:
tag = "ALTER USER MAPPING";
+ *pESqlType = T_Stat_ALTER_USER_MAPPING;
break;
case T_DropUserMappingStmt:
tag = "DROP USER MAPPING";
+ *pESqlType = T_Stat_DROP_USER_MAPPING;
break;
case T_CreateForeignTableStmt:
tag = "CREATE FOREIGN TABLE";
+ *pESqlType = T_Stat_CREATE_FOREIGN_TABLE;
break;
case T_ImportForeignSchemaStmt:
tag = "IMPORT FOREIGN SCHEMA";
+ *pESqlType = T_Stat_IMPORT_FOREIGN_SCHEMA;
break;
case T_DropStmt:
@@ -2280,156 +2543,197 @@ CreateCommandTag(Node *parsetree)
{
case OBJECT_TABLE:
tag = "DROP TABLE";
+ *pESqlType = T_Stat_DROP_TABLE;
break;
case OBJECT_SEQUENCE:
tag = "DROP SEQUENCE";
+ *pESqlType = T_Stat_DROP_SEQUENCE;
break;
case OBJECT_VIEW:
tag = "DROP VIEW";
+ *pESqlType = T_Stat_DROP_VIEW;
break;
case OBJECT_MATVIEW:
tag = "DROP MATERIALIZED VIEW";
+ *pESqlType = T_Stat_DROP_MATERIALIZED_VIEW;
break;
case OBJECT_INDEX:
tag = "DROP INDEX";
+ *pESqlType= T_Stat_DROP_INDEX;
break;
case OBJECT_TYPE:
tag = "DROP TYPE";
+ *pESqlType = T_Stat_DROP_TYPE;
break;
case OBJECT_DOMAIN:
tag = "DROP DOMAIN";
+ *pESqlType = T_Stat_DROP_DOMAIN;
break;
case OBJECT_COLLATION:
tag = "DROP COLLATION";
+ *pESqlType = T_Stat_DROP_COLLATION;
break;
case OBJECT_CONVERSION:
tag = "DROP CONVERSION";
+ *pESqlType = T_Stat_DROP_CONVERSION;
break;
case OBJECT_SCHEMA:
tag = "DROP SCHEMA";
+ *pESqlType = T_Stat_DROP_SCHEMA;
break;
case OBJECT_TSPARSER:
tag = "DROP TEXT SEARCH PARSER";
+ *pESqlType = T_Stat_DROP_TEXT_SEARCH_PARSER;
break;
case OBJECT_TSDICTIONARY:
tag = "DROP TEXT SEARCH DICTIONARY";
+ *pESqlType = T_Stat_DROP_TEXT_SEARCH_DICTIONARY;
break;
case OBJECT_TSTEMPLATE:
tag = "DROP TEXT SEARCH TEMPLATE";
+ *pESqlType = T_Stat_DROP_TEXT_SEARCH_TEMPLATE;
break;
case OBJECT_TSCONFIGURATION:
tag = "DROP TEXT SEARCH CONFIGURATION";
+ *pESqlType = T_Stat_DROP_DROP_TEXT_SEARCH_CONFIGURATION;
break;
case OBJECT_FOREIGN_TABLE:
tag = "DROP FOREIGN TABLE";
+ *pESqlType = T_Stat_DROP_DROP_FOREIGN_TABLE;
break;
case OBJECT_EXTENSION:
tag = "DROP EXTENSION";
+ *pESqlType = T_Stat_DROP_EXTENSION;
break;
case OBJECT_FUNCTION:
tag = "DROP FUNCTION";
+ *pESqlType = T_Stat_DROP_FUNCTION;
break;
case OBJECT_PROCEDURE:
tag = "DROP PROCEDURE";
+ *pESqlType = T_Stat_DROP_PROCEDURE;
break;
case OBJECT_ROUTINE:
tag = "DROP ROUTINE";
+ *pESqlType = T_Stat_DROP_ROUTINE;
break;
case OBJECT_AGGREGATE:
tag = "DROP AGGREGATE";
+ *pESqlType = T_Stat_DROP_AGGREGATE;
break;
case OBJECT_OPERATOR:
tag = "DROP OPERATOR";
+ *pESqlType = T_Stat_DROP_OPERATOR;
break;
case OBJECT_LANGUAGE:
tag = "DROP LANGUAGE";
+ *pESqlType = T_Stat_DROP_LANGUAGE;
break;
case OBJECT_CAST:
tag = "DROP CAST";
+ *pESqlType = T_Stat_DROP_CAST;
break;
case OBJECT_TRIGGER:
tag = "DROP TRIGGER";
+ *pESqlType = T_Stat_DROP_TRIGGER;
break;
case OBJECT_EVENT_TRIGGER:
tag = "DROP EVENT TRIGGER";
+ *pESqlType = T_Stat_DROP_EVENT_TRIGGER;
break;
case OBJECT_RULE:
tag = "DROP RULE";
+ *pESqlType = T_Stat_DROP_RULE;
break;
case OBJECT_FDW:
tag = "DROP FOREIGN DATA WRAPPER";
+ *pESqlType = T_Stat_DROP_FOREIGN_DATA_WRAPPER;
break;
case OBJECT_FOREIGN_SERVER:
tag = "DROP SERVER";
+ *pESqlType = T_Stat_DROP_SERVER;
break;
case OBJECT_OPCLASS:
tag = "DROP OPERATOR CLASS";
+ *pESqlType = T_Stat_DROP_OPERATOR_CLASS;
break;
case OBJECT_OPFAMILY:
tag = "DROP OPERATOR FAMILY";
+ *pESqlType = T_Stat_DROP_OPERATOR_FAMILY;
break;
case OBJECT_POLICY:
tag = "DROP POLICY";
+ *pESqlType = T_Stat_DROP_POLICY;
break;
case OBJECT_TRANSFORM:
tag = "DROP TRANSFORM";
+ *pESqlType = T_Stat_DROP_TRANSFORM;
break;
case OBJECT_ACCESS_METHOD:
tag = "DROP ACCESS METHOD";
+ *pESqlType = T_Stat_DROP_ACCESS_METHOD;
break;
case OBJECT_PUBLICATION:
tag = "DROP PUBLICATION";
+ *pESqlType = T_Stat_DROP_PUBLICATION;
break;
case OBJECT_STATISTIC_EXT:
tag = "DROP STATISTICS";
+ *pESqlType = T_Stat_DROP_STATISTICS;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
}
break;
case T_TruncateStmt:
tag = "TRUNCATE TABLE";
+ *pESqlType = T_Stat_TRUNCATE_TABLE;
break;
case T_CommentStmt:
tag = "COMMENT";
+ *pESqlType = T_Stat_TRUNCATE_TABLE;
break;
case T_SecLabelStmt:
tag = "SECURITY LABEL";
+ *pESqlType = T_Stat_SECURITY_LABEL;
break;
case T_CopyStmt:
tag = "COPY";
+ *pESqlType = T_Stat_COPY;
break;
case T_RenameStmt:
- tag = AlterObjectTypeCommandTag(((RenameStmt *) parsetree)->renameType);
+ tag = AlterObjectTypeCommandTagType(((RenameStmt *) parsetree)->renameType, pESqlType);
break;
case T_AlterObjectDependsStmt:
- tag = AlterObjectTypeCommandTag(((AlterObjectDependsStmt *) parsetree)->objectType);
+ tag = AlterObjectTypeCommandTagType(((AlterObjectDependsStmt *) parsetree)->objectType, pESqlType);
break;
case T_AlterObjectSchemaStmt:
- tag = AlterObjectTypeCommandTag(((AlterObjectSchemaStmt *) parsetree)->objectType);
+ tag = AlterObjectTypeCommandTagType(((AlterObjectSchemaStmt *) parsetree)->objectType, pESqlType);
break;
case T_AlterOwnerStmt:
- tag = AlterObjectTypeCommandTag(((AlterOwnerStmt *) parsetree)->objectType);
+ tag = AlterObjectTypeCommandTagType(((AlterOwnerStmt *) parsetree)->objectType, pESqlType);
break;
case T_AlterTableMoveAllStmt:
- tag = AlterObjectTypeCommandTag(((AlterTableMoveAllStmt *) parsetree)->objtype);
+ tag = AlterObjectTypeCommandTagType(((AlterTableMoveAllStmt *) parsetree)->objtype, pESqlType);
break;
case T_AlterTableStmt:
- tag = AlterObjectTypeCommandTag(((AlterTableStmt *) parsetree)->relkind);
+ tag = AlterObjectTypeCommandTagType(((AlterTableStmt *) parsetree)->relkind, pESqlType);
break;
case T_AlterDomainStmt:
tag = "ALTER DOMAIN";
+ *pESqlType = T_Stat_ALTER_DOMAIN;
break;
case T_AlterFunctionStmt:
@@ -2437,15 +2741,19 @@ CreateCommandTag(Node *parsetree)
{
case OBJECT_FUNCTION:
tag = "ALTER FUNCTION";
+ *pESqlType = T_Stat_ALTER_FUNCTION;
break;
case OBJECT_PROCEDURE:
tag = "ALTER PROCEDURE";
+ *pESqlType = T_Stat_ALTER_PROCEDURE;
break;
case OBJECT_ROUTINE:
tag = "ALTER ROUTINE";
+ *pESqlType = T_Stat_ALTER_ROUTINE;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
}
break;
@@ -2454,6 +2762,7 @@ CreateCommandTag(Node *parsetree)
GrantStmt *stmt = (GrantStmt *) parsetree;
tag = (stmt->is_grant) ? "GRANT" : "REVOKE";
+ *pESqlType = (stmt->is_grant) ? T_Stat_GRANT : T_Stat_REVOKE;
}
break;
@@ -2462,11 +2771,13 @@ CreateCommandTag(Node *parsetree)
GrantRoleStmt *stmt = (GrantRoleStmt *) parsetree;
tag = (stmt->is_grant) ? "GRANT ROLE" : "REVOKE ROLE";
+ *pESqlType = (stmt->is_grant) ? T_Stat_GRANT_ROLE : T_Stat_REVOKE_ROLE;
}
break;
case T_AlterDefaultPrivilegesStmt:
tag = "ALTER DEFAULT PRIVILEGES";
+ *pESqlType = T_Stat_ALTER_DEFAULT_PRIVILEGES;
break;
case T_DefineStmt:
@@ -2474,132 +2785,175 @@ CreateCommandTag(Node *parsetree)
{
case OBJECT_AGGREGATE:
tag = "CREATE AGGREGATE";
+ *pESqlType = T_Stat_CREATE_AGGREGATE;
break;
case OBJECT_OPERATOR:
tag = "CREATE OPERATOR";
+ *pESqlType = T_Stat_CREATE_OPERATOR;
break;
case OBJECT_TYPE:
tag = "CREATE TYPE";
+ *pESqlType = T_Stat_CREATE_TYPE;
break;
case OBJECT_TSPARSER:
tag = "CREATE TEXT SEARCH PARSER";
+ *pESqlType = T_Stat_CREATE_TEXT_SEARCH_PARSER;
break;
case OBJECT_TSDICTIONARY:
tag = "CREATE TEXT SEARCH DICTIONARY";
+ *pESqlType = T_Stat_CREATE_TEXT_SEARCH_DICTIONARY;
break;
case OBJECT_TSTEMPLATE:
tag = "CREATE TEXT SEARCH TEMPLATE";
+ *pESqlType = T_Stat_CREATE_TEXT_SEARCH_TEMPLATE;
break;
case OBJECT_TSCONFIGURATION:
tag = "CREATE TEXT SEARCH CONFIGURATION";
+ *pESqlType = T_Stat_CREATE_TEXT_SEARCH_CONFIGURATION;
break;
case OBJECT_COLLATION:
tag = "CREATE COLLATION";
+ *pESqlType = T_Stat_CREATE_COLLATION;
break;
case OBJECT_ACCESS_METHOD:
tag = "CREATE ACCESS METHOD";
+ *pESqlType = T_Stat_CREATE_ACCESS_METHOD;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
}
break;
case T_CompositeTypeStmt:
tag = "CREATE TYPE";
+ *pESqlType = T_Stat_CREATE_TYPE;
break;
case T_CreateEnumStmt:
tag = "CREATE TYPE";
+ *pESqlType = T_Stat_CREATE_TYPE;
break;
case T_CreateRangeStmt:
tag = "CREATE TYPE";
+ *pESqlType = T_Stat_CREATE_TYPE;
break;
case T_AlterEnumStmt:
tag = "ALTER TYPE";
+ *pESqlType = T_Stat_ALTER_TYPE;
break;
case T_ViewStmt:
tag = "CREATE VIEW";
+ *pESqlType = T_Stat_CREATE_VIEW;
break;
case T_CreateFunctionStmt:
if (((CreateFunctionStmt *) parsetree)->is_procedure)
+ {
tag = "CREATE PROCEDURE";
+ *pESqlType = T_Stat_CREATE_PROCEDURE;
+ }
else
+ {
tag = "CREATE FUNCTION";
+ *pESqlType = T_Stat_CREATE_FUNCTION;
+ }
break;
case T_IndexStmt:
tag = "CREATE INDEX";
+ *pESqlType = T_Stat_CREATE_INDEX;
break;
case T_RuleStmt:
tag = "CREATE RULE";
+ *pESqlType = T_Stat_CREATE_RULE;
break;
case T_CreateSeqStmt:
tag = "CREATE SEQUENCE";
+ *pESqlType = T_Stat_CREATE_SEQUENCE;
break;
case T_AlterSeqStmt:
tag = "ALTER SEQUENCE";
+ *pESqlType = T_Stat_ALTER_SEQUENCE;
break;
case T_DoStmt:
tag = "DO";
+ *pESqlType = T_Stat_DO;
break;
case T_CreatedbStmt:
tag = "CREATE DATABASE";
+ *pESqlType = T_Stat_CREATE_DATABASE;
break;
case T_AlterDatabaseStmt:
tag = "ALTER DATABASE";
+ *pESqlType = T_Stat_ALTER_DATABASE;
break;
case T_AlterDatabaseSetStmt:
tag = "ALTER DATABASE";
+ *pESqlType = T_Stat_ALTER_DATABASE;
break;
case T_DropdbStmt:
tag = "DROP DATABASE";
+ *pESqlType = T_Stat_DROP_DATABASE;
break;
case T_NotifyStmt:
tag = "NOTIFY";
+ *pESqlType = T_Stat_NOTIFY;
break;
case T_ListenStmt:
tag = "LISTEN";
+ *pESqlType = T_Stat_LISTEN;
break;
case T_UnlistenStmt:
tag = "UNLISTEN";
+ *pESqlType = T_Stat_UNLISTEN;
break;
case T_LoadStmt:
tag = "LOAD";
+ *pESqlType = T_Stat_LOAD;
break;
case T_CallStmt:
tag = "CALL";
+ *pESqlType = T_Stat_CALL;
break;
case T_ClusterStmt:
tag = "CLUSTER";
+ *pESqlType = T_Stat_CLUSTER;
break;
case T_VacuumStmt:
if (((VacuumStmt *) parsetree)->is_vacuumcmd)
+ {
tag = "VACUUM";
+ *pESqlType = T_Stat_VACUUM;
+ }
else
+ {
tag = "ANALYZE";
+ *pESqlType = T_Stat_ANALYZE;
+ }
break;
case T_ExplainStmt:
tag = "EXPLAIN";
+ *pESqlType = T_Stat_EXPLAIN;
break;
case T_CreateTableAsStmt:
@@ -2607,24 +2961,34 @@ CreateCommandTag(Node *parsetree)
{
case OBJECT_TABLE:
if (((CreateTableAsStmt *) parsetree)->is_select_into)
+ {
tag = "SELECT INTO";
+ *pESqlType = T_Stat_SELECT_INTO;
+ }
else
+ {
tag = "CREATE TABLE AS";
+ *pESqlType = T_Stat_CREATE_TABLE_AS;
+ }
break;
case OBJECT_MATVIEW:
tag = "CREATE MATERIALIZED VIEW";
+ *pESqlType = T_Stat_CREATE_MATERIALIZED_VIEW;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
}
break;
case T_RefreshMatViewStmt:
tag = "REFRESH MATERIALIZED VIEW";
+ *pESqlType = T_Stat_REFRESH_MATERIALIZED_VIEW;
break;
case T_AlterSystemStmt:
tag = "ALTER SYSTEM";
+ *pESqlType = T_Stat_ALTER_SYSTEM;
break;
case T_VariableSetStmt:
@@ -2635,18 +2999,22 @@ CreateCommandTag(Node *parsetree)
case VAR_SET_DEFAULT:
case VAR_SET_MULTI:
tag = "SET";
+ *pESqlType = T_Stat_SET;
break;
case VAR_RESET:
case VAR_RESET_ALL:
tag = "RESET";
+ *pESqlType = T_Stat_RESET;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
}
break;
case T_VariableShowStmt:
tag = "SHOW";
+ *pESqlType = T_Stat_SHOW;
break;
case T_DiscardStmt:
@@ -2654,163 +3022,204 @@ CreateCommandTag(Node *parsetree)
{
case DISCARD_ALL:
tag = "DISCARD ALL";
+ *pESqlType = T_Stat_DISCARD_ALL;
break;
case DISCARD_PLANS:
tag = "DISCARD PLANS";
+ *pESqlType = T_Stat_DISCARD_PLANS;
break;
case DISCARD_TEMP:
tag = "DISCARD TEMP";
+ *pESqlType = T_Stat_DISCARD_TEMP;
break;
case DISCARD_SEQUENCES:
tag = "DISCARD SEQUENCES";
+ *pESqlType = T_Stat_DISCARD_SEQUENCES;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
}
break;
case T_CreateTransformStmt:
tag = "CREATE TRANSFORM";
+ *pESqlType = T_Stat_CREATE_TRANSFORM;
break;
case T_CreateTrigStmt:
tag = "CREATE TRIGGER";
+ *pESqlType = T_Stat_CREATE_TRIGGER;
break;
case T_CreateEventTrigStmt:
tag = "CREATE EVENT TRIGGER";
+ *pESqlType = T_Stat_CREATE_EVENT_TRIGGER;
break;
case T_AlterEventTrigStmt:
tag = "ALTER EVENT TRIGGER";
+ *pESqlType = T_Stat_ALTER_EVENT_TRIGGER;
break;
case T_CreatePLangStmt:
tag = "CREATE LANGUAGE";
+ *pESqlType = T_Stat_CREATE_LANGUAGE;
break;
case T_CreateRoleStmt:
tag = "CREATE ROLE";
+ *pESqlType = T_Stat_CREATE_ROLE;
break;
case T_AlterRoleStmt:
tag = "ALTER ROLE";
+ *pESqlType = T_Stat_ALTER_ROLE;
break;
case T_AlterRoleSetStmt:
tag = "ALTER ROLE";
+ *pESqlType = T_Stat_ALTER_ROLE;
break;
case T_DropRoleStmt:
tag = "DROP ROLE";
+ *pESqlType = T_Stat_DROP_ROLE;
break;
case T_DropOwnedStmt:
tag = "DROP OWNED";
+ *pESqlType = T_Stat_DROP_OWNED;
break;
case T_ReassignOwnedStmt:
tag = "REASSIGN OWNED";
+ *pESqlType = T_Stat_REASSIGN_OWNED;
break;
case T_LockStmt:
tag = "LOCK TABLE";
+ *pESqlType = T_Stat_LOCK_TABLE;
break;
case T_ConstraintsSetStmt:
tag = "SET CONSTRAINTS";
+ *pESqlType = T_Stat_SET_CONSTRAINTS;
break;
case T_CheckPointStmt:
tag = "CHECKPOINT";
+ *pESqlType = T_Stat_CHECKPOINT;
break;
case T_ReindexStmt:
tag = "REINDEX";
+ *pESqlType = T_Stat_REINDEX;
break;
case T_CreateConversionStmt:
tag = "CREATE CONVERSION";
+ *pESqlType = T_Stat_CREATE_CONVERSION;
break;
case T_CreateCastStmt:
tag = "CREATE CAST";
+ *pESqlType = T_Stat_CREATE_CAST;
break;
case T_CreateOpClassStmt:
tag = "CREATE OPERATOR CLASS";
+ *pESqlType = T_Stat_CREATE_OPERATOR_CLASS;
break;
case T_CreateOpFamilyStmt:
tag = "CREATE OPERATOR FAMILY";
+ *pESqlType = T_Stat_CREATE_OPERATOR_FAMILY;
break;
case T_AlterOpFamilyStmt:
tag = "ALTER OPERATOR FAMILY";
+ *pESqlType = T_Stat_ALTER_OPERATOR_FAMILY;
break;
case T_AlterOperatorStmt:
tag = "ALTER OPERATOR";
+ *pESqlType = T_Stat_ALTER_OPERATOR;
break;
case T_AlterTSDictionaryStmt:
tag = "ALTER TEXT SEARCH DICTIONARY";
+ *pESqlType = T_Stat_ALTER_TEXT_SEARCH_DICTIONARY;
break;
case T_AlterTSConfigurationStmt:
tag = "ALTER TEXT SEARCH CONFIGURATION";
+ *pESqlType = T_Stat_ALTER_TEXT_SEARCH_CONFIGURATION;
break;
case T_CreatePolicyStmt:
tag = "CREATE POLICY";
+ *pESqlType = T_Stat_CREATE_POLICY;
break;
case T_AlterPolicyStmt:
tag = "ALTER POLICY";
+ *pESqlType = T_Stat_ALTER_POLICY;
break;
case T_CreateAmStmt:
tag = "CREATE ACCESS METHOD";
+ *pESqlType = T_Stat_CREATE_ACCESS_METHOD;
break;
case T_CreatePublicationStmt:
tag = "CREATE PUBLICATION";
+ *pESqlType = T_Stat_CREATE_PUBLICATION;
break;
case T_AlterPublicationStmt:
tag = "ALTER PUBLICATION";
+ *pESqlType = T_Stat_ALTER_PUBLICATION;
break;
case T_CreateSubscriptionStmt:
tag = "CREATE SUBSCRIPTION";
+ *pESqlType = T_Stat_CREATE_SUBSCRIPTION;
break;
case T_AlterSubscriptionStmt:
tag = "ALTER SUBSCRIPTION";
+ *pESqlType = T_Stat_ALTER_SUBSCRIPTION;
break;
case T_DropSubscriptionStmt:
tag = "DROP SUBSCRIPTION";
+ *pESqlType = T_Stat_DROP_SUBSCRIPTION;
break;
case T_AlterCollationStmt:
tag = "ALTER COLLATION";
+ *pESqlType = T_Stat_ALTER_COLLATION;
break;
case T_PrepareStmt:
tag = "PREPARE";
+ *pESqlType = T_Stat_PREPARE;
break;
case T_ExecuteStmt:
tag = "EXECUTE";
+ *pESqlType = T_Stat_EXECUTE;
break;
case T_CreateStatsStmt:
tag = "CREATE STATISTICS";
+ *pESqlType = T_Stat_CREATE_STATISTICS;
break;
case T_AlterStatsStmt:
tag = "ALTER STATISTICS";
+ *pESqlType = T_Stat_ALTER_STATISTICS;
break;
case T_DeallocateStmt:
@@ -2818,9 +3227,15 @@ CreateCommandTag(Node *parsetree)
DeallocateStmt *stmt = (DeallocateStmt *) parsetree;
if (stmt->name == NULL)
+ {
tag = "DEALLOCATE ALL";
+ *pESqlType = T_Stat_DEALLOCATE_ALL;
+ }
else
+ {
tag = "DEALLOCATE";
+ *pESqlType = T_Stat_DEALLOCATE;
+ }
}
break;
@@ -2845,40 +3260,50 @@ CreateCommandTag(Node *parsetree)
{
case LCS_FORKEYSHARE:
tag = "SELECT FOR KEY SHARE";
+ *pESqlType = T_Stat_SELECT_FOR_KEY_SHARE;
break;
case LCS_FORSHARE:
tag = "SELECT FOR SHARE";
+ *pESqlType = T_Stat_SELECT_FOR_SHARE;
break;
case LCS_FORNOKEYUPDATE:
tag = "SELECT FOR NO KEY UPDATE";
+ *pESqlType = T_Stat_SELECT_FOR_NO_KEY_UPDATE;
break;
case LCS_FORUPDATE:
tag = "SELECT FOR UPDATE";
+ *pESqlType = T_Stat_SELECT_FOR_UPDATE;
break;
default:
tag = "SELECT";
+ *pESqlType = T_Stat_SELECT;
break;
}
}
else
tag = "SELECT";
+ *pESqlType = T_Stat_SELECT;
break;
case CMD_UPDATE:
tag = "UPDATE";
+ *pESqlType = T_Stat_UPDATE;
break;
case CMD_INSERT:
tag = "INSERT";
+ *pESqlType = T_Stat_INSERT;
break;
case CMD_DELETE:
tag = "DELETE";
+ *pESqlType = T_Stat_DELETE;
break;
case CMD_UTILITY:
- tag = CreateCommandTag(stmt->utilityStmt);
+ tag = CreateCommandTagType(stmt->utilityStmt, pESqlType);
break;
default:
elog(WARNING, "unrecognized commandType: %d",
(int) stmt->commandType);
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
break;
}
}
@@ -2905,40 +3330,50 @@ CreateCommandTag(Node *parsetree)
{
case LCS_FORKEYSHARE:
tag = "SELECT FOR KEY SHARE";
+ *pESqlType = T_Stat_SELECT_FOR_KEY_SHARE;
break;
case LCS_FORSHARE:
tag = "SELECT FOR SHARE";
+ *pESqlType = T_Stat_SELECT_FOR_SHARE;
break;
case LCS_FORNOKEYUPDATE:
tag = "SELECT FOR NO KEY UPDATE";
+ *pESqlType = T_Stat_SELECT_FOR_NO_KEY_UPDATE;
break;
case LCS_FORUPDATE:
tag = "SELECT FOR UPDATE";
+ *pESqlType = T_Stat_SELECT_FOR_UPDATE;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
break;
}
}
else
tag = "SELECT";
+ *pESqlType = T_Stat_SELECT;
break;
case CMD_UPDATE:
tag = "UPDATE";
+ *pESqlType = T_Stat_UPDATE;
break;
case CMD_INSERT:
tag = "INSERT";
+ *pESqlType = T_Stat_INSERT;
break;
case CMD_DELETE:
tag = "DELETE";
+ *pESqlType = T_Stat_DELETE;
break;
case CMD_UTILITY:
- tag = CreateCommandTag(stmt->utilityStmt);
+ tag = CreateCommandTagType(stmt->utilityStmt, pESqlType);
break;
default:
elog(WARNING, "unrecognized commandType: %d",
(int) stmt->commandType);
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
break;
}
}
@@ -2948,6 +3383,7 @@ CreateCommandTag(Node *parsetree)
elog(WARNING, "unrecognized node type: %d",
(int) nodeTag(parsetree));
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
break;
}
@@ -2956,6 +3392,45 @@ CreateCommandTag(Node *parsetree)
/*
+ * CreateCommandTag
+ * utility to get a string representation of the command operation,
+ * given either a raw (un-analyzed) parsetree, an analyzed Query,
+ * or a PlannedStmt.
+ *
+ * This must handle all command types, but since the vast majority
+ * of 'em are utility commands, it seems sensible to keep it here.
+ *
+ * NB: all result strings must be shorter than COMPLETION_TAG_BUFSIZE.
+ * Also, the result must point at a true constant (permanent storage).
+ */
+const char *
+CreateCommandTag(Node *parsetree)
+{
+ StatSqlType eType;
+ return CreateCommandTagType(parsetree, &eType);
+}
+
+
+/*
+ * CreateCommandType
+ * utility to get a enum representation of the command operation,
+ * given either a raw (un-analyzed) parsetree, an analyzed Query,
+ * or a PlannedStmt.
+ *
+ * This must handle all command types, but since the vast majority
+ * of 'em are utility commands, it seems sensible to keep it here.
+ *
+ */
+StatSqlType
+CreateCommandType(Node *parsetree)
+{
+ StatSqlType eType;
+ CreateCommandTagType(parsetree, &eType);
+ return eType;
+}
+
+
+/*
* GetCommandLogLevel
* utility to get the minimum log_statement level for a command,
* given either a raw (un-analyzed) parsetree, an analyzed Query,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 05240bf..7618de7 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1970,3 +1970,64 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(HeapTupleGetDatum(
heap_form_tuple(tupdesc, values, nulls)));
}
+
+Datum
+pg_stat_sql(PG_FUNCTION_ARGS)
+{
+ TupleDesc tupdesc;
+ Datum values[2] = { 0, 0 };
+ bool nulls[2] = { false, false };
+ ReturnSetInfo *rsi;
+ MemoryContext old_cxt;
+ Tuplestorestate *tuple_store;
+
+ /* Function call to let the backend read the stats file */
+ pgstat_fetch_global();
+
+ rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* Check to see if caller supports us returning a tuplestore */
+ if (rsi == NULL || !IsA(rsi, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsi->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ rsi->returnMode = SFRM_Materialize;
+
+ /* 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");
+
+ /* Build tuplestore to hold the result rows */
+ old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+ tuple_store =
+ tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ false, work_mem);
+ rsi->setDesc = tupdesc;
+ rsi->setResult = tuple_store;
+
+ MemoryContextSwitchTo(old_cxt);
+
+ for (int i = 0; i < PGSTAT_SQLSTMT_SIZE; i++)
+ {
+ HeapTuple tuple;
+
+ if (pgstat_sql_counts[i] == 0)
+ continue;
+
+ /* Fill values and NULLs */
+ values[0] = CStringGetTextDatum(g_str_sql_type[i]);
+ values[1] = Int64GetDatum(pgstat_sql_counts[i]);
+
+ tuple = heap_form_tuple(tupdesc, values, nulls);
+ tuplestore_puttuple(tuple_store, tuple);
+ }
+
+ PG_RETURN_NULL();
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 2178e1c..081e936 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1407,7 +1407,15 @@ static struct config_bool ConfigureNamesBool[] =
false,
NULL, NULL, NULL
},
-
+ {
+ {"track_statement_statistics", PGC_SUSET, STATS_COLLECTOR,
+ gettext_noop("Collects the counters of SQL statments."),
+ NULL
+ },
+ &pgstat_track_statement_statistics,
+ false,
+ NULL, NULL, NULL
+ },
{
{"update_process_title", PGC_SUSET, PROCESS_TITLE,
gettext_noop("Updates the process title to show the active SQL command."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 0fc23e3..ff5ce0e 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -555,6 +555,7 @@
#track_activities = on
#track_counts = on
+#track_statement_statistics = off
#track_io_timing = off
#track_functions = none # none, pl, all
#track_activity_query_size = 1024 # (change requires restart)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 58ea5b9..a41d572 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5476,7 +5476,14 @@
proname => 'pg_stat_get_xact_function_self_time', provolatile => 'v',
proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
prosrc => 'pg_stat_get_xact_function_self_time' },
-
+{ oid => '4789',
+ descr => 'statistics: Show SQL statement statistics',
+ proname => 'pg_stat_sql', prorows => '1000', proretset => 't', provolatile => 'v',
+ proparallel => 'r', prorettype => 'record', proargtypes => '',
+ proallargtypes => '{text,int4}',
+ proargmodes => '{o,o}',
+ proargnames => '{tag,count}',
+ prosrc => 'pg_stat_sql' },
{ oid => '3788',
descr => 'statistics: timestamp of the current statistics snapshot',
proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index fe076d8..8cdbbd8 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -19,6 +19,7 @@
#include "storage/proc.h"
#include "utils/hsearch.h"
#include "utils/relcache.h"
+#include "tcop/utility.h"
/* ----------
@@ -119,7 +120,8 @@ typedef struct PgStat_TableCounts
typedef enum PgStat_Shared_Reset_Target
{
RESET_ARCHIVER,
- RESET_BGWRITER
+ RESET_BGWRITER,
+ RESET_SQLSTMT
} PgStat_Shared_Reset_Target;
/* Possible object types for resetting single counters */
@@ -581,7 +583,7 @@ typedef union PgStat_Msg
* ------------------------------------------------------------
*/
-#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9D
+#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9E
/* ----------
* PgStat_StatDBEntry The collector's data per database
@@ -1210,6 +1212,7 @@ typedef struct PgStat_FunctionCallUsage
*/
extern bool pgstat_track_activities;
extern bool pgstat_track_counts;
+extern bool pgstat_track_statement_statistics;
extern int pgstat_track_functions;
extern PGDLLIMPORT int pgstat_track_activity_query_size;
extern char *pgstat_stat_directory;
@@ -1222,6 +1225,11 @@ extern char *pgstat_stat_filename;
extern PgStat_MsgBgWriter BgWriterStats;
/*
+ * SQL statement statistics counter
+ */
+extern uint64* pgstat_sql_counts;
+
+/*
* Updated by pgstat_count_buffer_*_time macros
*/
extern PgStat_Counter pgStatBlockReadTime;
@@ -1299,6 +1307,9 @@ extern void pgstat_initstats(Relation rel);
extern char *pgstat_clip_activity(const char *raw_activity);
+extern Size pgstat_sql_shmem_size(void);
+extern void pgstat_sql_shmem_init(void);
+
/* ----------
* pgstat_report_wait_start() -
*
@@ -1420,6 +1431,7 @@ extern void pgstat_twophase_postabort(TransactionId xid, uint16 info,
extern void pgstat_send_archiver(const char *xlog, bool failed);
extern void pgstat_send_bgwriter(void);
+extern void pgstat_count_sqlstmt(StatSqlType sqlType);
/* ----------
* Support functions for the SQL-callable functions to
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 5abcacf..9a9e867 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -25,6 +25,203 @@ typedef enum
PROCESS_UTILITY_SUBCOMMAND /* a portion of a query */
} ProcessUtilityContext;
+/* ----------
+ * The sql statement types for pg_stat_sql
+ * ----------
+ */
+typedef enum StatSqlType
+{
+ T_Stat_INSERT,
+ T_Stat_DELETE,
+ T_Stat_UPDATE,
+ T_Stat_SELECT,
+ T_Stat_BEGIN,
+ T_Stat_START_TRANSACTION,
+ T_Stat_COMMIT,
+ T_Stat_ROLLBACK,
+ T_Stat_SAVEPOINT,
+ T_Stat_RELEASE,
+ T_Stat_PREPARE_TRANSACTION,
+ T_Stat_COMMIT_PREPARED,
+ T_Stat_ROLLBACK_PREPARED,
+ T_Stat_DECLARE_CURSOR,
+ T_Stat_CLOSE_CURSOR_ALL,
+ T_Stat_CLOSE_CURSOR,
+ T_Stat_MOVE,
+ T_Stat_FETCH,
+ T_Stat_CREATE_DOMAIN,
+ T_Stat_CREATE_SCHEMA,
+ T_Stat_CREATE_TABLE,
+ T_Stat_CREATE_TABLESPACE,
+ T_Stat_DROP_TABLESPACE,
+ T_Stat_ALTER_TABLESPACE,
+ T_Stat_CREATE_EXTENSION,
+ T_Stat_ALTER_EXTENSION,
+ T_Stat_CREATE_FOREIGN_DATA_WRAPPER,
+ T_Stat_ALTER_FOREIGN_DATA_WRAPPER,
+ T_Stat_CREATE_SERVER,
+ T_Stat_ALTER_SERVER,
+ T_Stat_CREATE_USER_MAPPING,
+ T_Stat_ALTER_USER_MAPPING,
+ T_Stat_DROP_USER_MAPPING,
+ T_Stat_CREATE_FOREIGN_TABLE,
+ T_Stat_IMPORT_FOREIGN_SCHEMA,
+ T_Stat_DROP_TABLE,
+ T_Stat_DROP_SEQUENCE,
+ T_Stat_DROP_VIEW,
+ T_Stat_DROP_MATERIALIZED_VIEW,
+ T_Stat_DROP_INDEX,
+ T_Stat_DROP_TYPE,
+ T_Stat_DROP_DOMAIN,
+ T_Stat_DROP_COLLATION,
+ T_Stat_DROP_CONVERSION,
+ T_Stat_DROP_SCHEMA,
+ T_Stat_DROP_TEXT_SEARCH_PARSER,
+ T_Stat_DROP_TEXT_SEARCH_DICTIONARY,
+ T_Stat_DROP_TEXT_SEARCH_TEMPLATE,
+ T_Stat_DROP_DROP_TEXT_SEARCH_CONFIGURATION,
+ T_Stat_DROP_DROP_FOREIGN_TABLE,
+ T_Stat_DROP_EXTENSION,
+ T_Stat_DROP_FUNCTION,
+ T_Stat_DROP_PROCEDURE,
+ T_Stat_DROP_ROUTINE,
+ T_Stat_DROP_AGGREGATE,
+ T_Stat_DROP_OPERATOR,
+ T_Stat_DROP_LANGUAGE,
+ T_Stat_DROP_CAST,
+ T_Stat_DROP_TRIGGER,
+ T_Stat_DROP_EVENT_TRIGGER,
+ T_Stat_DROP_RULE,
+ T_Stat_DROP_FOREIGN_DATA_WRAPPER,
+ T_Stat_DROP_SERVER,
+ T_Stat_DROP_OPERATOR_CLASS,
+ T_Stat_DROP_OPERATOR_FAMILY,
+ T_Stat_DROP_POLICY,
+ T_Stat_DROP_TRANSFORM,
+ T_Stat_DROP_ACCESS_METHOD,
+ T_Stat_DROP_PUBLICATION,
+ T_Stat_DROP_STATISTICS,
+ T_Stat_TRUNCATE_TABLE,
+ T_Stat_COMMENT,
+ T_Stat_SECURITY_LABEL,
+ T_Stat_COPY,
+ T_Stat_ALTER_AGGREGATE,
+ T_Stat_ALTER_TYPE,
+ T_Stat_ALTER_CAST,
+ T_Stat_ALTER_COLLATION,
+ T_Stat_ALTER_TABLE,
+ T_Stat_ALTER_CONVERSION,
+ T_Stat_ALTER_DATABASE,
+ T_Stat_ALTER_DOMAIN,
+ T_Stat_ALTER_FOREIGN_TABLE,
+ T_Stat_ALTER_FUNCTION,
+ T_Stat_ALTER_INDEX,
+ T_Stat_ALTER_LANGUAGE,
+ T_Stat_ALTER_LARGE_OBJECT,
+ T_Stat_ALTER_OPERATOR_CLASS,
+ T_Stat_ALTER_OPERATOR,
+ T_Stat_ALTER_OPERATOR_FAMILY,
+ T_Stat_ALTER_POLICY,
+ T_Stat_ALTER_PROCEDURE,
+ T_Stat_ALTER_ROLE,
+ T_Stat_ALTER_ROUTINE,
+ T_Stat_ALTER_RULE,
+ T_Stat_ALTER_SCHEMA,
+ T_Stat_ALTER_SEQUENCE,
+ T_Stat_ALTER_TRIGGER,
+ T_Stat_ALTER_EVENT_TRIGGER,
+ T_Stat_ALTER_TEXT_SEARCH_CONFIGURATION,
+ T_Stat_ALTER_TEXT_SEARCH_DICTIONARY,
+ T_Stat_ALTER_TEXT_SEARCH_PARSER,
+ T_Stat_ALTER_TEXT_SEARCH_TEMPLATE,
+ T_Stat_ALTER_VIEW,
+ T_Stat_ALTER_MATERIALIZED_VIEW,
+ T_Stat_ALTER_PUBLICATION,
+ T_Stat_ALTER_SUBSCRIPTION,
+ T_Stat_ALTER_STATISTICS,
+ T_Stat_GRANT,
+ T_Stat_REVOKE,
+ T_Stat_GRANT_ROLE,
+ T_Stat_REVOKE_ROLE,
+ T_Stat_ALTER_DEFAULT_PRIVILEGES,
+ T_Stat_CREATE_AGGREGATE,
+ T_Stat_CREATE_OPERATOR,
+ T_Stat_CREATE_TYPE,
+ T_Stat_CREATE_TEXT_SEARCH_PARSER,
+ T_Stat_CREATE_TEXT_SEARCH_DICTIONARY,
+ T_Stat_CREATE_TEXT_SEARCH_TEMPLATE,
+ T_Stat_CREATE_TEXT_SEARCH_CONFIGURATION,
+ T_Stat_CREATE_COLLATION,
+ T_Stat_CREATE_ACCESS_METHOD,
+ T_Stat_CREATE_VIEW,
+ T_Stat_CREATE_PROCEDURE,
+ T_Stat_CREATE_FUNCTION,
+ T_Stat_CREATE_INDEX,
+ T_Stat_CREATE_RULE,
+ T_Stat_CREATE_SEQUENCE,
+ T_Stat_DO,
+ T_Stat_CREATE_DATABASE,
+ T_Stat_DROP_DATABASE,
+ T_Stat_NOTIFY,
+ T_Stat_LISTEN,
+ T_Stat_UNLISTEN,
+ T_Stat_LOAD,
+ T_Stat_CALL,
+ T_Stat_CLUSTER,
+ T_Stat_VACUUM,
+ T_Stat_ANALYZE,
+ T_Stat_EXPLAIN,
+ T_Stat_SELECT_INTO,
+ T_Stat_CREATE_TABLE_AS,
+ T_Stat_CREATE_MATERIALIZED_VIEW,
+ T_Stat_REFRESH_MATERIALIZED_VIEW,
+ T_Stat_ALTER_SYSTEM,
+ T_Stat_SET,
+ T_Stat_RESET,
+ T_Stat_SHOW,
+ T_Stat_DISCARD_ALL,
+ T_Stat_DISCARD_PLANS,
+ T_Stat_DISCARD_TEMP,
+ T_Stat_DISCARD_SEQUENCES,
+ T_Stat_CREATE_TRANSFORM,
+ T_Stat_CREATE_TRIGGER,
+ T_Stat_CREATE_EVENT_TRIGGER,
+ T_Stat_CREATE_LANGUAGE,
+ T_Stat_CREATE_ROLE,
+ T_Stat_DROP_ROLE,
+ T_Stat_DROP_OWNED,
+ T_Stat_REASSIGN_OWNED,
+ T_Stat_LOCK_TABLE,
+ T_Stat_SET_CONSTRAINTS,
+ T_Stat_CHECKPOINT,
+ T_Stat_REINDEX,
+ T_Stat_CREATE_CONVERSION,
+ T_Stat_CREATE_CAST,
+ T_Stat_CREATE_OPERATOR_CLASS,
+ T_Stat_CREATE_OPERATOR_FAMILY,
+ T_Stat_CREATE_POLICY,
+ T_Stat_CREATE_PUBLICATION,
+ T_Stat_CREATE_SUBSCRIPTION,
+ T_Stat_DROP_SUBSCRIPTION,
+ T_Stat_PREPARE,
+ T_Stat_EXECUTE,
+ T_Stat_CREATE_STATISTICS,
+ T_Stat_DEALLOCATE_ALL,
+ T_Stat_DEALLOCATE,
+ T_Stat_SELECT_FOR_KEY_SHARE,
+ T_Stat_SELECT_FOR_SHARE,
+ T_Stat_SELECT_FOR_NO_KEY_UPDATE,
+ T_Stat_SELECT_FOR_UPDATE,
+
+ T_Stat_UNKNOWN
+
+} StatSqlType;
+
+#define PGSTAT_SQLSTMT_SIZE (T_Stat_UNKNOWN + 1)
+
+extern const char* g_str_sql_type[];
+
+
/* Hook for plugins to get control in ProcessUtility() */
typedef void (*ProcessUtility_hook_type) (PlannedStmt *pstmt,
const char *queryString, ProcessUtilityContext context,
@@ -50,6 +247,10 @@ extern Query *UtilityContainsQuery(Node *parsetree);
extern const char *CreateCommandTag(Node *parsetree);
+extern StatSqlType CreateCommandType(Node *parsetree);
+
+extern const char* CreateCommandTagType(Node *parsetree, StatSqlType* pESqlType);
+
extern LogStmtLevel GetCommandLogLevel(Node *parsetree);
extern bool CommandIsReadOnly(PlannedStmt *pstmt);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 210e9cd..1b3d6f5 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1955,6 +1955,9 @@ pg_stat_replication| SELECT s.pid,
FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc)
JOIN pg_stat_get_wal_senders() w(pid, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, write_lag, flush_lag, replay_lag, sync_priority, sync_state, reply_time) ON ((s.pid = w.pid)))
LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
+pg_stat_sql| SELECT pg_stat_sql.tag,
+ pg_stat_sql.count
+ FROM pg_stat_sql() pg_stat_sql(tag, count);
pg_stat_ssl| SELECT s.pid,
s.ssl,
s.sslversion AS version,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index b01e58b..50846c7 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -4,6 +4,70 @@
-- Must be run after tenk2 has been created (by create_table),
-- populated (by create_misc) and indexed (by create_index).
--
+-- pg_stat_sql
+SET track_statement_statistics TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+-----+-------
+(0 rows)
+
+SET track_statement_statistics TO ON;
+CREATE TABLE t1 (c1 int);
+INSERT INTO t1 VALUES(1);
+INSERT INTO t1 VALUES(1);
+INSERT INTO t1 VALUES(1);
+DELETE FROM t1;
+SELECT * FROM pg_stat_sql;
+ tag | count
+--------------+-------
+ INSERT | 3
+ DELETE | 1
+ CREATE TABLE | 1
+ SET | 1
+(4 rows)
+
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+--------+-------
+ SELECT | 2
+(1 row)
+
+SET track_statement_statistics TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+-----+-------
+(0 rows)
+
-- conditio sine qua non
SHOW track_counts; -- must be on
track_counts
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index feaaee6..48e88d3 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -4,6 +4,30 @@
-- Must be run after tenk2 has been created (by create_table),
-- populated (by create_misc) and indexed (by create_index).
--
+-- pg_stat_sql
+SET track_statement_statistics TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
+SET track_statement_statistics TO ON;
+
+CREATE TABLE t1 (c1 int);
+INSERT INTO t1 VALUES(1);
+INSERT INTO t1 VALUES(1);
+INSERT INTO t1 VALUES(1);
+DELETE FROM t1;
+SELECT * FROM pg_stat_sql;
+
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
+SET track_statement_statistics TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
-- conditio sine qua non
SHOW track_counts; -- must be on
--
1.8.3.1
On Thu, Oct 17, 2019 at 3:22 PM Smith, Peter <peters@fast.au.fujitsu.com> wrote:
We have resurrected this 2 year old "SQL statements statistics counter" proposal from Hari.
The attached patch addresses the previous review issues.
Hi,
No comment on the patch but I noticed that the documentation changes
don't build. Please make sure you can "make docs" successfully,
having installed the documentation tools[1]https://www.postgresql.org/docs/devel/docguide-toolsets.html.
[1]: https://www.postgresql.org/docs/devel/docguide-toolsets.html
From: Thomas Munro <thomas.munro@gmail.com> Sent: Monday, 4 November 2019 1:43 PM
No comment on the patch but I noticed that the documentation changes don't build. Please make sure you can "make docs" successfully, having installed the documentation tools[1].
Thanks for the feedback. An updated patch which fixes the docs issue is attached.
Kind Regards.
Peter Smith
---
Fujitsu Australia
Attachments:
0001-pg_stat_sql.patchapplication/octet-stream; name=0001-pg_stat_sql.patchDownload
From 266c0fba36117e1d9cb8efe47a051bcace3a6eb6 Mon Sep 17 00:00:00 2001
From: Alex Zhao <alexyuzhao@gmail.com>
Date: Wed, 13 Nov 2019 12:49:01 +1100
Subject: [PATCH] pg_stat_sql
---
doc/src/sgml/config.sgml | 15 +
doc/src/sgml/monitoring.sgml | 42 +++
src/backend/catalog/system_views.sql | 4 +
src/backend/postmaster/pgstat.c | 57 ++-
src/backend/storage/ipc/ipci.c | 2 +
src/backend/storage/lmgr/lwlocknames.txt | 1 +
src/backend/tcop/postgres.c | 18 +-
src/backend/tcop/utility.c | 523 ++++++++++++++++++++++++--
src/backend/utils/adt/pgstatfuncs.c | 61 +++
src/backend/utils/misc/guc.c | 10 +-
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/catalog/pg_proc.dat | 9 +-
src/include/pgstat.h | 16 +-
src/include/tcop/utility.h | 201 ++++++++++
src/test/regress/expected/rules.out | 3 +
src/test/regress/expected/stats.out | 64 ++++
src/test/regress/sql/stats.sql | 24 ++
17 files changed, 1020 insertions(+), 31 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f837703..e43988d 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -6983,6 +6983,21 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
</listitem>
</varlistentry>
+ <varlistentry id="guc-track-statement-statistics" xreflabel="track_statement_statistics">
+ <term><varname>track_statement_statistics</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>track_statement_statistics</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Enables collection of different SQL statement statistics that are
+ executed on the instance. This parameter is off by default. Only
+ superusers can change this setting.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-stats-temp-directory" xreflabel="stats_temp_directory">
<term><varname>stats_temp_directory</varname> (<type>string</type>)
<indexterm>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 901fee9..3514833 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -361,6 +361,13 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
</row>
<row>
+ <entry><structname>pg_stat_sql</structname>><indexterm><primary>pg_stat_sql</primary></indexterm></entry>
+ <entry>Shows statistics about the SQL statements that are
+ executed on the instance.
+ See <xref linkend='pg-stat-sql-view'/> for details.</entry>
+ </row>
+
+ <row>
<entry><structname>pg_stat_progress_cluster</structname><indexterm><primary>pg_stat_progress_cluster</primary></indexterm></entry>
<entry>One row for each backend running
<command>CLUSTER</command> or <command>VACUUM FULL</command>, showing current progress.
@@ -3185,6 +3192,39 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
controls exactly which functions are tracked.
</para>
+ <table id="pg-stat-sql-view" xreflabel="pg_stat_sql">
+ <title><structname>pg_stat_sql</structname> View</title>
+ <tgroup cols="3">
+ <thead>
+ <row>
+ <entry>Column</entry>
+ <entry>Type</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry><structfield>tag</structfield></entry>
+ <entry><type>text</type></entry>
+ <entry>Tag of the SQL statement</entry>
+ </row>
+ <row>
+ <entry><structfield>count</structfield></entry>
+ <entry><type>bigint</type></entry>
+ <entry>Number of times the SQL statement is executed</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The <structname>pg_stat_sql</structname> view will contain statistics
+ about number of SQL statements that are executed on the instance.
+ The <xref linkend="guc-track-statement-statistics"/> parameter controls the SQL statement
+ execution statistics.
+ </para>
+
</sect2>
<sect2 id="monitoring-stats-functions">
@@ -3280,6 +3320,8 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
counters shown in the <structname>pg_stat_bgwriter</structname> view.
Calling <literal>pg_stat_reset_shared('archiver')</literal> will zero all the
counters shown in the <structname>pg_stat_archiver</structname> view.
+ Calling <literal>pg_stat_reset_shared('sqlstmt')</literal> will zero all the
+ counters shown in the <structname>pg_stat_sql</structname> view.
</entry>
</row>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 4456fef..598c1d3 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1063,6 +1063,10 @@ REVOKE ALL ON pg_subscription FROM public;
GRANT SELECT (subdbid, subname, subowner, subenabled, subslotname, subpublications)
ON pg_subscription TO public;
+CREATE VIEW pg_stat_sql AS
+ SELECT * FROM pg_stat_sql();
+
+REVOKE EXECUTE ON FUNCTION pg_stat_sql() FROM public;
--
-- We have a few function definitions in here, too.
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index fabcf31..90dfd90 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -122,6 +122,7 @@
*/
bool pgstat_track_activities = false;
bool pgstat_track_counts = false;
+bool pgstat_track_statement_statistics = false;
int pgstat_track_functions = TRACK_FUNC_OFF;
int pgstat_track_activity_query_size = 1024;
@@ -140,6 +141,13 @@ char *pgstat_stat_tmpname = NULL;
*/
PgStat_MsgBgWriter BgWriterStats;
+
+/*
+ * Global SQL statement counters gathered that are stored in
+ * shared memory.
+ */
+uint64* pgstat_sql_counts = NULL;
+
/* ----------
* Local data
* ----------
@@ -629,6 +637,32 @@ startup_failed:
SetConfigOption("track_counts", "off", PGC_INTERNAL, PGC_S_OVERRIDE);
}
+
+/*
+ * Initialization of shared memory for pgstat_sql_count
+ */
+Size
+pgstat_sql_shmem_size(void)
+{
+ return sizeof(uint64) * PGSTAT_SQLSTMT_SIZE;
+}
+
+void
+pgstat_sql_shmem_init(void)
+{
+ bool foundWALWrites;
+
+ pgstat_sql_counts = (uint64 *)
+ ShmemInitStruct("pgstat sql counter", pgstat_sql_shmem_size(), &foundWALWrites);
+
+ if (!foundWALWrites)
+ {
+ MemSet(pgstat_sql_counts, 0, pgstat_sql_shmem_size());
+ }
+}
+
+
+
/*
* subroutine for pgstat_reset_all
*/
@@ -1339,11 +1373,21 @@ pgstat_reset_shared_counters(const char *target)
msg.m_resettarget = RESET_ARCHIVER;
else if (strcmp(target, "bgwriter") == 0)
msg.m_resettarget = RESET_BGWRITER;
+ else if (strcmp(target, "sqlstmt") == 0)
+ {
+ /*
+ * Reset the pgstat sql counters. These statistics are not reset
+ * by the stats collector because they reside in shared memory.
+ */
+ LWLockAcquire(WALWriteLock, LW_EXCLUSIVE);
+ memset(pgstat_sql_counts, 0, pgstat_sql_shmem_size());
+ LWLockRelease(WALWriteLock);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized reset target: \"%s\"", target),
- errhint("Target must be \"archiver\" or \"bgwriter\".")));
+ errhint("Target must be \"archiver\" or \"bgwriter\" or \"sqlstmt\".")));
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSHAREDCOUNTER);
pgstat_send(&msg, sizeof(msg));
@@ -4413,6 +4457,17 @@ pgstat_send_bgwriter(void)
}
+/*
+ * Count SQL statement for pg_stat_sql view
+ */
+void
+pgstat_count_sqlstmt(StatSqlType sqlType)
+{
+ LWLockAcquire(PGSTATSqlLock, LW_EXCLUSIVE);
+ pgstat_sql_counts[sqlType] += 1;
+ LWLockRelease(PGSTATSqlLock);
+}
+
/* ----------
* PgstatCollectorMain() -
*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 4829953..380e2fe 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -147,6 +147,7 @@ CreateSharedMemoryAndSemaphores(void)
size = add_size(size, BTreeShmemSize());
size = add_size(size, SyncScanShmemSize());
size = add_size(size, AsyncShmemSize());
+ size = add_size(size, pgstat_sql_shmem_size());
#ifdef EXEC_BACKEND
size = add_size(size, ShmemBackendArraySize());
#endif
@@ -212,6 +213,7 @@ CreateSharedMemoryAndSemaphores(void)
* Set up xlog, clog, and buffers
*/
XLOGShmemInit();
+ pgstat_sql_shmem_init();
CLOGShmemInit();
CommitTsShmemInit();
SUBTRANSShmemInit();
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index db47843..50214d6 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -49,3 +49,4 @@ MultiXactTruncationLock 41
OldSnapshotTimeMapLock 42
LogicalRepWorkerLock 43
CLogTruncationLock 44
+PGSTATSqlLock 45
\ No newline at end of file
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index d7a72c0..4957e21 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1063,6 +1063,7 @@ exec_simple_query(const char *query_string)
RawStmt *parsetree = lfirst_node(RawStmt, parsetree_item);
bool snapshot_set = false;
const char *commandTag;
+ StatSqlType eSqlType;
char completionTag[COMPLETION_TAG_BUFSIZE];
MemoryContext per_parsetree_context = NULL;
List *querytree_list,
@@ -1077,8 +1078,7 @@ exec_simple_query(const char *query_string)
* do any special start-of-SQL-command processing needed by the
* destination.
*/
- commandTag = CreateCommandTag(parsetree->stmt);
-
+ commandTag = CreateCommandTagType(parsetree->stmt, &eSqlType);
set_ps_display(commandTag, false);
BeginCommand(commandTag, dest);
@@ -1234,6 +1234,12 @@ exec_simple_query(const char *query_string)
PortalDrop(portal, false);
+ /*
+ * Count SQL statement for pg_stat_sql view
+ */
+ if (pgstat_track_statement_statistics)
+ pgstat_count_sqlstmt(eSqlType);
+
if (lnext(parsetree_list, parsetree_item) == NULL)
{
/*
@@ -2118,6 +2124,14 @@ exec_execute_message(const char *portal_name, long max_rows)
receiver->rDestroy(receiver);
+ /*
+ * Count SQL Statement for pgx_stat_sql
+ */
+ if (pgstat_track_statement_statistics && !execute_is_fetch)
+ {
+ pgstat_count_sqlstmt(T_Stat_EXECUTE);
+ }
+
if (completed)
{
if (is_xact_command)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6787d8e..3e4f433 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -85,6 +85,193 @@ static void ProcessUtilitySlow(ParseState *pstate,
char *completionTag);
static void ExecDropStmt(DropStmt *stmt, bool isTopLevel);
+const char* g_str_sql_type[] =
+{
+ [T_Stat_INSERT] = "INSERT",
+ [T_Stat_DELETE] = "DELETE",
+ [T_Stat_UPDATE] = "UPDATE",
+ [T_Stat_SELECT] = "SELECT",
+ [T_Stat_BEGIN] = "BEGIN",
+ [T_Stat_START_TRANSACTION] = "START TRANSACTION",
+ [T_Stat_COMMIT] = "COMMIT",
+ [T_Stat_ROLLBACK] = "ROLLBACK",
+ [T_Stat_SAVEPOINT] = "SAVEPOINT",
+ [T_Stat_RELEASE] = "RELEASE",
+ [T_Stat_PREPARE_TRANSACTION] = "PREPARE TRANSACTION",
+ [T_Stat_COMMIT_PREPARED] = "COMMIT PREPARED",
+ [T_Stat_ROLLBACK_PREPARED] = "ROLLBACK PREPARED",
+ [T_Stat_DECLARE_CURSOR] = "DECLARE CURSOR",
+ [T_Stat_CLOSE_CURSOR_ALL] = "CLOSE CURSOR ALL",
+ [T_Stat_CLOSE_CURSOR] = "CLOSE CURSOR",
+ [T_Stat_MOVE] = "MOVE",
+ [T_Stat_FETCH] = "FETCH",
+ [T_Stat_CREATE_DOMAIN] = "CREATE DOMAIN",
+ [T_Stat_CREATE_SCHEMA] = "CREATE SCHEMA",
+ [T_Stat_CREATE_TABLE] = "CREATE TABLE",
+ [T_Stat_CREATE_TABLESPACE] = "CREATE TABLESPACE",
+ [T_Stat_DROP_TABLESPACE] = "DROP TABLESPACE",
+ [T_Stat_ALTER_TABLESPACE] = "ALTER TABLESPACE",
+ [T_Stat_CREATE_EXTENSION] = "CREATE EXTENSION",
+ [T_Stat_ALTER_EXTENSION] = "ALTER EXTENSION",
+ [T_Stat_CREATE_FOREIGN_DATA_WRAPPER] = "CREATE FOREIGN DATA WRAPPER",
+ [T_Stat_ALTER_FOREIGN_DATA_WRAPPER] = "ALTER FOREIGN DATA WRAPPER",
+ [T_Stat_CREATE_SERVER] = "CREATE SERVER",
+ [T_Stat_ALTER_SERVER] = "ALTER SERVER",
+ [T_Stat_CREATE_USER_MAPPING] = "CREATE USER MAPPING",
+ [T_Stat_ALTER_USER_MAPPING] = "ALTER USER MAPPING",
+ [T_Stat_DROP_USER_MAPPING] = "DROP USER MAPPING",
+ [T_Stat_CREATE_FOREIGN_TABLE] = "CREATE FOREIGN TABLE",
+ [T_Stat_IMPORT_FOREIGN_SCHEMA] = "IMPORT FOREIGN SCHEMA",
+ [T_Stat_DROP_TABLE] = "DROP TABLE",
+ [T_Stat_DROP_SEQUENCE] = "DROP SEQUENCE",
+ [T_Stat_DROP_VIEW] = "DROP VIEW",
+ [T_Stat_DROP_MATERIALIZED_VIEW] = "DROP MATERIALIZED VIEW",
+ [T_Stat_DROP_INDEX] = "DROP INDEX",
+ [T_Stat_DROP_TYPE] = "DROP TYPE",
+ [T_Stat_DROP_DOMAIN] = "DROP DOMAIN",
+ [T_Stat_DROP_COLLATION] = "DROP COLLATION",
+ [T_Stat_DROP_CONVERSION] = "DROP CONVERSION",
+ [T_Stat_DROP_SCHEMA] = "DROP SCHEMA",
+ [T_Stat_DROP_TEXT_SEARCH_PARSER] = "DROP TEXT SEARCH PARSER",
+ [T_Stat_DROP_TEXT_SEARCH_DICTIONARY] = "DROP TEXT SEARCH DICTIONARY",
+ [T_Stat_DROP_TEXT_SEARCH_TEMPLATE] = "DROP TEXT SEARCH TEMPLATE",
+ [T_Stat_DROP_DROP_TEXT_SEARCH_CONFIGURATION] = "DROP TEXT SEARCH CONFIGURATION",
+ [T_Stat_DROP_DROP_FOREIGN_TABLE] = "DROP FOREIGN TABLE",
+ [T_Stat_DROP_EXTENSION] = "DROP EXTENSION",
+ [T_Stat_DROP_FUNCTION] = "DROP FUNCTION",
+ [T_Stat_DROP_PROCEDURE] = "DROP PROCEDURE",
+ [T_Stat_DROP_ROUTINE] = "DROP ROUTINE",
+ [T_Stat_DROP_AGGREGATE] = "DROP AGGREGATE",
+ [T_Stat_DROP_OPERATOR] = "DROP OPERATOR",
+ [T_Stat_DROP_LANGUAGE] = "DROP LANGUAGE",
+ [T_Stat_DROP_CAST] = "DROP CAST",
+ [T_Stat_DROP_TRIGGER] = "DROP TRIGGER",
+ [T_Stat_DROP_EVENT_TRIGGER] = "DROP EVENT TRIGGER",
+ [T_Stat_DROP_RULE] = "DROP RULE",
+ [T_Stat_DROP_FOREIGN_DATA_WRAPPER] = "DROP FOREIGN DATA WRAPPER",
+ [T_Stat_DROP_SERVER] = "DROP SERVER",
+ [T_Stat_DROP_OPERATOR_CLASS] = "DROP OPERATOR CLASS",
+ [T_Stat_DROP_OPERATOR_FAMILY] = "DROP OPERATOR FAMILY",
+ [T_Stat_DROP_POLICY] = "DROP POLICY",
+ [T_Stat_DROP_TRANSFORM] = "DROP TRANSFORM",
+ [T_Stat_DROP_ACCESS_METHOD] = "DROP ACCESS METHOD",
+ [T_Stat_DROP_PUBLICATION] = "DROP PUBLICATION",
+ [T_Stat_DROP_STATISTICS] = "DROP STATISTICS",
+ [T_Stat_TRUNCATE_TABLE] = "TRUNCATE TABLE",
+ [T_Stat_COMMENT] = "COMMENT",
+ [T_Stat_SECURITY_LABEL] = "SECURITY LABEL",
+ [T_Stat_COPY] = "COPY",
+ [T_Stat_ALTER_AGGREGATE] = "ALTER AGGREGATE",
+ [T_Stat_ALTER_TYPE] = "ALTER TYPE",
+ [T_Stat_ALTER_CAST] = "ALTER CAST",
+ [T_Stat_ALTER_COLLATION] = "ALTER COLLATION",
+ [T_Stat_ALTER_TABLE] = "ALTER TABLE",
+ [T_Stat_ALTER_CONVERSION] = "ALTER CONVERSION",
+ [T_Stat_ALTER_DATABASE] = "ALTER DATABASE",
+ [T_Stat_ALTER_DOMAIN] = "ALTER DOMAIN",
+ [T_Stat_ALTER_FOREIGN_TABLE] = "ALTER FOREIGN TABLE",
+ [T_Stat_ALTER_FUNCTION] = "ALTER FUNCTION",
+ [T_Stat_ALTER_INDEX] = "ALTER INDEX",
+ [T_Stat_ALTER_LANGUAGE] = "ALTER LANGUAGE",
+ [T_Stat_ALTER_LARGE_OBJECT] = "ALTER LARGE OBJECT",
+ [T_Stat_ALTER_OPERATOR_CLASS] = "ALTER OPERATOR CLASS",
+ [T_Stat_ALTER_OPERATOR] = "ALTER OPERATOR",
+ [T_Stat_ALTER_OPERATOR_FAMILY] = "ALTER OPERATOR FAMILY",
+ [T_Stat_ALTER_POLICY] = "ALTER POLICY",
+ [T_Stat_ALTER_PROCEDURE] = "ALTER PROCEDURE",
+ [T_Stat_ALTER_ROLE] = "ALTER ROLE",
+ [T_Stat_ALTER_ROUTINE] = "ALTER ROUTINE",
+ [T_Stat_ALTER_RULE] = "ALTER RULE",
+ [T_Stat_ALTER_SCHEMA] = "ALTER SCHEMA",
+ [T_Stat_ALTER_SEQUENCE] = "ALTER SEQUENCE",
+ [T_Stat_ALTER_TRIGGER] = "ALTER TRIGGER",
+ [T_Stat_ALTER_EVENT_TRIGGER] = "ALTER EVENT TRIGGER",
+ [T_Stat_ALTER_TEXT_SEARCH_CONFIGURATION] = "ALTER TEXT SEARCH CONFIGURATION",
+ [T_Stat_ALTER_TEXT_SEARCH_DICTIONARY] = "ALTER TEXT SEARCH DICTIONARY",
+ [T_Stat_ALTER_TEXT_SEARCH_PARSER] = "ALTER TEXT SEARCH PARSER",
+ [T_Stat_ALTER_TEXT_SEARCH_TEMPLATE] = "ALTER TEXT SEARCH TEMPLATE",
+ [T_Stat_ALTER_VIEW] = "ALTER VIEW",
+ [T_Stat_ALTER_MATERIALIZED_VIEW] = "ALTER MATERIALIZED VIEW",
+ [T_Stat_ALTER_PUBLICATION] = "ALTER PUBLICATION",
+ [T_Stat_ALTER_SUBSCRIPTION] = "ALTER SUBSCRIPTION",
+ [T_Stat_ALTER_STATISTICS] = "ALTER STATISTICS",
+ [T_Stat_GRANT] = "GRANT",
+ [T_Stat_REVOKE] = "REVOKE",
+ [T_Stat_GRANT_ROLE] = "GRANT ROLE",
+ [T_Stat_REVOKE_ROLE] = "REVOKE ROLE",
+ [T_Stat_ALTER_DEFAULT_PRIVILEGES] = "ALTER DEFAULT PRIVILEGES",
+ [T_Stat_CREATE_AGGREGATE] = "CREATE AGGREGATE",
+ [T_Stat_CREATE_OPERATOR] = "CREATE OPERATOR",
+ [T_Stat_CREATE_TYPE] = "CREATE TYPE",
+ [T_Stat_CREATE_TEXT_SEARCH_PARSER] = "CREATE TEXT SEARCH PARSER",
+ [T_Stat_CREATE_TEXT_SEARCH_DICTIONARY] = "CREATE TEXT SEARCH DICTIONARY",
+ [T_Stat_CREATE_TEXT_SEARCH_TEMPLATE] = "CREATE TEXT SEARCH TEMPLATE",
+ [T_Stat_CREATE_TEXT_SEARCH_CONFIGURATION] = "CREATE TEXT SEARCH CONFIGURATION",
+ [T_Stat_CREATE_COLLATION] = "CREATE COLLATION",
+ [T_Stat_CREATE_ACCESS_METHOD] = "CREATE ACCESS METHOD",
+ [T_Stat_CREATE_VIEW] = "CREATE VIEW",
+ [T_Stat_CREATE_PROCEDURE] = "CREATE PROCEDURE",
+ [T_Stat_CREATE_FUNCTION] = "CREATE FUNCTION",
+ [T_Stat_CREATE_INDEX] = "CREATE INDEX",
+ [T_Stat_CREATE_RULE] = "CREATE RULE",
+ [T_Stat_CREATE_SEQUENCE] = "CREATE SEQUENCE",
+ [T_Stat_DO] = "DO",
+ [T_Stat_CREATE_DATABASE] = "CREATE DATABASE",
+ [T_Stat_DROP_DATABASE] = "DROP DATABASE",
+ [T_Stat_NOTIFY] = "NOTIFY",
+ [T_Stat_LISTEN] = "LISTEN",
+ [T_Stat_UNLISTEN] = "UNLISTEN",
+ [T_Stat_LOAD] = "LOAD",
+ [T_Stat_CALL] = "CALL",
+ [T_Stat_CLUSTER] = "CLUSTER",
+ [T_Stat_VACUUM] = "VACUUM",
+ [T_Stat_ANALYZE] = "ANALYZE",
+ [T_Stat_EXPLAIN] = "EXPLAIN",
+ [T_Stat_SELECT_INTO] = "SELECT INTO",
+ [T_Stat_CREATE_TABLE_AS] = "CREATE TABLE AS",
+ [T_Stat_CREATE_MATERIALIZED_VIEW] = "CREATE MATERIALIZED VIEW",
+ [T_Stat_REFRESH_MATERIALIZED_VIEW] = "REFRESH MATERIALIZED VIEW",
+ [T_Stat_ALTER_SYSTEM] = "ALTER SYSTEM",
+ [T_Stat_SET] = "SET",
+ [T_Stat_RESET] = "RESET",
+ [T_Stat_SHOW] = "SHOW",
+ [T_Stat_DISCARD_ALL] = "DISCARD ALL",
+ [T_Stat_DISCARD_PLANS] = "DISCARD PLANS",
+ [T_Stat_DISCARD_TEMP] = "DISCARD TEMP",
+ [T_Stat_DISCARD_SEQUENCES] = "DISCARD SEQUENCES",
+ [T_Stat_CREATE_TRANSFORM] = "CREATE TRANSFORM",
+ [T_Stat_CREATE_TRIGGER] = "CREATE TRIGGER",
+ [T_Stat_CREATE_EVENT_TRIGGER] = "CREATE EVENT TRIGGER",
+ [T_Stat_CREATE_LANGUAGE] = "CREATE LANGUAGE",
+ [T_Stat_CREATE_ROLE] = "CREATE ROLE",
+ [T_Stat_DROP_ROLE] = "DROP ROLE",
+ [T_Stat_DROP_OWNED] = "DROP OWNED",
+ [T_Stat_REASSIGN_OWNED] = "REASSIGN OWNED",
+ [T_Stat_LOCK_TABLE] = "LOCK TABLE",
+ [T_Stat_SET_CONSTRAINTS] = "SET CONSTRAINTS",
+ [T_Stat_CHECKPOINT] = "CHECKPOINT",
+ [T_Stat_REINDEX] = "REINDEX",
+ [T_Stat_CREATE_CONVERSION] = "CREATE CONVERSION",
+ [T_Stat_CREATE_CAST] = "CREATE CAST",
+ [T_Stat_CREATE_OPERATOR_CLASS] = "CREATE OPERATOR CLASS",
+ [T_Stat_CREATE_OPERATOR_FAMILY] = "CREATE OPERATOR FAMILY",
+ [T_Stat_CREATE_POLICY] = "CREATE POLICY",
+ [T_Stat_CREATE_PUBLICATION] = "CREATE PUBLICATION",
+ [T_Stat_CREATE_SUBSCRIPTION] = "CREATE SUBSCRIPTION",
+ [T_Stat_DROP_SUBSCRIPTION] = "DROP SUBSCRIPTION",
+ [T_Stat_PREPARE] = "PREPARE",
+ [T_Stat_EXECUTE] = "EXECUTE",
+ [T_Stat_CREATE_STATISTICS] = "CREATE STATISTICS",
+ [T_Stat_DEALLOCATE_ALL] = "DEALLOCATE ALL",
+ [T_Stat_DEALLOCATE] = "DEALLOCATE",
+ [T_Stat_SELECT_FOR_KEY_SHARE] = "SELECT FOR KEY SHARE",
+ [T_Stat_SELECT_FOR_SHARE] = "SELECT FOR SHARE",
+ [T_Stat_SELECT_FOR_NO_KEY_UPDATE] = "SELECT FOR NO KEY UPDATE",
+ [T_Stat_SELECT_FOR_UPDATE] = "SELECT FOR UPDATE",
+ [T_Stat_UNKNOWN] = "???"
+};
+
+
/*
* CommandIsReadOnly: is an executable query read-only?
@@ -1939,13 +2126,13 @@ UtilityContainsQuery(Node *parsetree)
/*
- * AlterObjectTypeCommandTag
- * helper function for CreateCommandTag
+ * AlterObjectTypeCommandTagType
+ * helper function for CreateCommandTagType
*
* This covers most cases where ALTER is used with an ObjectType enum.
*/
static const char *
-AlterObjectTypeCommandTag(ObjectType objtype)
+AlterObjectTypeCommandTagType(ObjectType objtype, StatSqlType* pESqlType)
{
const char *tag;
@@ -1953,128 +2140,169 @@ AlterObjectTypeCommandTag(ObjectType objtype)
{
case OBJECT_AGGREGATE:
tag = "ALTER AGGREGATE";
+ *pESqlType = T_Stat_ALTER_AGGREGATE;
break;
case OBJECT_ATTRIBUTE:
tag = "ALTER TYPE";
+ *pESqlType = T_Stat_ALTER_TYPE;
break;
case OBJECT_CAST:
tag = "ALTER CAST";
+ *pESqlType = T_Stat_ALTER_CAST;
break;
case OBJECT_COLLATION:
tag = "ALTER COLLATION";
+ *pESqlType = T_Stat_ALTER_COLLATION;
break;
case OBJECT_COLUMN:
tag = "ALTER TABLE";
+ *pESqlType = T_Stat_ALTER_TABLE;
break;
case OBJECT_CONVERSION:
tag = "ALTER CONVERSION";
+ *pESqlType = T_Stat_ALTER_CONVERSION;
break;
case OBJECT_DATABASE:
tag = "ALTER DATABASE";
+ *pESqlType = T_Stat_ALTER_DATABASE;
break;
case OBJECT_DOMAIN:
case OBJECT_DOMCONSTRAINT:
tag = "ALTER DOMAIN";
+ *pESqlType = T_Stat_ALTER_DOMAIN;
break;
case OBJECT_EXTENSION:
tag = "ALTER EXTENSION";
+ *pESqlType = T_Stat_ALTER_EXTENSION;
break;
case OBJECT_FDW:
tag = "ALTER FOREIGN DATA WRAPPER";
+ *pESqlType = T_Stat_ALTER_FOREIGN_DATA_WRAPPER;
break;
case OBJECT_FOREIGN_SERVER:
tag = "ALTER SERVER";
+ *pESqlType = T_Stat_ALTER_SERVER;
break;
case OBJECT_FOREIGN_TABLE:
tag = "ALTER FOREIGN TABLE";
+ *pESqlType = T_Stat_ALTER_FOREIGN_TABLE;
break;
case OBJECT_FUNCTION:
tag = "ALTER FUNCTION";
+ *pESqlType = T_Stat_ALTER_FUNCTION;
break;
case OBJECT_INDEX:
tag = "ALTER INDEX";
+ *pESqlType = T_Stat_ALTER_INDEX;
break;
case OBJECT_LANGUAGE:
tag = "ALTER LANGUAGE";
+ *pESqlType = T_Stat_ALTER_LANGUAGE;
break;
case OBJECT_LARGEOBJECT:
tag = "ALTER LARGE OBJECT";
+ *pESqlType = T_Stat_ALTER_LARGE_OBJECT;
break;
case OBJECT_OPCLASS:
tag = "ALTER OPERATOR CLASS";
+ *pESqlType = T_Stat_ALTER_OPERATOR_CLASS;
break;
case OBJECT_OPERATOR:
tag = "ALTER OPERATOR";
+ *pESqlType = T_Stat_ALTER_OPERATOR;
break;
case OBJECT_OPFAMILY:
tag = "ALTER OPERATOR FAMILY";
+ *pESqlType = T_Stat_ALTER_OPERATOR_FAMILY;
break;
case OBJECT_POLICY:
tag = "ALTER POLICY";
+ *pESqlType = T_Stat_ALTER_POLICY;
break;
case OBJECT_PROCEDURE:
tag = "ALTER PROCEDURE";
+ *pESqlType = T_Stat_ALTER_PROCEDURE;
break;
case OBJECT_ROLE:
tag = "ALTER ROLE";
+ *pESqlType = T_Stat_ALTER_ROLE;
break;
case OBJECT_ROUTINE:
tag = "ALTER ROUTINE";
+ *pESqlType = T_Stat_ALTER_ROUTINE;
break;
case OBJECT_RULE:
tag = "ALTER RULE";
+ *pESqlType = T_Stat_ALTER_RULE;
break;
case OBJECT_SCHEMA:
tag = "ALTER SCHEMA";
+ *pESqlType = T_Stat_ALTER_SCHEMA;
break;
case OBJECT_SEQUENCE:
tag = "ALTER SEQUENCE";
+ *pESqlType = T_Stat_ALTER_SEQUENCE;
break;
case OBJECT_TABLE:
case OBJECT_TABCONSTRAINT:
tag = "ALTER TABLE";
+ *pESqlType = T_Stat_ALTER_TABLE;
break;
case OBJECT_TABLESPACE:
tag = "ALTER TABLESPACE";
+ *pESqlType = T_Stat_ALTER_TABLESPACE;
break;
case OBJECT_TRIGGER:
tag = "ALTER TRIGGER";
+ *pESqlType = T_Stat_ALTER_TRIGGER;
break;
case OBJECT_EVENT_TRIGGER:
tag = "ALTER EVENT TRIGGER";
+ *pESqlType = T_Stat_ALTER_EVENT_TRIGGER;
break;
case OBJECT_TSCONFIGURATION:
tag = "ALTER TEXT SEARCH CONFIGURATION";
+ *pESqlType = T_Stat_ALTER_TEXT_SEARCH_CONFIGURATION;
break;
case OBJECT_TSDICTIONARY:
tag = "ALTER TEXT SEARCH DICTIONARY";
+ *pESqlType = T_Stat_ALTER_TEXT_SEARCH_DICTIONARY;
break;
case OBJECT_TSPARSER:
tag = "ALTER TEXT SEARCH PARSER";
+ *pESqlType = T_Stat_ALTER_TEXT_SEARCH_PARSER;
break;
case OBJECT_TSTEMPLATE:
tag = "ALTER TEXT SEARCH TEMPLATE";
+ *pESqlType = T_Stat_ALTER_TEXT_SEARCH_TEMPLATE;
break;
case OBJECT_TYPE:
tag = "ALTER TYPE";
+ *pESqlType = T_Stat_ALTER_TYPE;
break;
case OBJECT_VIEW:
tag = "ALTER VIEW";
+ *pESqlType = T_Stat_ALTER_VIEW;
break;
case OBJECT_MATVIEW:
tag = "ALTER MATERIALIZED VIEW";
+ *pESqlType = T_Stat_ALTER_MATERIALIZED_VIEW;
break;
case OBJECT_PUBLICATION:
tag = "ALTER PUBLICATION";
+ *pESqlType = T_Stat_ALTER_PUBLICATION;
break;
case OBJECT_SUBSCRIPTION:
tag = "ALTER SUBSCRIPTION";
+ *pESqlType = T_Stat_ALTER_SUBSCRIPTION;
break;
case OBJECT_STATISTIC_EXT:
tag = "ALTER STATISTICS";
+ *pESqlType = T_Stat_ALTER_STATISTICS;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
break;
}
@@ -2082,19 +2310,15 @@ AlterObjectTypeCommandTag(ObjectType objtype)
}
/*
- * CreateCommandTag
- * utility to get a string representation of the command operation,
+ * CreateCommandType
+ * utility to get a StatSqlType representation of the command operation,
* given either a raw (un-analyzed) parsetree, an analyzed Query,
* or a PlannedStmt.
- *
- * This must handle all command types, but since the vast majority
- * of 'em are utility commands, it seems sensible to keep it here.
- *
- * NB: all result strings must be shorter than COMPLETION_TAG_BUFSIZE.
- * Also, the result must point at a true constant (permanent storage).
+ * This function is copy-and-modify from CreateCommandTag,
+ * so if CreateCommandTag changed, this function should be changed accordingly.
*/
-const char *
-CreateCommandTag(Node *parsetree)
+const char*
+CreateCommandTagType(Node *parsetree, StatSqlType* pESqlType)
{
const char *tag;
@@ -2102,24 +2326,28 @@ CreateCommandTag(Node *parsetree)
{
/* recurse if we're given a RawStmt */
case T_RawStmt:
- tag = CreateCommandTag(((RawStmt *) parsetree)->stmt);
+ tag = CreateCommandTagType(((RawStmt *) parsetree)->stmt, pESqlType);
break;
/* raw plannable queries */
case T_InsertStmt:
tag = "INSERT";
+ *pESqlType = T_Stat_INSERT;
break;
case T_DeleteStmt:
tag = "DELETE";
+ *pESqlType = T_Stat_DELETE;
break;
case T_UpdateStmt:
tag = "UPDATE";
+ *pESqlType = T_Stat_UPDATE;
break;
case T_SelectStmt:
tag = "SELECT";
+ *pESqlType = T_Stat_SELECT;
break;
/* utility statements --- same whether raw or cooked */
@@ -2131,43 +2359,53 @@ CreateCommandTag(Node *parsetree)
{
case TRANS_STMT_BEGIN:
tag = "BEGIN";
+ *pESqlType = T_Stat_BEGIN;
break;
case TRANS_STMT_START:
tag = "START TRANSACTION";
+ *pESqlType = T_Stat_START_TRANSACTION;
break;
case TRANS_STMT_COMMIT:
tag = "COMMIT";
+ *pESqlType = T_Stat_COMMIT;
break;
case TRANS_STMT_ROLLBACK:
case TRANS_STMT_ROLLBACK_TO:
tag = "ROLLBACK";
+ *pESqlType = T_Stat_ROLLBACK;
break;
case TRANS_STMT_SAVEPOINT:
tag = "SAVEPOINT";
+ *pESqlType = T_Stat_SAVEPOINT;
break;
case TRANS_STMT_RELEASE:
tag = "RELEASE";
+ *pESqlType = T_Stat_RELEASE;
break;
case TRANS_STMT_PREPARE:
tag = "PREPARE TRANSACTION";
+ *pESqlType = T_Stat_PREPARE_TRANSACTION;
break;
case TRANS_STMT_COMMIT_PREPARED:
tag = "COMMIT PREPARED";
+ *pESqlType = T_Stat_COMMIT_PREPARED;
break;
case TRANS_STMT_ROLLBACK_PREPARED:
tag = "ROLLBACK PREPARED";
+ *pESqlType = T_Stat_ROLLBACK_PREPARED;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
break;
}
}
@@ -2175,6 +2413,7 @@ CreateCommandTag(Node *parsetree)
case T_DeclareCursorStmt:
tag = "DECLARE CURSOR";
+ *pESqlType = T_Stat_DECLARE_CURSOR;
break;
case T_ClosePortalStmt:
@@ -2182,90 +2421,114 @@ CreateCommandTag(Node *parsetree)
ClosePortalStmt *stmt = (ClosePortalStmt *) parsetree;
if (stmt->portalname == NULL)
+ {
tag = "CLOSE CURSOR ALL";
+ *pESqlType = T_Stat_CLOSE_CURSOR_ALL;
+ }
else
+ {
tag = "CLOSE CURSOR";
+ *pESqlType = T_Stat_CLOSE_CURSOR;
+ }
}
break;
case T_FetchStmt:
{
FetchStmt *stmt = (FetchStmt *) parsetree;
-
tag = (stmt->ismove) ? "MOVE" : "FETCH";
+ *pESqlType = (stmt->ismove) ? T_Stat_MOVE : T_Stat_FETCH;
}
break;
case T_CreateDomainStmt:
tag = "CREATE DOMAIN";
+ *pESqlType = T_Stat_CREATE_DOMAIN;
break;
case T_CreateSchemaStmt:
tag = "CREATE SCHEMA";
+ *pESqlType = T_Stat_CREATE_SCHEMA;
break;
case T_CreateStmt:
tag = "CREATE TABLE";
+ *pESqlType = T_Stat_CREATE_TABLE;
break;
case T_CreateTableSpaceStmt:
tag = "CREATE TABLESPACE";
+ *pESqlType = T_Stat_CREATE_TABLESPACE;
break;
case T_DropTableSpaceStmt:
tag = "DROP TABLESPACE";
+ *pESqlType = T_Stat_DROP_TABLESPACE;
break;
case T_AlterTableSpaceOptionsStmt:
tag = "ALTER TABLESPACE";
+ *pESqlType = T_Stat_ALTER_TABLESPACE;
break;
case T_CreateExtensionStmt:
tag = "CREATE EXTENSION";
+ *pESqlType = T_Stat_CREATE_EXTENSION;
break;
case T_AlterExtensionStmt:
tag = "ALTER EXTENSION";
+ *pESqlType = T_Stat_ALTER_EXTENSION;
break;
case T_AlterExtensionContentsStmt:
tag = "ALTER EXTENSION";
+ *pESqlType = T_Stat_ALTER_EXTENSION;
break;
case T_CreateFdwStmt:
tag = "CREATE FOREIGN DATA WRAPPER";
+ *pESqlType = T_Stat_CREATE_FOREIGN_DATA_WRAPPER;
break;
case T_AlterFdwStmt:
tag = "ALTER FOREIGN DATA WRAPPER";
+ *pESqlType = T_Stat_ALTER_FOREIGN_DATA_WRAPPER;
break;
case T_CreateForeignServerStmt:
tag = "CREATE SERVER";
+ *pESqlType = T_Stat_CREATE_SERVER;
break;
case T_AlterForeignServerStmt:
tag = "ALTER SERVER";
+ *pESqlType = T_Stat_ALTER_SERVER;
break;
case T_CreateUserMappingStmt:
tag = "CREATE USER MAPPING";
+ *pESqlType = T_Stat_CREATE_USER_MAPPING;
break;
case T_AlterUserMappingStmt:
tag = "ALTER USER MAPPING";
+ *pESqlType = T_Stat_ALTER_USER_MAPPING;
break;
case T_DropUserMappingStmt:
tag = "DROP USER MAPPING";
+ *pESqlType = T_Stat_DROP_USER_MAPPING;
break;
case T_CreateForeignTableStmt:
tag = "CREATE FOREIGN TABLE";
+ *pESqlType = T_Stat_CREATE_FOREIGN_TABLE;
break;
case T_ImportForeignSchemaStmt:
tag = "IMPORT FOREIGN SCHEMA";
+ *pESqlType = T_Stat_IMPORT_FOREIGN_SCHEMA;
break;
case T_DropStmt:
@@ -2273,128 +2536,168 @@ CreateCommandTag(Node *parsetree)
{
case OBJECT_TABLE:
tag = "DROP TABLE";
+ *pESqlType = T_Stat_DROP_TABLE;
break;
case OBJECT_SEQUENCE:
tag = "DROP SEQUENCE";
+ *pESqlType = T_Stat_DROP_SEQUENCE;
break;
case OBJECT_VIEW:
tag = "DROP VIEW";
+ *pESqlType = T_Stat_DROP_VIEW;
break;
case OBJECT_MATVIEW:
tag = "DROP MATERIALIZED VIEW";
+ *pESqlType = T_Stat_DROP_MATERIALIZED_VIEW;
break;
case OBJECT_INDEX:
tag = "DROP INDEX";
+ *pESqlType= T_Stat_DROP_INDEX;
break;
case OBJECT_TYPE:
tag = "DROP TYPE";
+ *pESqlType = T_Stat_DROP_TYPE;
break;
case OBJECT_DOMAIN:
tag = "DROP DOMAIN";
+ *pESqlType = T_Stat_DROP_DOMAIN;
break;
case OBJECT_COLLATION:
tag = "DROP COLLATION";
+ *pESqlType = T_Stat_DROP_COLLATION;
break;
case OBJECT_CONVERSION:
tag = "DROP CONVERSION";
+ *pESqlType = T_Stat_DROP_CONVERSION;
break;
case OBJECT_SCHEMA:
tag = "DROP SCHEMA";
+ *pESqlType = T_Stat_DROP_SCHEMA;
break;
case OBJECT_TSPARSER:
tag = "DROP TEXT SEARCH PARSER";
+ *pESqlType = T_Stat_DROP_TEXT_SEARCH_PARSER;
break;
case OBJECT_TSDICTIONARY:
tag = "DROP TEXT SEARCH DICTIONARY";
+ *pESqlType = T_Stat_DROP_TEXT_SEARCH_DICTIONARY;
break;
case OBJECT_TSTEMPLATE:
tag = "DROP TEXT SEARCH TEMPLATE";
+ *pESqlType = T_Stat_DROP_TEXT_SEARCH_TEMPLATE;
break;
case OBJECT_TSCONFIGURATION:
tag = "DROP TEXT SEARCH CONFIGURATION";
+ *pESqlType = T_Stat_DROP_DROP_TEXT_SEARCH_CONFIGURATION;
break;
case OBJECT_FOREIGN_TABLE:
tag = "DROP FOREIGN TABLE";
+ *pESqlType = T_Stat_DROP_DROP_FOREIGN_TABLE;
break;
case OBJECT_EXTENSION:
tag = "DROP EXTENSION";
+ *pESqlType = T_Stat_DROP_EXTENSION;
break;
case OBJECT_FUNCTION:
tag = "DROP FUNCTION";
+ *pESqlType = T_Stat_DROP_FUNCTION;
break;
case OBJECT_PROCEDURE:
tag = "DROP PROCEDURE";
+ *pESqlType = T_Stat_DROP_PROCEDURE;
break;
case OBJECT_ROUTINE:
tag = "DROP ROUTINE";
+ *pESqlType = T_Stat_DROP_ROUTINE;
break;
case OBJECT_AGGREGATE:
tag = "DROP AGGREGATE";
+ *pESqlType = T_Stat_DROP_AGGREGATE;
break;
case OBJECT_OPERATOR:
tag = "DROP OPERATOR";
+ *pESqlType = T_Stat_DROP_OPERATOR;
break;
case OBJECT_LANGUAGE:
tag = "DROP LANGUAGE";
+ *pESqlType = T_Stat_DROP_LANGUAGE;
break;
case OBJECT_CAST:
tag = "DROP CAST";
+ *pESqlType = T_Stat_DROP_CAST;
break;
case OBJECT_TRIGGER:
tag = "DROP TRIGGER";
+ *pESqlType = T_Stat_DROP_TRIGGER;
break;
case OBJECT_EVENT_TRIGGER:
tag = "DROP EVENT TRIGGER";
+ *pESqlType = T_Stat_DROP_EVENT_TRIGGER;
break;
case OBJECT_RULE:
tag = "DROP RULE";
+ *pESqlType = T_Stat_DROP_RULE;
break;
case OBJECT_FDW:
tag = "DROP FOREIGN DATA WRAPPER";
+ *pESqlType = T_Stat_DROP_FOREIGN_DATA_WRAPPER;
break;
case OBJECT_FOREIGN_SERVER:
tag = "DROP SERVER";
+ *pESqlType = T_Stat_DROP_SERVER;
break;
case OBJECT_OPCLASS:
tag = "DROP OPERATOR CLASS";
+ *pESqlType = T_Stat_DROP_OPERATOR_CLASS;
break;
case OBJECT_OPFAMILY:
tag = "DROP OPERATOR FAMILY";
+ *pESqlType = T_Stat_DROP_OPERATOR_FAMILY;
break;
case OBJECT_POLICY:
tag = "DROP POLICY";
+ *pESqlType = T_Stat_DROP_POLICY;
break;
case OBJECT_TRANSFORM:
tag = "DROP TRANSFORM";
+ *pESqlType = T_Stat_DROP_TRANSFORM;
break;
case OBJECT_ACCESS_METHOD:
tag = "DROP ACCESS METHOD";
+ *pESqlType = T_Stat_DROP_ACCESS_METHOD;
break;
case OBJECT_PUBLICATION:
tag = "DROP PUBLICATION";
+ *pESqlType = T_Stat_DROP_PUBLICATION;
break;
case OBJECT_STATISTIC_EXT:
tag = "DROP STATISTICS";
+ *pESqlType = T_Stat_DROP_STATISTICS;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
}
break;
case T_TruncateStmt:
tag = "TRUNCATE TABLE";
+ *pESqlType = T_Stat_TRUNCATE_TABLE;
break;
case T_CommentStmt:
tag = "COMMENT";
+ *pESqlType = T_Stat_TRUNCATE_TABLE;
break;
case T_SecLabelStmt:
tag = "SECURITY LABEL";
+ *pESqlType = T_Stat_SECURITY_LABEL;
break;
case T_CopyStmt:
tag = "COPY";
+ *pESqlType = T_Stat_COPY;
break;
case T_RenameStmt:
@@ -2402,34 +2705,35 @@ CreateCommandTag(Node *parsetree)
* When the column is renamed, the command tag is created
* from its relation type
*/
- tag = AlterObjectTypeCommandTag(
+ tag = AlterObjectTypeCommandTagType(
((RenameStmt *) parsetree)->renameType == OBJECT_COLUMN ?
((RenameStmt *) parsetree)->relationType :
- ((RenameStmt *) parsetree)->renameType);
+ ((RenameStmt *) parsetree)->renameType, pESqlType);
break;
case T_AlterObjectDependsStmt:
- tag = AlterObjectTypeCommandTag(((AlterObjectDependsStmt *) parsetree)->objectType);
+ tag = AlterObjectTypeCommandTagType(((AlterObjectDependsStmt *) parsetree)->objectType, pESqlType);
break;
case T_AlterObjectSchemaStmt:
- tag = AlterObjectTypeCommandTag(((AlterObjectSchemaStmt *) parsetree)->objectType);
+ tag = AlterObjectTypeCommandTagType(((AlterObjectSchemaStmt *) parsetree)->objectType, pESqlType);
break;
case T_AlterOwnerStmt:
- tag = AlterObjectTypeCommandTag(((AlterOwnerStmt *) parsetree)->objectType);
+ tag = AlterObjectTypeCommandTagType(((AlterOwnerStmt *) parsetree)->objectType, pESqlType);
break;
case T_AlterTableMoveAllStmt:
- tag = AlterObjectTypeCommandTag(((AlterTableMoveAllStmt *) parsetree)->objtype);
+ tag = AlterObjectTypeCommandTagType(((AlterTableMoveAllStmt *) parsetree)->objtype, pESqlType);
break;
case T_AlterTableStmt:
- tag = AlterObjectTypeCommandTag(((AlterTableStmt *) parsetree)->relkind);
+ tag = AlterObjectTypeCommandTagType(((AlterTableStmt *) parsetree)->relkind, pESqlType);
break;
case T_AlterDomainStmt:
tag = "ALTER DOMAIN";
+ *pESqlType = T_Stat_ALTER_DOMAIN;
break;
case T_AlterFunctionStmt:
@@ -2437,15 +2741,19 @@ CreateCommandTag(Node *parsetree)
{
case OBJECT_FUNCTION:
tag = "ALTER FUNCTION";
+ *pESqlType = T_Stat_ALTER_FUNCTION;
break;
case OBJECT_PROCEDURE:
tag = "ALTER PROCEDURE";
+ *pESqlType = T_Stat_ALTER_PROCEDURE;
break;
case OBJECT_ROUTINE:
tag = "ALTER ROUTINE";
+ *pESqlType = T_Stat_ALTER_ROUTINE;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
}
break;
@@ -2454,6 +2762,7 @@ CreateCommandTag(Node *parsetree)
GrantStmt *stmt = (GrantStmt *) parsetree;
tag = (stmt->is_grant) ? "GRANT" : "REVOKE";
+ *pESqlType = (stmt->is_grant) ? T_Stat_GRANT : T_Stat_REVOKE;
}
break;
@@ -2462,11 +2771,13 @@ CreateCommandTag(Node *parsetree)
GrantRoleStmt *stmt = (GrantRoleStmt *) parsetree;
tag = (stmt->is_grant) ? "GRANT ROLE" : "REVOKE ROLE";
+ *pESqlType = (stmt->is_grant) ? T_Stat_GRANT_ROLE : T_Stat_REVOKE_ROLE;
}
break;
case T_AlterDefaultPrivilegesStmt:
tag = "ALTER DEFAULT PRIVILEGES";
+ *pESqlType = T_Stat_ALTER_DEFAULT_PRIVILEGES;
break;
case T_DefineStmt:
@@ -2474,132 +2785,175 @@ CreateCommandTag(Node *parsetree)
{
case OBJECT_AGGREGATE:
tag = "CREATE AGGREGATE";
+ *pESqlType = T_Stat_CREATE_AGGREGATE;
break;
case OBJECT_OPERATOR:
tag = "CREATE OPERATOR";
+ *pESqlType = T_Stat_CREATE_OPERATOR;
break;
case OBJECT_TYPE:
tag = "CREATE TYPE";
+ *pESqlType = T_Stat_CREATE_TYPE;
break;
case OBJECT_TSPARSER:
tag = "CREATE TEXT SEARCH PARSER";
+ *pESqlType = T_Stat_CREATE_TEXT_SEARCH_PARSER;
break;
case OBJECT_TSDICTIONARY:
tag = "CREATE TEXT SEARCH DICTIONARY";
+ *pESqlType = T_Stat_CREATE_TEXT_SEARCH_DICTIONARY;
break;
case OBJECT_TSTEMPLATE:
tag = "CREATE TEXT SEARCH TEMPLATE";
+ *pESqlType = T_Stat_CREATE_TEXT_SEARCH_TEMPLATE;
break;
case OBJECT_TSCONFIGURATION:
tag = "CREATE TEXT SEARCH CONFIGURATION";
+ *pESqlType = T_Stat_CREATE_TEXT_SEARCH_CONFIGURATION;
break;
case OBJECT_COLLATION:
tag = "CREATE COLLATION";
+ *pESqlType = T_Stat_CREATE_COLLATION;
break;
case OBJECT_ACCESS_METHOD:
tag = "CREATE ACCESS METHOD";
+ *pESqlType = T_Stat_CREATE_ACCESS_METHOD;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
}
break;
case T_CompositeTypeStmt:
tag = "CREATE TYPE";
+ *pESqlType = T_Stat_CREATE_TYPE;
break;
case T_CreateEnumStmt:
tag = "CREATE TYPE";
+ *pESqlType = T_Stat_CREATE_TYPE;
break;
case T_CreateRangeStmt:
tag = "CREATE TYPE";
+ *pESqlType = T_Stat_CREATE_TYPE;
break;
case T_AlterEnumStmt:
tag = "ALTER TYPE";
+ *pESqlType = T_Stat_ALTER_TYPE;
break;
case T_ViewStmt:
tag = "CREATE VIEW";
+ *pESqlType = T_Stat_CREATE_VIEW;
break;
case T_CreateFunctionStmt:
if (((CreateFunctionStmt *) parsetree)->is_procedure)
+ {
tag = "CREATE PROCEDURE";
+ *pESqlType = T_Stat_CREATE_PROCEDURE;
+ }
else
+ {
tag = "CREATE FUNCTION";
+ *pESqlType = T_Stat_CREATE_FUNCTION;
+ }
break;
case T_IndexStmt:
tag = "CREATE INDEX";
+ *pESqlType = T_Stat_CREATE_INDEX;
break;
case T_RuleStmt:
tag = "CREATE RULE";
+ *pESqlType = T_Stat_CREATE_RULE;
break;
case T_CreateSeqStmt:
tag = "CREATE SEQUENCE";
+ *pESqlType = T_Stat_CREATE_SEQUENCE;
break;
case T_AlterSeqStmt:
tag = "ALTER SEQUENCE";
+ *pESqlType = T_Stat_ALTER_SEQUENCE;
break;
case T_DoStmt:
tag = "DO";
+ *pESqlType = T_Stat_DO;
break;
case T_CreatedbStmt:
tag = "CREATE DATABASE";
+ *pESqlType = T_Stat_CREATE_DATABASE;
break;
case T_AlterDatabaseStmt:
tag = "ALTER DATABASE";
+ *pESqlType = T_Stat_ALTER_DATABASE;
break;
case T_AlterDatabaseSetStmt:
tag = "ALTER DATABASE";
+ *pESqlType = T_Stat_ALTER_DATABASE;
break;
case T_DropdbStmt:
tag = "DROP DATABASE";
+ *pESqlType = T_Stat_DROP_DATABASE;
break;
case T_NotifyStmt:
tag = "NOTIFY";
+ *pESqlType = T_Stat_NOTIFY;
break;
case T_ListenStmt:
tag = "LISTEN";
+ *pESqlType = T_Stat_LISTEN;
break;
case T_UnlistenStmt:
tag = "UNLISTEN";
+ *pESqlType = T_Stat_UNLISTEN;
break;
case T_LoadStmt:
tag = "LOAD";
+ *pESqlType = T_Stat_LOAD;
break;
case T_CallStmt:
tag = "CALL";
+ *pESqlType = T_Stat_CALL;
break;
case T_ClusterStmt:
tag = "CLUSTER";
+ *pESqlType = T_Stat_CLUSTER;
break;
case T_VacuumStmt:
if (((VacuumStmt *) parsetree)->is_vacuumcmd)
+ {
tag = "VACUUM";
+ *pESqlType = T_Stat_VACUUM;
+ }
else
+ {
tag = "ANALYZE";
+ *pESqlType = T_Stat_ANALYZE;
+ }
break;
case T_ExplainStmt:
tag = "EXPLAIN";
+ *pESqlType = T_Stat_EXPLAIN;
break;
case T_CreateTableAsStmt:
@@ -2607,24 +2961,34 @@ CreateCommandTag(Node *parsetree)
{
case OBJECT_TABLE:
if (((CreateTableAsStmt *) parsetree)->is_select_into)
+ {
tag = "SELECT INTO";
+ *pESqlType = T_Stat_SELECT_INTO;
+ }
else
+ {
tag = "CREATE TABLE AS";
+ *pESqlType = T_Stat_CREATE_TABLE_AS;
+ }
break;
case OBJECT_MATVIEW:
tag = "CREATE MATERIALIZED VIEW";
+ *pESqlType = T_Stat_CREATE_MATERIALIZED_VIEW;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
}
break;
case T_RefreshMatViewStmt:
tag = "REFRESH MATERIALIZED VIEW";
+ *pESqlType = T_Stat_REFRESH_MATERIALIZED_VIEW;
break;
case T_AlterSystemStmt:
tag = "ALTER SYSTEM";
+ *pESqlType = T_Stat_ALTER_SYSTEM;
break;
case T_VariableSetStmt:
@@ -2635,18 +2999,22 @@ CreateCommandTag(Node *parsetree)
case VAR_SET_DEFAULT:
case VAR_SET_MULTI:
tag = "SET";
+ *pESqlType = T_Stat_SET;
break;
case VAR_RESET:
case VAR_RESET_ALL:
tag = "RESET";
+ *pESqlType = T_Stat_RESET;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
}
break;
case T_VariableShowStmt:
tag = "SHOW";
+ *pESqlType = T_Stat_SHOW;
break;
case T_DiscardStmt:
@@ -2654,163 +3022,204 @@ CreateCommandTag(Node *parsetree)
{
case DISCARD_ALL:
tag = "DISCARD ALL";
+ *pESqlType = T_Stat_DISCARD_ALL;
break;
case DISCARD_PLANS:
tag = "DISCARD PLANS";
+ *pESqlType = T_Stat_DISCARD_PLANS;
break;
case DISCARD_TEMP:
tag = "DISCARD TEMP";
+ *pESqlType = T_Stat_DISCARD_TEMP;
break;
case DISCARD_SEQUENCES:
tag = "DISCARD SEQUENCES";
+ *pESqlType = T_Stat_DISCARD_SEQUENCES;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
}
break;
case T_CreateTransformStmt:
tag = "CREATE TRANSFORM";
+ *pESqlType = T_Stat_CREATE_TRANSFORM;
break;
case T_CreateTrigStmt:
tag = "CREATE TRIGGER";
+ *pESqlType = T_Stat_CREATE_TRIGGER;
break;
case T_CreateEventTrigStmt:
tag = "CREATE EVENT TRIGGER";
+ *pESqlType = T_Stat_CREATE_EVENT_TRIGGER;
break;
case T_AlterEventTrigStmt:
tag = "ALTER EVENT TRIGGER";
+ *pESqlType = T_Stat_ALTER_EVENT_TRIGGER;
break;
case T_CreatePLangStmt:
tag = "CREATE LANGUAGE";
+ *pESqlType = T_Stat_CREATE_LANGUAGE;
break;
case T_CreateRoleStmt:
tag = "CREATE ROLE";
+ *pESqlType = T_Stat_CREATE_ROLE;
break;
case T_AlterRoleStmt:
tag = "ALTER ROLE";
+ *pESqlType = T_Stat_ALTER_ROLE;
break;
case T_AlterRoleSetStmt:
tag = "ALTER ROLE";
+ *pESqlType = T_Stat_ALTER_ROLE;
break;
case T_DropRoleStmt:
tag = "DROP ROLE";
+ *pESqlType = T_Stat_DROP_ROLE;
break;
case T_DropOwnedStmt:
tag = "DROP OWNED";
+ *pESqlType = T_Stat_DROP_OWNED;
break;
case T_ReassignOwnedStmt:
tag = "REASSIGN OWNED";
+ *pESqlType = T_Stat_REASSIGN_OWNED;
break;
case T_LockStmt:
tag = "LOCK TABLE";
+ *pESqlType = T_Stat_LOCK_TABLE;
break;
case T_ConstraintsSetStmt:
tag = "SET CONSTRAINTS";
+ *pESqlType = T_Stat_SET_CONSTRAINTS;
break;
case T_CheckPointStmt:
tag = "CHECKPOINT";
+ *pESqlType = T_Stat_CHECKPOINT;
break;
case T_ReindexStmt:
tag = "REINDEX";
+ *pESqlType = T_Stat_REINDEX;
break;
case T_CreateConversionStmt:
tag = "CREATE CONVERSION";
+ *pESqlType = T_Stat_CREATE_CONVERSION;
break;
case T_CreateCastStmt:
tag = "CREATE CAST";
+ *pESqlType = T_Stat_CREATE_CAST;
break;
case T_CreateOpClassStmt:
tag = "CREATE OPERATOR CLASS";
+ *pESqlType = T_Stat_CREATE_OPERATOR_CLASS;
break;
case T_CreateOpFamilyStmt:
tag = "CREATE OPERATOR FAMILY";
+ *pESqlType = T_Stat_CREATE_OPERATOR_FAMILY;
break;
case T_AlterOpFamilyStmt:
tag = "ALTER OPERATOR FAMILY";
+ *pESqlType = T_Stat_ALTER_OPERATOR_FAMILY;
break;
case T_AlterOperatorStmt:
tag = "ALTER OPERATOR";
+ *pESqlType = T_Stat_ALTER_OPERATOR;
break;
case T_AlterTSDictionaryStmt:
tag = "ALTER TEXT SEARCH DICTIONARY";
+ *pESqlType = T_Stat_ALTER_TEXT_SEARCH_DICTIONARY;
break;
case T_AlterTSConfigurationStmt:
tag = "ALTER TEXT SEARCH CONFIGURATION";
+ *pESqlType = T_Stat_ALTER_TEXT_SEARCH_CONFIGURATION;
break;
case T_CreatePolicyStmt:
tag = "CREATE POLICY";
+ *pESqlType = T_Stat_CREATE_POLICY;
break;
case T_AlterPolicyStmt:
tag = "ALTER POLICY";
+ *pESqlType = T_Stat_ALTER_POLICY;
break;
case T_CreateAmStmt:
tag = "CREATE ACCESS METHOD";
+ *pESqlType = T_Stat_CREATE_ACCESS_METHOD;
break;
case T_CreatePublicationStmt:
tag = "CREATE PUBLICATION";
+ *pESqlType = T_Stat_CREATE_PUBLICATION;
break;
case T_AlterPublicationStmt:
tag = "ALTER PUBLICATION";
+ *pESqlType = T_Stat_ALTER_PUBLICATION;
break;
case T_CreateSubscriptionStmt:
tag = "CREATE SUBSCRIPTION";
+ *pESqlType = T_Stat_CREATE_SUBSCRIPTION;
break;
case T_AlterSubscriptionStmt:
tag = "ALTER SUBSCRIPTION";
+ *pESqlType = T_Stat_ALTER_SUBSCRIPTION;
break;
case T_DropSubscriptionStmt:
tag = "DROP SUBSCRIPTION";
+ *pESqlType = T_Stat_DROP_SUBSCRIPTION;
break;
case T_AlterCollationStmt:
tag = "ALTER COLLATION";
+ *pESqlType = T_Stat_ALTER_COLLATION;
break;
case T_PrepareStmt:
tag = "PREPARE";
+ *pESqlType = T_Stat_PREPARE;
break;
case T_ExecuteStmt:
tag = "EXECUTE";
+ *pESqlType = T_Stat_EXECUTE;
break;
case T_CreateStatsStmt:
tag = "CREATE STATISTICS";
+ *pESqlType = T_Stat_CREATE_STATISTICS;
break;
case T_AlterStatsStmt:
tag = "ALTER STATISTICS";
+ *pESqlType = T_Stat_ALTER_STATISTICS;
break;
case T_DeallocateStmt:
@@ -2818,9 +3227,15 @@ CreateCommandTag(Node *parsetree)
DeallocateStmt *stmt = (DeallocateStmt *) parsetree;
if (stmt->name == NULL)
+ {
tag = "DEALLOCATE ALL";
+ *pESqlType = T_Stat_DEALLOCATE_ALL;
+ }
else
+ {
tag = "DEALLOCATE";
+ *pESqlType = T_Stat_DEALLOCATE;
+ }
}
break;
@@ -2845,40 +3260,50 @@ CreateCommandTag(Node *parsetree)
{
case LCS_FORKEYSHARE:
tag = "SELECT FOR KEY SHARE";
+ *pESqlType = T_Stat_SELECT_FOR_KEY_SHARE;
break;
case LCS_FORSHARE:
tag = "SELECT FOR SHARE";
+ *pESqlType = T_Stat_SELECT_FOR_SHARE;
break;
case LCS_FORNOKEYUPDATE:
tag = "SELECT FOR NO KEY UPDATE";
+ *pESqlType = T_Stat_SELECT_FOR_NO_KEY_UPDATE;
break;
case LCS_FORUPDATE:
tag = "SELECT FOR UPDATE";
+ *pESqlType = T_Stat_SELECT_FOR_UPDATE;
break;
default:
tag = "SELECT";
+ *pESqlType = T_Stat_SELECT;
break;
}
}
else
tag = "SELECT";
+ *pESqlType = T_Stat_SELECT;
break;
case CMD_UPDATE:
tag = "UPDATE";
+ *pESqlType = T_Stat_UPDATE;
break;
case CMD_INSERT:
tag = "INSERT";
+ *pESqlType = T_Stat_INSERT;
break;
case CMD_DELETE:
tag = "DELETE";
+ *pESqlType = T_Stat_DELETE;
break;
case CMD_UTILITY:
- tag = CreateCommandTag(stmt->utilityStmt);
+ tag = CreateCommandTagType(stmt->utilityStmt, pESqlType);
break;
default:
elog(WARNING, "unrecognized commandType: %d",
(int) stmt->commandType);
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
break;
}
}
@@ -2905,40 +3330,50 @@ CreateCommandTag(Node *parsetree)
{
case LCS_FORKEYSHARE:
tag = "SELECT FOR KEY SHARE";
+ *pESqlType = T_Stat_SELECT_FOR_KEY_SHARE;
break;
case LCS_FORSHARE:
tag = "SELECT FOR SHARE";
+ *pESqlType = T_Stat_SELECT_FOR_SHARE;
break;
case LCS_FORNOKEYUPDATE:
tag = "SELECT FOR NO KEY UPDATE";
+ *pESqlType = T_Stat_SELECT_FOR_NO_KEY_UPDATE;
break;
case LCS_FORUPDATE:
tag = "SELECT FOR UPDATE";
+ *pESqlType = T_Stat_SELECT_FOR_UPDATE;
break;
default:
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
break;
}
}
else
tag = "SELECT";
+ *pESqlType = T_Stat_SELECT;
break;
case CMD_UPDATE:
tag = "UPDATE";
+ *pESqlType = T_Stat_UPDATE;
break;
case CMD_INSERT:
tag = "INSERT";
+ *pESqlType = T_Stat_INSERT;
break;
case CMD_DELETE:
tag = "DELETE";
+ *pESqlType = T_Stat_DELETE;
break;
case CMD_UTILITY:
- tag = CreateCommandTag(stmt->utilityStmt);
+ tag = CreateCommandTagType(stmt->utilityStmt, pESqlType);
break;
default:
elog(WARNING, "unrecognized commandType: %d",
(int) stmt->commandType);
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
break;
}
}
@@ -2948,6 +3383,7 @@ CreateCommandTag(Node *parsetree)
elog(WARNING, "unrecognized node type: %d",
(int) nodeTag(parsetree));
tag = "???";
+ *pESqlType = T_Stat_UNKNOWN;
break;
}
@@ -2956,6 +3392,45 @@ CreateCommandTag(Node *parsetree)
/*
+ * CreateCommandTag
+ * utility to get a string representation of the command operation,
+ * given either a raw (un-analyzed) parsetree, an analyzed Query,
+ * or a PlannedStmt.
+ *
+ * This must handle all command types, but since the vast majority
+ * of 'em are utility commands, it seems sensible to keep it here.
+ *
+ * NB: all result strings must be shorter than COMPLETION_TAG_BUFSIZE.
+ * Also, the result must point at a true constant (permanent storage).
+ */
+const char *
+CreateCommandTag(Node *parsetree)
+{
+ StatSqlType eType;
+ return CreateCommandTagType(parsetree, &eType);
+}
+
+
+/*
+ * CreateCommandType
+ * utility to get a enum representation of the command operation,
+ * given either a raw (un-analyzed) parsetree, an analyzed Query,
+ * or a PlannedStmt.
+ *
+ * This must handle all command types, but since the vast majority
+ * of 'em are utility commands, it seems sensible to keep it here.
+ *
+ */
+StatSqlType
+CreateCommandType(Node *parsetree)
+{
+ StatSqlType eType;
+ CreateCommandTagType(parsetree, &eType);
+ return eType;
+}
+
+
+/*
* GetCommandLogLevel
* utility to get the minimum log_statement level for a command,
* given either a raw (un-analyzed) parsetree, an analyzed Query,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 05240bf..7618de7 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1970,3 +1970,64 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(HeapTupleGetDatum(
heap_form_tuple(tupdesc, values, nulls)));
}
+
+Datum
+pg_stat_sql(PG_FUNCTION_ARGS)
+{
+ TupleDesc tupdesc;
+ Datum values[2] = { 0, 0 };
+ bool nulls[2] = { false, false };
+ ReturnSetInfo *rsi;
+ MemoryContext old_cxt;
+ Tuplestorestate *tuple_store;
+
+ /* Function call to let the backend read the stats file */
+ pgstat_fetch_global();
+
+ rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* Check to see if caller supports us returning a tuplestore */
+ if (rsi == NULL || !IsA(rsi, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsi->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ rsi->returnMode = SFRM_Materialize;
+
+ /* 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");
+
+ /* Build tuplestore to hold the result rows */
+ old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+ tuple_store =
+ tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ false, work_mem);
+ rsi->setDesc = tupdesc;
+ rsi->setResult = tuple_store;
+
+ MemoryContextSwitchTo(old_cxt);
+
+ for (int i = 0; i < PGSTAT_SQLSTMT_SIZE; i++)
+ {
+ HeapTuple tuple;
+
+ if (pgstat_sql_counts[i] == 0)
+ continue;
+
+ /* Fill values and NULLs */
+ values[0] = CStringGetTextDatum(g_str_sql_type[i]);
+ values[1] = Int64GetDatum(pgstat_sql_counts[i]);
+
+ tuple = heap_form_tuple(tupdesc, values, nulls);
+ tuplestore_puttuple(tuple_store, tuple);
+ }
+
+ PG_RETURN_NULL();
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 4b3769b..22154d1 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1413,7 +1413,15 @@ static struct config_bool ConfigureNamesBool[] =
false,
NULL, NULL, NULL
},
-
+ {
+ {"track_statement_statistics", PGC_SUSET, STATS_COLLECTOR,
+ gettext_noop("Collects the counters of SQL statments."),
+ NULL
+ },
+ &pgstat_track_statement_statistics,
+ false,
+ NULL, NULL, NULL
+ },
{
{"update_process_title", PGC_SUSET, PROCESS_TITLE,
gettext_noop("Updates the process title to show the active SQL command."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index be02a76..4d460c5 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -566,6 +566,7 @@
#track_activities = on
#track_counts = on
+#track_statement_statistics = off
#track_io_timing = off
#track_functions = none # none, pl, all
#track_activity_query_size = 1024 # (change requires restart)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 58ea5b9..a41d572 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5476,7 +5476,14 @@
proname => 'pg_stat_get_xact_function_self_time', provolatile => 'v',
proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
prosrc => 'pg_stat_get_xact_function_self_time' },
-
+{ oid => '4789',
+ descr => 'statistics: Show SQL statement statistics',
+ proname => 'pg_stat_sql', prorows => '1000', proretset => 't', provolatile => 'v',
+ proparallel => 'r', prorettype => 'record', proargtypes => '',
+ proallargtypes => '{text,int4}',
+ proargmodes => '{o,o}',
+ proargnames => '{tag,count}',
+ prosrc => 'pg_stat_sql' },
{ oid => '3788',
descr => 'statistics: timestamp of the current statistics snapshot',
proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index fe076d8..8cdbbd8 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -19,6 +19,7 @@
#include "storage/proc.h"
#include "utils/hsearch.h"
#include "utils/relcache.h"
+#include "tcop/utility.h"
/* ----------
@@ -119,7 +120,8 @@ typedef struct PgStat_TableCounts
typedef enum PgStat_Shared_Reset_Target
{
RESET_ARCHIVER,
- RESET_BGWRITER
+ RESET_BGWRITER,
+ RESET_SQLSTMT
} PgStat_Shared_Reset_Target;
/* Possible object types for resetting single counters */
@@ -581,7 +583,7 @@ typedef union PgStat_Msg
* ------------------------------------------------------------
*/
-#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9D
+#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9E
/* ----------
* PgStat_StatDBEntry The collector's data per database
@@ -1210,6 +1212,7 @@ typedef struct PgStat_FunctionCallUsage
*/
extern bool pgstat_track_activities;
extern bool pgstat_track_counts;
+extern bool pgstat_track_statement_statistics;
extern int pgstat_track_functions;
extern PGDLLIMPORT int pgstat_track_activity_query_size;
extern char *pgstat_stat_directory;
@@ -1222,6 +1225,11 @@ extern char *pgstat_stat_filename;
extern PgStat_MsgBgWriter BgWriterStats;
/*
+ * SQL statement statistics counter
+ */
+extern uint64* pgstat_sql_counts;
+
+/*
* Updated by pgstat_count_buffer_*_time macros
*/
extern PgStat_Counter pgStatBlockReadTime;
@@ -1299,6 +1307,9 @@ extern void pgstat_initstats(Relation rel);
extern char *pgstat_clip_activity(const char *raw_activity);
+extern Size pgstat_sql_shmem_size(void);
+extern void pgstat_sql_shmem_init(void);
+
/* ----------
* pgstat_report_wait_start() -
*
@@ -1420,6 +1431,7 @@ extern void pgstat_twophase_postabort(TransactionId xid, uint16 info,
extern void pgstat_send_archiver(const char *xlog, bool failed);
extern void pgstat_send_bgwriter(void);
+extern void pgstat_count_sqlstmt(StatSqlType sqlType);
/* ----------
* Support functions for the SQL-callable functions to
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 5abcacf..9a9e867 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -25,6 +25,203 @@ typedef enum
PROCESS_UTILITY_SUBCOMMAND /* a portion of a query */
} ProcessUtilityContext;
+/* ----------
+ * The sql statement types for pg_stat_sql
+ * ----------
+ */
+typedef enum StatSqlType
+{
+ T_Stat_INSERT,
+ T_Stat_DELETE,
+ T_Stat_UPDATE,
+ T_Stat_SELECT,
+ T_Stat_BEGIN,
+ T_Stat_START_TRANSACTION,
+ T_Stat_COMMIT,
+ T_Stat_ROLLBACK,
+ T_Stat_SAVEPOINT,
+ T_Stat_RELEASE,
+ T_Stat_PREPARE_TRANSACTION,
+ T_Stat_COMMIT_PREPARED,
+ T_Stat_ROLLBACK_PREPARED,
+ T_Stat_DECLARE_CURSOR,
+ T_Stat_CLOSE_CURSOR_ALL,
+ T_Stat_CLOSE_CURSOR,
+ T_Stat_MOVE,
+ T_Stat_FETCH,
+ T_Stat_CREATE_DOMAIN,
+ T_Stat_CREATE_SCHEMA,
+ T_Stat_CREATE_TABLE,
+ T_Stat_CREATE_TABLESPACE,
+ T_Stat_DROP_TABLESPACE,
+ T_Stat_ALTER_TABLESPACE,
+ T_Stat_CREATE_EXTENSION,
+ T_Stat_ALTER_EXTENSION,
+ T_Stat_CREATE_FOREIGN_DATA_WRAPPER,
+ T_Stat_ALTER_FOREIGN_DATA_WRAPPER,
+ T_Stat_CREATE_SERVER,
+ T_Stat_ALTER_SERVER,
+ T_Stat_CREATE_USER_MAPPING,
+ T_Stat_ALTER_USER_MAPPING,
+ T_Stat_DROP_USER_MAPPING,
+ T_Stat_CREATE_FOREIGN_TABLE,
+ T_Stat_IMPORT_FOREIGN_SCHEMA,
+ T_Stat_DROP_TABLE,
+ T_Stat_DROP_SEQUENCE,
+ T_Stat_DROP_VIEW,
+ T_Stat_DROP_MATERIALIZED_VIEW,
+ T_Stat_DROP_INDEX,
+ T_Stat_DROP_TYPE,
+ T_Stat_DROP_DOMAIN,
+ T_Stat_DROP_COLLATION,
+ T_Stat_DROP_CONVERSION,
+ T_Stat_DROP_SCHEMA,
+ T_Stat_DROP_TEXT_SEARCH_PARSER,
+ T_Stat_DROP_TEXT_SEARCH_DICTIONARY,
+ T_Stat_DROP_TEXT_SEARCH_TEMPLATE,
+ T_Stat_DROP_DROP_TEXT_SEARCH_CONFIGURATION,
+ T_Stat_DROP_DROP_FOREIGN_TABLE,
+ T_Stat_DROP_EXTENSION,
+ T_Stat_DROP_FUNCTION,
+ T_Stat_DROP_PROCEDURE,
+ T_Stat_DROP_ROUTINE,
+ T_Stat_DROP_AGGREGATE,
+ T_Stat_DROP_OPERATOR,
+ T_Stat_DROP_LANGUAGE,
+ T_Stat_DROP_CAST,
+ T_Stat_DROP_TRIGGER,
+ T_Stat_DROP_EVENT_TRIGGER,
+ T_Stat_DROP_RULE,
+ T_Stat_DROP_FOREIGN_DATA_WRAPPER,
+ T_Stat_DROP_SERVER,
+ T_Stat_DROP_OPERATOR_CLASS,
+ T_Stat_DROP_OPERATOR_FAMILY,
+ T_Stat_DROP_POLICY,
+ T_Stat_DROP_TRANSFORM,
+ T_Stat_DROP_ACCESS_METHOD,
+ T_Stat_DROP_PUBLICATION,
+ T_Stat_DROP_STATISTICS,
+ T_Stat_TRUNCATE_TABLE,
+ T_Stat_COMMENT,
+ T_Stat_SECURITY_LABEL,
+ T_Stat_COPY,
+ T_Stat_ALTER_AGGREGATE,
+ T_Stat_ALTER_TYPE,
+ T_Stat_ALTER_CAST,
+ T_Stat_ALTER_COLLATION,
+ T_Stat_ALTER_TABLE,
+ T_Stat_ALTER_CONVERSION,
+ T_Stat_ALTER_DATABASE,
+ T_Stat_ALTER_DOMAIN,
+ T_Stat_ALTER_FOREIGN_TABLE,
+ T_Stat_ALTER_FUNCTION,
+ T_Stat_ALTER_INDEX,
+ T_Stat_ALTER_LANGUAGE,
+ T_Stat_ALTER_LARGE_OBJECT,
+ T_Stat_ALTER_OPERATOR_CLASS,
+ T_Stat_ALTER_OPERATOR,
+ T_Stat_ALTER_OPERATOR_FAMILY,
+ T_Stat_ALTER_POLICY,
+ T_Stat_ALTER_PROCEDURE,
+ T_Stat_ALTER_ROLE,
+ T_Stat_ALTER_ROUTINE,
+ T_Stat_ALTER_RULE,
+ T_Stat_ALTER_SCHEMA,
+ T_Stat_ALTER_SEQUENCE,
+ T_Stat_ALTER_TRIGGER,
+ T_Stat_ALTER_EVENT_TRIGGER,
+ T_Stat_ALTER_TEXT_SEARCH_CONFIGURATION,
+ T_Stat_ALTER_TEXT_SEARCH_DICTIONARY,
+ T_Stat_ALTER_TEXT_SEARCH_PARSER,
+ T_Stat_ALTER_TEXT_SEARCH_TEMPLATE,
+ T_Stat_ALTER_VIEW,
+ T_Stat_ALTER_MATERIALIZED_VIEW,
+ T_Stat_ALTER_PUBLICATION,
+ T_Stat_ALTER_SUBSCRIPTION,
+ T_Stat_ALTER_STATISTICS,
+ T_Stat_GRANT,
+ T_Stat_REVOKE,
+ T_Stat_GRANT_ROLE,
+ T_Stat_REVOKE_ROLE,
+ T_Stat_ALTER_DEFAULT_PRIVILEGES,
+ T_Stat_CREATE_AGGREGATE,
+ T_Stat_CREATE_OPERATOR,
+ T_Stat_CREATE_TYPE,
+ T_Stat_CREATE_TEXT_SEARCH_PARSER,
+ T_Stat_CREATE_TEXT_SEARCH_DICTIONARY,
+ T_Stat_CREATE_TEXT_SEARCH_TEMPLATE,
+ T_Stat_CREATE_TEXT_SEARCH_CONFIGURATION,
+ T_Stat_CREATE_COLLATION,
+ T_Stat_CREATE_ACCESS_METHOD,
+ T_Stat_CREATE_VIEW,
+ T_Stat_CREATE_PROCEDURE,
+ T_Stat_CREATE_FUNCTION,
+ T_Stat_CREATE_INDEX,
+ T_Stat_CREATE_RULE,
+ T_Stat_CREATE_SEQUENCE,
+ T_Stat_DO,
+ T_Stat_CREATE_DATABASE,
+ T_Stat_DROP_DATABASE,
+ T_Stat_NOTIFY,
+ T_Stat_LISTEN,
+ T_Stat_UNLISTEN,
+ T_Stat_LOAD,
+ T_Stat_CALL,
+ T_Stat_CLUSTER,
+ T_Stat_VACUUM,
+ T_Stat_ANALYZE,
+ T_Stat_EXPLAIN,
+ T_Stat_SELECT_INTO,
+ T_Stat_CREATE_TABLE_AS,
+ T_Stat_CREATE_MATERIALIZED_VIEW,
+ T_Stat_REFRESH_MATERIALIZED_VIEW,
+ T_Stat_ALTER_SYSTEM,
+ T_Stat_SET,
+ T_Stat_RESET,
+ T_Stat_SHOW,
+ T_Stat_DISCARD_ALL,
+ T_Stat_DISCARD_PLANS,
+ T_Stat_DISCARD_TEMP,
+ T_Stat_DISCARD_SEQUENCES,
+ T_Stat_CREATE_TRANSFORM,
+ T_Stat_CREATE_TRIGGER,
+ T_Stat_CREATE_EVENT_TRIGGER,
+ T_Stat_CREATE_LANGUAGE,
+ T_Stat_CREATE_ROLE,
+ T_Stat_DROP_ROLE,
+ T_Stat_DROP_OWNED,
+ T_Stat_REASSIGN_OWNED,
+ T_Stat_LOCK_TABLE,
+ T_Stat_SET_CONSTRAINTS,
+ T_Stat_CHECKPOINT,
+ T_Stat_REINDEX,
+ T_Stat_CREATE_CONVERSION,
+ T_Stat_CREATE_CAST,
+ T_Stat_CREATE_OPERATOR_CLASS,
+ T_Stat_CREATE_OPERATOR_FAMILY,
+ T_Stat_CREATE_POLICY,
+ T_Stat_CREATE_PUBLICATION,
+ T_Stat_CREATE_SUBSCRIPTION,
+ T_Stat_DROP_SUBSCRIPTION,
+ T_Stat_PREPARE,
+ T_Stat_EXECUTE,
+ T_Stat_CREATE_STATISTICS,
+ T_Stat_DEALLOCATE_ALL,
+ T_Stat_DEALLOCATE,
+ T_Stat_SELECT_FOR_KEY_SHARE,
+ T_Stat_SELECT_FOR_SHARE,
+ T_Stat_SELECT_FOR_NO_KEY_UPDATE,
+ T_Stat_SELECT_FOR_UPDATE,
+
+ T_Stat_UNKNOWN
+
+} StatSqlType;
+
+#define PGSTAT_SQLSTMT_SIZE (T_Stat_UNKNOWN + 1)
+
+extern const char* g_str_sql_type[];
+
+
/* Hook for plugins to get control in ProcessUtility() */
typedef void (*ProcessUtility_hook_type) (PlannedStmt *pstmt,
const char *queryString, ProcessUtilityContext context,
@@ -50,6 +247,10 @@ extern Query *UtilityContainsQuery(Node *parsetree);
extern const char *CreateCommandTag(Node *parsetree);
+extern StatSqlType CreateCommandType(Node *parsetree);
+
+extern const char* CreateCommandTagType(Node *parsetree, StatSqlType* pESqlType);
+
extern LogStmtLevel GetCommandLogLevel(Node *parsetree);
extern bool CommandIsReadOnly(PlannedStmt *pstmt);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 14e7214..a9dd290 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1956,6 +1956,9 @@ pg_stat_replication| SELECT s.pid,
FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc)
JOIN pg_stat_get_wal_senders() w(pid, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, write_lag, flush_lag, replay_lag, sync_priority, sync_state, reply_time) ON ((s.pid = w.pid)))
LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
+pg_stat_sql| SELECT pg_stat_sql.tag,
+ pg_stat_sql.count
+ FROM pg_stat_sql() pg_stat_sql(tag, count);
pg_stat_ssl| SELECT s.pid,
s.ssl,
s.sslversion AS version,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index b01e58b..50846c7 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -4,6 +4,70 @@
-- Must be run after tenk2 has been created (by create_table),
-- populated (by create_misc) and indexed (by create_index).
--
+-- pg_stat_sql
+SET track_statement_statistics TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+-----+-------
+(0 rows)
+
+SET track_statement_statistics TO ON;
+CREATE TABLE t1 (c1 int);
+INSERT INTO t1 VALUES(1);
+INSERT INTO t1 VALUES(1);
+INSERT INTO t1 VALUES(1);
+DELETE FROM t1;
+SELECT * FROM pg_stat_sql;
+ tag | count
+--------------+-------
+ INSERT | 3
+ DELETE | 1
+ CREATE TABLE | 1
+ SET | 1
+(4 rows)
+
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+--------+-------
+ SELECT | 2
+(1 row)
+
+SET track_statement_statistics TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+SELECT pg_sleep(1.0);
+ pg_sleep
+----------
+
+(1 row)
+
+SELECT * FROM pg_stat_sql where tag='SELECT';
+ tag | count
+-----+-------
+(0 rows)
+
-- conditio sine qua non
SHOW track_counts; -- must be on
track_counts
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index feaaee6..48e88d3 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -4,6 +4,30 @@
-- Must be run after tenk2 has been created (by create_table),
-- populated (by create_misc) and indexed (by create_index).
--
+-- pg_stat_sql
+SET track_statement_statistics TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
+SET track_statement_statistics TO ON;
+
+CREATE TABLE t1 (c1 int);
+INSERT INTO t1 VALUES(1);
+INSERT INTO t1 VALUES(1);
+INSERT INTO t1 VALUES(1);
+DELETE FROM t1;
+SELECT * FROM pg_stat_sql;
+
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
+
+SET track_statement_statistics TO OFF;
+SELECT pg_stat_reset_shared('sqlstmt'); -- reset the counters
+
+SELECT pg_sleep(1.0);
+SELECT * FROM pg_stat_sql where tag='SELECT';
-- conditio sine qua non
SHOW track_counts; -- must be on
--
1.8.3.1
On 2019-Nov-13, Smith, Peter wrote:
From: Thomas Munro <thomas.munro@gmail.com> Sent: Monday, 4 November 2019 1:43 PM
No comment on the patch but I noticed that the documentation changes don't build. Please make sure you can "make docs" successfully, having installed the documentation tools[1].
Thanks for the feedback. An updated patch which fixes the docs issue is attached.
So, who is updating this patch for the new cmdtaglist.h stuff?
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Mar 3, 2020, at 6:50 PM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
On 2019-Nov-13, Smith, Peter wrote:
From: Thomas Munro <thomas.munro@gmail.com> Sent: Monday, 4 November 2019 1:43 PM
No comment on the patch but I noticed that the documentation changes don't build. Please make sure you can "make docs" successfully, having installed the documentation tools[1].
Thanks for the feedback. An updated patch which fixes the docs issue is attached.
So, who is updating this patch for the new cmdtaglist.h stuff?
I have a much altered (competing?) patch inspired by pg_stat_sql. I am working to integrate the changes you made to my commandtag patch into my commandstats patch before posting it.
It might be good if somebody else tackled the pg_stat_sql patch in case my version is rejected.
I don't want to highjack this thread to talk in detail about the other patch, so I'll just give an overview that might be useful for anybody thinking about putting effort into committing this patch first.
The motivation for the commandtag patch that you've committed (thanks again!) was to make the commandstats patch possible. I didn't like the way pg_stat_sql stored the tags as strings, but I also still don't like the way it needs to take a lock every time a backend increments the counter for a command. That is bad, I think, because the lock overhead for an otherwise fast command (like moving a cursor) may be high enough to discourage users from turning this feature on in production.
I solve that by trading off speed and memory. For most commandtags, there is a single uint64 counter, and incrementing the counter requires taking a lock. This is very similar to how the other patch works. For the few commandtags that we deem worthy of the special treatment, there is an additional uint32 counter *per backend* reserved in shared memory. Backends update this counter using atomics, but do not require locks. When the counter is in danger of overflow, locks are taken to roll the count into the uint64 counters and zero out the backend-specific uint32 counter. I have roughly 20 command tags receiving this special treatment, but the design of the patch doesn't make it too hard to change the exact list of tags worthy of it, so I don't plan to stress too much about the exact set of tags. I'm sure you'll all tell me which ones you think do or do not deserve the treatment.
If it isn't obvious, the logic of having this treatment for only a subset of tags is that 192 tags * 4 bytes per counter * MaxBackends gets expensive for folks running a high number of backends. Taking that from 192 tags down to one or two dozen seems worthwhile.
I expect to post on a separate thread before the day is over.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Hackers,
as mentioned in [1]/messages/by-id/28F46159-3764-421B-904B-004DEA339310@enterprisedb.com, I have created an implementation of command counter statistics very similar in purpose to the one already pending in the commitfest going by the name "pg_stat_sql". I don't really care if this implementation is seen as building on that one or as separate, but I was worried about hijacking that thread, so I'm starting this thead. There are elements of this patch that borrowed from that one, so if this is accepted, authorship should reflect that.
See the work by Haribabu Kommi (haribabu) at https://commitfest.postgresql.org/27/790/
The two main differences are that
(1) This implementation is based on commandtags as enums, not strings and
(2) This implementation uses techniques to reduce lock contention
I think (2) is the more important part.
[1]: /messages/by-id/28F46159-3764-421B-904B-004DEA339310@enterprisedb.com
Attachments:
v1-0001-Implementing-the-cmdstats-subsystem.patchapplication/octet-stream; name=v1-0001-Implementing-the-cmdstats-subsystem.patch; x-unix-mode=0644Download
From ba2f2f3dae82189ef455547a569bfa5747d30b0b Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Wed, 4 Mar 2020 14:39:46 -0800
Subject: [PATCH v1] Implementing the cmdstats subsystem.
This implements a subsystem for tracking the number of times a type of command
has been run in a database cluster, since startup or since the last time the
counts were reset, whichever is newer.
This work is based on and borrows from work by Haribabu Kommi (haribabu) as
reflected in the patch submission:
SQL statements statistics counter view (pg_stat_sql)
which itself was reviewed by dilip kumar (dilip.kumar), Vinayak Pokale (vinpokale)
and probably others.
---
src/backend/catalog/system_views.sql | 7 +
src/backend/postmaster/Makefile | 1 +
src/backend/postmaster/cmdstats.c | 447 +++++++++++++++++++++++
src/backend/postmaster/pgstat.c | 7 +
src/backend/storage/ipc/ipci.c | 3 +
src/backend/storage/lmgr/lwlocknames.txt | 1 +
src/backend/tcop/cmdtag.c | 5 +-
src/backend/tcop/dest.c | 4 +
src/backend/utils/adt/pgstatfuncs.c | 62 ++++
src/backend/utils/misc/guc.c | 13 +
src/include/catalog/pg_proc.dat | 6 +
src/include/postmaster/cmdstats.h | 68 ++++
src/include/tcop/cmdtag.h | 6 +-
src/include/tcop/cmdtaglist.h | 391 ++++++++++----------
src/test/Makefile | 2 +-
src/test/cmdstats/Makefile | 25 ++
src/test/cmdstats/README | 23 ++
src/test/cmdstats/t/001_guc_settings.pl | 31 ++
src/test/cmdstats/t/002_stats.pl | 53 +++
src/test/regress/expected/rules.out | 3 +
src/test/regress/expected/stats.out | 21 ++
src/test/regress/sql/stats.sql | 10 +
src/tools/pgindent/typedefs.list | 1 +
23 files changed, 993 insertions(+), 197 deletions(-)
create mode 100644 src/backend/postmaster/cmdstats.c
create mode 100644 src/include/postmaster/cmdstats.h
create mode 100644 src/test/cmdstats/Makefile
create mode 100644 src/test/cmdstats/README
create mode 100644 src/test/cmdstats/t/001_guc_settings.pl
create mode 100644 src/test/cmdstats/t/002_stats.pl
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46912..b27f6abbf9 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -290,6 +290,13 @@ CREATE VIEW pg_stats_ext WITH (security_barrier) AS
WHERE NOT has_column_privilege(c.oid, a.attnum, 'select') )
AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+CREATE VIEW pg_command_stats AS
+ SELECT s.tag, s.cnt
+ FROM pg_command_stats_data() AS s(tag, cnt);
+
+REVOKE EXECUTE ON FUNCTION pg_command_stats_data() FROM PUBLIC;
+REVOKE ALL ON pg_command_stats FROM PUBLIC;
+
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL on pg_statistic_ext_data FROM public;
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index bfdf6a833d..d23580a457 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -17,6 +17,7 @@ OBJS = \
bgworker.o \
bgwriter.o \
checkpointer.o \
+ cmdstats.o \
fork_process.o \
interrupt.o \
pgarch.o \
diff --git a/src/backend/postmaster/cmdstats.c b/src/backend/postmaster/cmdstats.c
new file mode 100644
index 0000000000..a16d1e9d0c
--- /dev/null
+++ b/src/backend/postmaster/cmdstats.c
@@ -0,0 +1,447 @@
+/* ----------
+ * cmdstats.c
+ *
+ * Copyright (c) 2001-2020, PostgreSQL Global Development Group
+ *
+ * src/backend/postmaster/cmdstats.c
+ * ----------
+ */
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "commands/dbcommands.h"
+#include "miscadmin.h"
+#include "port/atomics.h"
+#include "postmaster/cmdstats.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "utils/acl.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
+
+
+/*
+ * Create a mapping from a CommandTag to its offset in the per-backend stats
+ * (if any), else defaulting to -1. There will be an entry in this array for
+ * every CommandTag.
+ */
+typedef struct LocalStatsOffset {
+ int offset;
+} LocalStatsOffset;
+
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, localstats) \
+ [tag] = { .offset = localstats },
+
+const LocalStatsOffset local_stats_offset[] = {
+#include "tcop/cmdtaglist.h"
+ [COMMAND_TAG_NEXTTAG] = { .offset = -1 }
+};
+
+#undef PG_CMDTAG
+
+static void
+merge_local_accum(CmdStats *pg_restrict stats, const LocalStatsAccum *pg_restrict accum)
+{
+ CommandTag tag;
+ uint64 *dst = (uint64 *) stats->cnt;
+ const uint64 *src = (const uint64 *) accum->cnt;
+
+ for (tag = FIRST_CMDTAG; tag <= LAST_CMDTAG; tag++)
+ {
+ int offset = local_stats_offset[tag].offset;
+ if (offset >= 0)
+ dst[tag] += src[offset];
+ }
+}
+
+static void
+rollup_local_stats(CmdStats *pg_restrict stats, LocalCmdStats *pg_restrict localstats)
+{
+ CommandTag tag;
+ uint64 *dst = (uint64 *) stats->cnt;
+ pg_atomic_uint32 *src = (pg_atomic_uint32 *) localstats->cnt;
+
+ LWLockAcquire(CmdStatsLock, LW_EXCLUSIVE);
+ /*
+ * We need to read the src[] array element and zero it in the same atomic
+ * operation. We hold an exclusive lock, but backends who merely update the
+ * value of their own count don't try to take the lock, so we get no protection
+ * against concurrent updates by them.
+ */
+ for (tag = FIRST_CMDTAG; tag <= LAST_CMDTAG; tag++)
+ {
+ int offset = local_stats_offset[tag].offset;
+ if (offset >= 0)
+ dst[tag] += pg_atomic_exchange_u32(&(src[offset]), 0);
+ }
+ LWLockRelease(CmdStatsLock);
+}
+
+/* ----------
+ * GUC parameters
+ * ----------
+ */
+bool cmdstats_tracking = false;
+
+/*
+ * This structure holds counters in shared memory tracking how many commands of
+ * a given type have been run.
+ *
+ * CmdStatsShmem->cmdstats
+ * -------------------------------
+ * A single shared array of uint64 counts reserved for counting per commandtag
+ * how many operations of that commandtag have been executed to completion.
+ * Reading and writing these counts requires lwlocks. The size of this array
+ * is proportional to NUM_CMDSTATS_COUNTS. These counts may underrepresent the
+ * true totals if the backends are keeping local per-backend counts.
+ *
+ * CmdStatsShmem->localcmdstats
+ * ------------------------------------
+ * For the subset of commands that may be quick and be run in rapid succession,
+ * to avoid adding the locking overhead to each run of the command, separate
+ * counters are kept per backend. These local per-backend counters can be
+ * updated by the owning backend through atomics without the use of locking.
+ * This requires shared memory that scales proportional to MaxBackends *
+ * NUM_LOCAL_STATS. Owing to the impact on the amount of shared memory
+ * required, this treatment is reserved only to the fastest commands which
+ * might otherwise suffer a measurable performance hit.
+ *
+ * LOCKING AND ATOMIC READS
+ * ------------------------
+ *
+ * Both the shared array of counts and the local per-backend counts are stored
+ * in shared memory. The "local per-backend" counts are equally visible to all
+ * backends. By convention, backends may read and write their own per-backend
+ * counts, but may only read counts from other backends. Note that this
+ * restriction holds even for backends holding the lwlock in exclusive mode.
+ * Even then, the backend holding the lock must not modify the counts of any
+ * other backend.
+ *
+ * Atomic reads and writes are supported on all architectures for uint32, but
+ * only on a subset of architectures for uint64. To make the implementation
+ * the same on all architectures, the backends track their counts as uint32
+ * variables. It may be tempting to upgrade these counts to uint64 if atomic
+ * support were universal, but that would double the amount of shared memory
+ * for command statistics used per backend process. It may also be tempting to
+ * reduce to a uint16 in order to reduce the per backend shared memory usage,
+ * but there is no atomics support for uint16.
+ *
+ * At any moment, the true clusterwide count per command is the sum of the
+ * shared counter for that command and all backend local counters for that
+ * command, if any. Since uint32 is small enough to overflow, we keep a shared
+ * uint64 counter per command tag for all command tags, including those which
+ * get special per-backend counters. This implementation assumes uint64 counts
+ * will not overflow during the lifetime of a running cluster.
+ *
+ * Whenever a backend's uint32 counts are about to overflow, the backend must
+ * acquire the shared CmdStatsLock exclusively, merge its own counts into the
+ * shared counts, and then reset its own counts to zero. Other backends which
+ * are simultaneously updating their own uint32 counts are not blocked by this,
+ * but if another backend were to simultaneously need to merge counts into the
+ * shared counts, it would have to wait for the lock. Since merging counts
+ * only happens once every 2^32 commands per backend, backends are not expected
+ * to block each other in this way sufficiently often to be of concern.
+ *
+ * Backends totaling up the counts to return to a caller as part of executing
+ * one of the command stats system views must acquire the CmdStatsLock and hold
+ * it while tallying the shared counts and all the backend counts together.
+ * Each concurrently running backend may update its counts while this lock is
+ * held, but no backend may merge its counts into the shared counts nor reset
+ * its counts to zero. Since the command stats system views make no
+ * transaction isolation level guarantees about the stability of the counts,
+ * this seems sufficient, so long as the atomicity of changes to the counts is
+ * carefully handled.
+ */
+typedef struct
+{
+ CmdStats *cmdstats; /* Array of cluster totals */
+ LocalCmdStats *localcmdstats; /* Array of backend totals */
+}
+#if defined(pg_attribute_aligned)
+ pg_attribute_aligned(MAXIMUM_ALIGNOF)
+#endif
+CmdStatsShmemStruct;
+
+static CmdStatsShmemStruct *CmdStatsShmem;
+
+static Size
+command_stats_struct_size(void)
+{
+ return MAXALIGN(sizeof(CmdStatsShmemStruct));
+}
+
+static Size
+shared_cmdstats_size(void)
+{
+ return MAXALIGN(sizeof(CmdStats));
+}
+
+static Size
+local_cmdstats_size(void)
+{
+ return mul_size(MAXALIGN(sizeof(LocalCmdStats)), MaxBackends);
+}
+
+/*
+ * CmdStatsShmemSize
+ * Compute space needed for cmdstats-related shared memory
+ */
+Size
+CmdStatsShmemSize(void)
+{
+ Size size;
+
+ if (!cmdstats_tracking)
+ return 0;
+ size = command_stats_struct_size(); /* The struct itself */
+ size = add_size(size, shared_cmdstats_size()); /* cmdstats */
+ size = add_size(size, local_cmdstats_size()); /* localcmdstats */
+ return size;
+}
+
+/*
+ * CmdStatsShmemInit
+ * Allocate and initialize cmdstats-related shared memory
+ */
+void
+CmdStatsShmemInit(void)
+{
+ bool found;
+ Size shmemsize = CmdStatsShmemSize();
+
+ if (shmemsize == 0)
+ {
+ CmdStatsShmem = NULL;
+ return;
+ }
+
+ CmdStatsShmem = (CmdStatsShmemStruct *)
+ ShmemInitStruct("CmdStats Data", shmemsize, &found);
+
+ if (!IsUnderPostmaster)
+ {
+ char *next; /* really a void pointer, but char for portability */
+ int bkend, idx;
+
+ /*
+ * We should only be called at postmaster startup, before any
+ * backends are looking at the CmdStatsShmem, so acquiring
+ * the lock is probably not required. There doesn't seem to be
+ * any reason to skip the lock, though, since we only have to do
+ * this once.
+ */
+ LWLockAcquire(CmdStatsLock, LW_EXCLUSIVE);
+
+ /*
+ * Each pointer field in the struct points to memory contiguous with
+ * the struct itself. Each such field is MAXALIGNED, but other than
+ * the padding that might introduce, the fields are in-order and back
+ * to back.
+ */
+ memset(CmdStatsShmem, 0, shmemsize);
+ next = (char*)CmdStatsShmem + command_stats_struct_size();
+ CmdStatsShmem->cmdstats = (CmdStats *)next;
+ next = next + shared_cmdstats_size();
+ CmdStatsShmem->localcmdstats = (LocalCmdStats *)next;
+ next = next + local_cmdstats_size();
+
+ /* Initialize all the atomic variables before anybody can use them */
+ for (bkend = 0; bkend < MaxBackends; bkend++)
+ for (idx = 0; idx < NUM_LOCAL_STATS; idx++)
+ pg_atomic_init_u32(
+ &(CmdStatsShmem->localcmdstats[bkend].cnt[idx]), 0);
+
+ /* It is now safe for other backends to get the lock */
+ LWLockRelease(CmdStatsLock);
+
+ /*
+ * For sanity, assert that we used precisely the amount of memory that
+ * we allocated. There wouldn't be much harm in allocating a bit more
+ * than necessary, but we did not do so intentionally, so it would be
+ * indicative of a memory size calculation error if things don't match.
+ */
+ Assert(next == (char *)CmdStatsShmem + shmemsize);
+
+ elog(DEBUG1, "cmdstats finished initializing shared memory of size "
+ "%llu ", (long long unsigned int) shmemsize);
+ }
+ else
+ Assert(found);
+}
+
+CmdStats *
+cmdstats_shared_tally(void)
+{
+ int backend,
+ localidx,
+ tagidx;
+ LocalCmdStats *local_stats;
+ pg_atomic_uint32 *bkend_local_array;
+ const CmdStats *shared_stats;
+ const uint64 *shared_array;
+ CmdStats *result_stats;
+ uint64 *result_array;
+ LocalStatsAccum local_accum;
+ uint64 *accum_array;
+
+
+ Assert(cmdstats_tracking);
+ Assert(CmdStatsShmem);
+
+ shared_stats = (const CmdStats *) CmdStatsShmem->cmdstats;
+ shared_array = (const uint64 *) shared_stats->cnt;
+ local_stats = (LocalCmdStats *) CmdStatsShmem->localcmdstats;
+
+ result_stats = (CmdStats *) palloc0(sizeof(CmdStats));
+ result_array = (uint64 *) result_stats->cnt;
+
+ memset(&local_accum, 0, sizeof(local_accum));
+ accum_array = (uint64*) &(local_accum.cnt[0]);
+
+ LWLockAcquire(CmdStatsLock, LW_SHARED);
+ for (backend = 0; backend < MaxBackends; backend++)
+ {
+ bkend_local_array = local_stats[backend].cnt;
+ for (localidx = 0; localidx < NUM_LOCAL_STATS; localidx++)
+ accum_array[localidx] += pg_atomic_read_u32(&(bkend_local_array[localidx]));
+ }
+ for (tagidx = 0; tagidx < NUM_CMDSTATS_COUNTS; tagidx++)
+ result_array[tagidx] += shared_array[tagidx];
+ merge_local_accum(result_stats, &local_accum);
+ LWLockRelease(CmdStatsLock);
+
+ return result_stats;
+}
+
+void
+cmdstats_rollup_localstats(LocalCmdStats *localstats)
+{
+ Assert(cmdstats_tracking);
+ Assert(CmdStatsShmem);
+
+ rollup_local_stats(CmdStatsShmem->cmdstats, localstats);
+}
+
+void
+cmdstats_increment(CommandTag commandtag)
+{
+ int local_stats_idx;
+
+ /* Should not get here unless cmdstats_tracking is true */
+ Assert(cmdstats_tracking);
+ Assert(CmdStatsShmem);
+ Assert(commandtag >= FIRST_CMDTAG && commandtag < LAST_CMDTAG);
+
+ /*
+ * If the commandtag has per-backend counters, we'll update the per-backend
+ * counter belonging to this backend, otherwise we'll update the shared
+ * counter used by all backends. Note that updating the shared counter
+ * when we could have updated the per-backend counter is not a correctness
+ * problem, only a performance problem. The per-backend counters
+ * eventually get rolled into the shared counters anyway.
+ *
+ * There is no good reason why a postmaster should ever call this function,
+ * so we complain loudly if it does. Having the postmaster update a
+ * per-backend counter would be inappropriate, but if all shared counters
+ * have been properly initialized, the postmaster could in theory safely
+ * increment the shared counter for the commandtag. But if the postmaster
+ * is calling this function it suggests that something has gone awry, and
+ * we can't really trust that the shared memory has been properly
+ * initialized yet, so a hard error seems more appropriate.
+ */
+ local_stats_idx = local_stats_offset[commandtag].offset;
+ if (local_stats_idx < 0)
+ {
+ /*
+ * There are no per-backend counters for this commandtag, so we must
+ * hold an exclusive lock while incrementing the shared counter.
+ * We don't expect this to be performed by the postmaster, but we don't
+ * really care, which is why we're not checking IsUnderPostmaster here.
+ */
+ LWLockAcquire(CmdStatsLock, LW_EXCLUSIVE);
+ CmdStatsShmem->cmdstats->cnt[commandtag]++;
+ LWLockRelease(CmdStatsLock);
+ }
+ else if (IsUnderPostmaster)
+ {
+ /*
+ * There are per-backend counters for this commandtag, so we must
+ * actually be a backend to have one assigned to us (hence the
+ * IsUnderPostmaster check), and we have exclusive ownership of the
+ * counter so we don't need to take the lock. But since we might
+ * increment our counter while others are simultaneously reading our
+ * counter, we must use atomic operations.
+ */
+ int bkend;
+ pg_atomic_uint32 *cnt;
+
+ /* Assert that we're called by a valid backend */
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ /* Assert that our argument is within bounds */
+ Assert(local_stats_idx < NUM_LOCAL_STATS);
+
+ /*
+ * MyBackendId runs from [1..MaxBackends] inclusive, but our
+ * indexes only operate from [0..MaxBackends-1]
+ */
+ bkend = MyBackendId - 1;
+
+ /* We have a local stats counter for This commandtag. */
+ cnt = &(CmdStatsShmem->localcmdstats[bkend].cnt[local_stats_idx]);
+
+ /*
+ * We need to verify that our counter will not overflow when we
+ * increment it. We don't have to worry about anybody else changing
+ * our per-backend counter, so there is no race condition between
+ * checking for overflow and later incrementing.
+ *
+ * The atomic read is overkill, since nobody else can change the cnt.
+ * In most spots we need to read and write these values atomically,
+ * so we're just following that pattern here for good measure.
+ */
+ if (pg_atomic_read_u32(cnt) >= PG_UINT32_MAX)
+ /* Harvest and zero all our backend's stats. This takes a lock */
+ cmdstats_rollup_localstats(&CmdStatsShmem->localcmdstats[bkend]);
+
+ /*
+ * Increment our count atomically. This needs to be atomic since
+ * other backends could be reading the count simultaneously.
+ */
+ pg_atomic_fetch_add_u32(cnt, 1);
+ }
+ else
+ {
+ elog(ERROR, "cmdstats_increment called by postmaster");
+ }
+}
+
+void
+reset_cmdstats(void)
+{
+ int backend,
+ localidx,
+ tagidx;
+ LocalCmdStats *local_stats;
+ pg_atomic_uint32 *bkend_local_array;
+ CmdStats *shared_stats;
+ uint64 *shared_array;
+
+ Assert(cmdstats_tracking);
+ Assert(CmdStatsShmem);
+
+ shared_stats = (CmdStats *) CmdStatsShmem->cmdstats;
+ shared_array = (uint64 *) shared_stats->cnt;
+ local_stats = (LocalCmdStats *) CmdStatsShmem->localcmdstats;
+
+ LWLockAcquire(CmdStatsLock, LW_EXCLUSIVE);
+ for (backend = 0; backend < MaxBackends; backend++)
+ {
+ bkend_local_array = local_stats[backend].cnt;
+ for (localidx = 0; localidx < NUM_LOCAL_STATS; localidx++)
+ pg_atomic_write_u32(&(bkend_local_array[localidx]), 0);
+ }
+ for (tagidx = 0; tagidx < NUM_CMDSTATS_COUNTS; tagidx++)
+ shared_array[tagidx] = 0;
+ LWLockRelease(CmdStatsLock);
+}
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 462b4d7e06..0db0943e21 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -48,6 +48,7 @@
#include "pg_trace.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
+#include "postmaster/cmdstats.h"
#include "postmaster/fork_process.h"
#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
@@ -1337,6 +1338,12 @@ pgstat_reset_shared_counters(const char *target)
msg.m_resettarget = RESET_ARCHIVER;
else if (strcmp(target, "bgwriter") == 0)
msg.m_resettarget = RESET_BGWRITER;
+ else if (strcmp(target, "cmdstats") == 0)
+ {
+ if (cmdstats_tracking)
+ reset_cmdstats();
+ return;
+ }
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d59cd..1e336cb7ce 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -27,6 +27,7 @@
#include "postmaster/autovacuum.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/bgwriter.h"
+#include "postmaster/cmdstats.h"
#include "postmaster/postmaster.h"
#include "replication/logicallauncher.h"
#include "replication/origin.h"
@@ -138,6 +139,7 @@ CreateSharedMemoryAndSemaphores(void)
size = add_size(size, ProcSignalShmemSize());
size = add_size(size, CheckpointerShmemSize());
size = add_size(size, AutoVacuumShmemSize());
+ size = add_size(size, CmdStatsShmemSize());
size = add_size(size, ReplicationSlotsShmemSize());
size = add_size(size, ReplicationOriginShmemSize());
size = add_size(size, WalSndShmemSize());
@@ -250,6 +252,7 @@ CreateSharedMemoryAndSemaphores(void)
ProcSignalShmemInit();
CheckpointerShmemInit();
AutoVacuumShmemInit();
+ CmdStatsShmemInit();
ReplicationSlotsShmemInit();
ReplicationOriginShmemInit();
WalSndShmemInit();
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index db47843229..9c76e30051 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -49,3 +49,4 @@ MultiXactTruncationLock 41
OldSnapshotTimeMapLock 42
LogicalRepWorkerLock 43
CLogTruncationLock 44
+CmdStatsLock 45
diff --git a/src/backend/tcop/cmdtag.c b/src/backend/tcop/cmdtag.c
index b9fbff612f..7d173db2f5 100644
--- a/src/backend/tcop/cmdtag.c
+++ b/src/backend/tcop/cmdtag.c
@@ -23,10 +23,11 @@ typedef struct CommandTagBehavior
const bool event_trigger_ok;
const bool table_rewrite_ok;
const bool display_rowcount;
+ const int local_stats_idx;
} CommandTagBehavior;
-#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt) \
- { name, evtrgok, rwrok, rowcnt },
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, localstats) \
+ { name, evtrgok, rwrok, rowcnt, localstats },
const CommandTagBehavior tag_behavior[COMMAND_TAG_NEXTTAG] = {
#include "tcop/cmdtaglist.h"
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index 7208751ec7..abace92b96 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -39,6 +39,7 @@
#include "executor/tstoreReceiver.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
+#include "postmaster/cmdstats.h"
#include "utils/portal.h"
@@ -169,6 +170,9 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o
CommandTag tag;
const char *tagname;
+ if (cmdstats_tracking)
+ cmdstats_increment(qc->commandTag);
+
switch (dest)
{
case DestRemote:
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 54d2673254..0331c9e7aa 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -23,6 +23,7 @@
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/bgworker_internals.h"
+#include "postmaster/cmdstats.h"
#include "postmaster/postmaster.h"
#include "storage/proc.h"
#include "storage/procarray.h"
@@ -1984,3 +1985,64 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS)
/* Returns the record as Datum */
PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
}
+
+Datum
+pg_command_stats_data(PG_FUNCTION_ARGS)
+{
+ TupleDesc tupdesc;
+ Datum values[2] = { 0, 0 };
+ bool nulls[2] = { false, false };
+ ReturnSetInfo *rsi;
+ MemoryContext old_ctx;
+ Tuplestorestate *tuple_store;
+ CommandTag tag;
+
+ rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* Check to see if caller supports us returning a tuplestore */
+ if (rsi == NULL || !IsA(rsi, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsi->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not "
+ "allowed in this context")));
+
+ rsi->returnMode = SFRM_Materialize;
+
+ /* 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");
+
+ /* Build tuplestore to hold the result rows */
+ old_ctx = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+ tuple_store =
+ tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ false, work_mem);
+ rsi->setDesc = tupdesc;
+ rsi->setResult = tuple_store;
+
+ MemoryContextSwitchTo(old_ctx);
+
+ if (cmdstats_tracking)
+ {
+ CmdStats *tally = cmdstats_shared_tally();
+ for (tag = FIRST_CMDTAG; tally != NULL && tag <= LAST_CMDTAG; tag++)
+ {
+ HeapTuple tuple;
+
+ if (tally->cnt[tag] == 0)
+ continue;
+
+ values[0] = CStringGetTextDatum(GetCommandTagName(tag));
+ values[1] = Int64GetDatum(tally->cnt[tag]);
+
+ tuple = heap_form_tuple(tupdesc, values, nulls);
+ tuplestore_puttuple(tuple_store, tuple);
+ }
+ }
+ PG_RETURN_NULL();
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index dbecc00fef..897e5e757f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -62,6 +62,7 @@
#include "postmaster/autovacuum.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/bgwriter.h"
+#include "postmaster/cmdstats.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "postmaster/walwriter.h"
@@ -1483,6 +1484,18 @@ static struct config_bool ConfigureNamesBool[] =
false,
NULL, NULL, NULL
},
+ {
+ {"cmdstats_tracking", PGC_POSTMASTER, STATS_COLLECTOR,
+ gettext_noop("Collects statistics of commands by type."),
+ gettext_noop("For each type of command (INSERT, UPDATE, DELETE, "
+ "CREATE TABLE, etc), tracks statistics about the "
+ "commands of that type which have been run."),
+ GUC_SUPERUSER_ONLY
+ },
+ &cmdstats_tracking,
+ false,
+ NULL, NULL, NULL
+ },
{
{"update_process_title", PGC_SUSET, PROCESS_TITLE,
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 07a86c7b7b..91f1f45b0b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5379,6 +5379,12 @@
proargmodes => '{o,o,o,o,o,o,o}',
proargnames => '{archived_count,last_archived_wal,last_archived_time,failed_count,last_failed_wal,last_failed_time,stats_reset}',
prosrc => 'pg_stat_get_archiver' },
+{ oid => '8450', descr => 'statistics: information about sql commands',
+ proname => 'pg_command_stats_data', prorows => '100', proretset => 't',
+ proisstrict => 'f', provolatile => 'v', proparallel => 'r',
+ prorettype => 'record', proargtypes => '', proallargtypes => '{text,int8}',
+ proargmodes => '{o,o}', proargnames => '{tag,count}',
+ prosrc => 'pg_command_stats_data' },
{ oid => '2769',
descr => 'statistics: number of timed checkpoints started by the bgwriter',
proname => 'pg_stat_get_bgwriter_timed_checkpoints', provolatile => 's',
diff --git a/src/include/postmaster/cmdstats.h b/src/include/postmaster/cmdstats.h
new file mode 100644
index 0000000000..c1a08dcb0c
--- /dev/null
+++ b/src/include/postmaster/cmdstats.h
@@ -0,0 +1,68 @@
+/* ----------
+ * pgstat.h
+ *
+ * Declarations for PostgreSQL command statistics
+ *
+ * Copyright (c) 2001-2020, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/cmdstats.h
+ * ----------
+ */
+#ifndef CMDSTATS_H
+#define CMDSTATS_H
+
+#include "postgres.h"
+
+#include "tcop/cmdtag.h"
+
+extern PGDLLIMPORT bool cmdstats_tracking;
+
+/*-------------
+ * Command Stats shmem structs.
+ *-------------
+ */
+/*
+ * For each command tag, we track how many commands completed
+ * for that type.
+ */
+#define NUM_CMDSTATS_COUNTS ((uint32)NUM_CMDTAGS)
+
+/*
+ * CmdStats is used for holding the shared counts per CommandTag for all tags.
+ */
+typedef struct CmdStats {
+ uint64 cnt[NUM_CMDSTATS_COUNTS];
+} CmdStats;
+
+/*
+ * LocalCmdStats is used for holding the per-backend local counts for just
+ * the subset of CommandTags which have per-backend counts. There are
+ * NUM_LOCAL_STATS < NUM_CMDTAGS number of those. Indexing into this
+ * structure for a given CommandTag requires looking up the appropriate
+ * offset. See local_stats_offset for details.
+ */
+typedef struct LocalCmdStats {
+ pg_atomic_uint32 cnt[NUM_LOCAL_STATS];
+} LocalCmdStats;
+
+/*
+ * LocalStatsAccum is used for totalling up the per-backend counts. We cannot
+ * simply use a LocalCmdStats struct for that, as the totals across backends
+ * may overflow a uint32. Thus, we have only NUM_LOCAL_STATS counts, but use a
+ * uint64 per count.
+ */
+typedef struct LocalStatsAccum {
+ uint64 cnt[NUM_LOCAL_STATS];
+} LocalStatsAccum;
+
+/* shared memory stuff */
+extern int NumCmdStats(void);
+extern Size CmdStatsPerBackendShmemSize(void);
+extern Size CmdStatsShmemSize(void);
+extern void CmdStatsShmemInit(void);
+extern CmdStats *cmdstats_shared_tally(void);
+extern void cmdstats_rollup_localstats(LocalCmdStats *localstats);
+extern void cmdstats_increment(CommandTag commandtag);
+extern void reset_cmdstats(void);
+
+#endif /* CMDSTATS_H */
diff --git a/src/include/tcop/cmdtag.h b/src/include/tcop/cmdtag.h
index f75a91e7c8..5795db9ba3 100644
--- a/src/include/tcop/cmdtag.h
+++ b/src/include/tcop/cmdtag.h
@@ -14,7 +14,7 @@
#define CMDTAG_H
-#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt) \
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, localstats) \
tag,
typedef enum CommandTag
@@ -23,6 +23,10 @@ typedef enum CommandTag
COMMAND_TAG_NEXTTAG
} CommandTag;
+#define FIRST_CMDTAG ((CommandTag)0)
+#define LAST_CMDTAG ((CommandTag)(COMMAND_TAG_NEXTTAG - 1))
+#define NUM_CMDTAGS ((int)COMMAND_TAG_NEXTTAG)
+
#undef PG_CMDTAG
typedef struct QueryCompletion
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d28145a50d..67016cdf95 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -23,196 +23,201 @@
* textual name, so that we can bsearch on it; see GetCommandTagEnum().
*/
-/* symbol name, textual name, event_trigger_ok, table_rewrite_ok, rowcount, last_oid */
-PG_CMDTAG(CMDTAG_UNKNOWN, "???", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ACCESS_METHOD, "ALTER ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_AGGREGATE, "ALTER AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CAST, "ALTER CAST", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_COLLATION, "ALTER COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CONVERSION, "ALTER CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DATABASE, "ALTER DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DEFAULT_PRIVILEGES, "ALTER DEFAULT PRIVILEGES", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DOMAIN, "ALTER DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_EVENT_TRIGGER, "ALTER EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_EXTENSION, "ALTER EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FOREIGN_DATA_WRAPPER, "ALTER FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FOREIGN_TABLE, "ALTER FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FUNCTION, "ALTER FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_INDEX, "ALTER INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_LANGUAGE, "ALTER LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_LARGE_OBJECT, "ALTER LARGE OBJECT", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_MATERIALIZED_VIEW, "ALTER MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR, "ALTER OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_POLICY, "ALTER POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ROLE, "ALTER ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ROUTINE, "ALTER ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_RULE, "ALTER RULE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SCHEMA, "ALTER SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SEQUENCE, "ALTER SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SERVER, "ALTER SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_STATISTICS, "ALTER STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SUBSCRIPTION, "ALTER SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SYSTEM, "ALTER SYSTEM", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TABLE, "ALTER TABLE", true, true, false)
-PG_CMDTAG(CMDTAG_ALTER_TABLESPACE, "ALTER TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION, "ALTER TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY, "ALTER TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_PARSER, "ALTER TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_TEMPLATE, "ALTER TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false)
-PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false)
-PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false)
-PG_CMDTAG(CMDTAG_CALL, "CALL", false, false, false)
-PG_CMDTAG(CMDTAG_CHECKPOINT, "CHECKPOINT", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE, "CLOSE", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE_CURSOR, "CLOSE CURSOR", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE_CURSOR_ALL, "CLOSE CURSOR ALL", false, false, false)
-PG_CMDTAG(CMDTAG_CLUSTER, "CLUSTER", false, false, false)
-PG_CMDTAG(CMDTAG_COMMENT, "COMMENT", true, false, false)
-PG_CMDTAG(CMDTAG_COMMIT, "COMMIT", false, false, false)
-PG_CMDTAG(CMDTAG_COMMIT_PREPARED, "COMMIT PREPARED", false, false, false)
-PG_CMDTAG(CMDTAG_COPY, "COPY", false, false, true)
-PG_CMDTAG(CMDTAG_COPY_FROM, "COPY FROM", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_DOMAIN, "CREATE DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_EVENT_TRIGGER, "CREATE EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_EXTENSION, "CREATE EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FOREIGN_DATA_WRAPPER, "CREATE FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FOREIGN_TABLE, "CREATE FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FUNCTION, "CREATE FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_INDEX, "CREATE INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_LANGUAGE, "CREATE LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_MATERIALIZED_VIEW, "CREATE MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR, "CREATE OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_POLICY, "CREATE POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ROLE, "CREATE ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ROUTINE, "CREATE ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_RULE, "CREATE RULE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SCHEMA, "CREATE SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SEQUENCE, "CREATE SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SERVER, "CREATE SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_STATISTICS, "CREATE STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SUBSCRIPTION, "CREATE SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLE, "CREATE TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLE_AS, "CREATE TABLE AS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLESPACE, "CREATE TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_CONFIGURATION, "CREATE TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_DICTIONARY, "CREATE TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_PARSER, "CREATE TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_TEMPLATE, "CREATE TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false)
-PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false)
-PG_CMDTAG(CMDTAG_DECLARE_CURSOR, "DECLARE CURSOR", false, false, false)
-PG_CMDTAG(CMDTAG_DELETE, "DELETE", false, false, true)
-PG_CMDTAG(CMDTAG_DISCARD, "DISCARD", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false)
-PG_CMDTAG(CMDTAG_DO, "DO", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_DOMAIN, "DROP DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_EVENT_TRIGGER, "DROP EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_EXTENSION, "DROP EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FOREIGN_DATA_WRAPPER, "DROP FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FOREIGN_TABLE, "DROP FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FUNCTION, "DROP FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_INDEX, "DROP INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_LANGUAGE, "DROP LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_MATERIALIZED_VIEW, "DROP MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR, "DROP OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR_CLASS, "DROP OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_REPLICATION_SLOT, "DROP REPLICATION SLOT", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ROLE, "DROP ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ROUTINE, "DROP ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_RULE, "DROP RULE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SCHEMA, "DROP SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SEQUENCE, "DROP SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SERVER, "DROP SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_STATISTICS, "DROP STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SUBSCRIPTION, "DROP SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TABLE, "DROP TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TABLESPACE, "DROP TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_CONFIGURATION, "DROP TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_DICTIONARY, "DROP TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_PARSER, "DROP TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_TEMPLATE, "DROP TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false)
-PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false)
-PG_CMDTAG(CMDTAG_FETCH, "FETCH", false, false, true)
-PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false)
-PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true)
-PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false)
-PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false)
-PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false)
-PG_CMDTAG(CMDTAG_MOVE, "MOVE", false, false, true)
-PG_CMDTAG(CMDTAG_NOTIFY, "NOTIFY", false, false, false)
-PG_CMDTAG(CMDTAG_PREPARE, "PREPARE", false, false, false)
-PG_CMDTAG(CMDTAG_PREPARE_TRANSACTION, "PREPARE TRANSACTION", false, false, false)
-PG_CMDTAG(CMDTAG_REASSIGN_OWNED, "REASSIGN OWNED", false, false, false)
-PG_CMDTAG(CMDTAG_REFRESH_MATERIALIZED_VIEW, "REFRESH MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_REINDEX, "REINDEX", false, false, false)
-PG_CMDTAG(CMDTAG_RELEASE, "RELEASE", false, false, false)
-PG_CMDTAG(CMDTAG_RESET, "RESET", false, false, false)
-PG_CMDTAG(CMDTAG_REVOKE, "REVOKE", true, false, false)
-PG_CMDTAG(CMDTAG_REVOKE_ROLE, "REVOKE ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_ROLLBACK, "ROLLBACK", false, false, false)
-PG_CMDTAG(CMDTAG_ROLLBACK_PREPARED, "ROLLBACK PREPARED", false, false, false)
-PG_CMDTAG(CMDTAG_SAVEPOINT, "SAVEPOINT", false, false, false)
-PG_CMDTAG(CMDTAG_SECURITY_LABEL, "SECURITY LABEL", true, false, false)
-PG_CMDTAG(CMDTAG_SELECT, "SELECT", false, false, true)
-PG_CMDTAG(CMDTAG_SELECT_FOR_KEY_SHARE, "SELECT FOR KEY SHARE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_NO_KEY_UPDATE, "SELECT FOR NO KEY UPDATE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_SHARE, "SELECT FOR SHARE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_UPDATE, "SELECT FOR UPDATE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_INTO, "SELECT INTO", true, false, false)
-PG_CMDTAG(CMDTAG_SET, "SET", false, false, false)
-PG_CMDTAG(CMDTAG_SET_CONSTRAINTS, "SET CONSTRAINTS", false, false, false)
-PG_CMDTAG(CMDTAG_SHOW, "SHOW", false, false, false)
-PG_CMDTAG(CMDTAG_START_TRANSACTION, "START TRANSACTION", false, false, false)
-PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
-PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
-PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
-PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+/* symbol name, textual name, event_trigger_ok, table_rewrite_ok, rowcount, localstats */
+PG_CMDTAG(CMDTAG_UNKNOWN, "???", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_ACCESS_METHOD, "ALTER ACCESS METHOD", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_AGGREGATE, "ALTER AGGREGATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_CAST, "ALTER CAST", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_COLLATION, "ALTER COLLATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_CONVERSION, "ALTER CONVERSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_DATABASE, "ALTER DATABASE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_DEFAULT_PRIVILEGES, "ALTER DEFAULT PRIVILEGES", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_DOMAIN, "ALTER DOMAIN", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_EVENT_TRIGGER, "ALTER EVENT TRIGGER", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_EXTENSION, "ALTER EXTENSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_FOREIGN_DATA_WRAPPER, "ALTER FOREIGN DATA WRAPPER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_FOREIGN_TABLE, "ALTER FOREIGN TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_FUNCTION, "ALTER FUNCTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_INDEX, "ALTER INDEX", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_LANGUAGE, "ALTER LANGUAGE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_LARGE_OBJECT, "ALTER LARGE OBJECT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_MATERIALIZED_VIEW, "ALTER MATERIALIZED VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR, "ALTER OPERATOR", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_POLICY, "ALTER POLICY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_ROLE, "ALTER ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_ROUTINE, "ALTER ROUTINE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_RULE, "ALTER RULE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SCHEMA, "ALTER SCHEMA", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SEQUENCE, "ALTER SEQUENCE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SERVER, "ALTER SERVER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_STATISTICS, "ALTER STATISTICS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SUBSCRIPTION, "ALTER SUBSCRIPTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SYSTEM, "ALTER SYSTEM", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TABLE, "ALTER TABLE", true, true, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TABLESPACE, "ALTER TABLESPACE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION, "ALTER TEXT SEARCH CONFIGURATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY, "ALTER TEXT SEARCH DICTIONARY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_PARSER, "ALTER TEXT SEARCH PARSER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_TEMPLATE, "ALTER TEXT SEARCH TEMPLATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false, 0)
+PG_CMDTAG(CMDTAG_CALL, "CALL", false, false, false, 1)
+PG_CMDTAG(CMDTAG_CHECKPOINT, "CHECKPOINT", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CLOSE, "CLOSE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CLOSE_CURSOR, "CLOSE CURSOR", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CLOSE_CURSOR_ALL, "CLOSE CURSOR ALL", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CLUSTER, "CLUSTER", false, false, false, -1)
+PG_CMDTAG(CMDTAG_COMMENT, "COMMENT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_COMMIT, "COMMIT", false, false, false, 2)
+PG_CMDTAG(CMDTAG_COMMIT_PREPARED, "COMMIT PREPARED", false, false, false, 3)
+PG_CMDTAG(CMDTAG_COPY, "COPY", false, false, true, -1)
+PG_CMDTAG(CMDTAG_COPY_FROM, "COPY FROM", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_DOMAIN, "CREATE DOMAIN", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_EVENT_TRIGGER, "CREATE EVENT TRIGGER", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_EXTENSION, "CREATE EXTENSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_FOREIGN_DATA_WRAPPER, "CREATE FOREIGN DATA WRAPPER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_FOREIGN_TABLE, "CREATE FOREIGN TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_FUNCTION, "CREATE FUNCTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_INDEX, "CREATE INDEX", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_LANGUAGE, "CREATE LANGUAGE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_MATERIALIZED_VIEW, "CREATE MATERIALIZED VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR, "CREATE OPERATOR", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_POLICY, "CREATE POLICY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_ROLE, "CREATE ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_ROUTINE, "CREATE ROUTINE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_RULE, "CREATE RULE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_SCHEMA, "CREATE SCHEMA", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_SEQUENCE, "CREATE SEQUENCE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_SERVER, "CREATE SERVER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_STATISTICS, "CREATE STATISTICS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_SUBSCRIPTION, "CREATE SUBSCRIPTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TABLE, "CREATE TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TABLE_AS, "CREATE TABLE AS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TABLESPACE, "CREATE TABLESPACE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_CONFIGURATION, "CREATE TEXT SEARCH CONFIGURATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_DICTIONARY, "CREATE TEXT SEARCH DICTIONARY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_PARSER, "CREATE TEXT SEARCH PARSER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_TEMPLATE, "CREATE TEXT SEARCH TEMPLATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DECLARE_CURSOR, "DECLARE CURSOR", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DELETE, "DELETE", false, false, true, 4)
+PG_CMDTAG(CMDTAG_DISCARD, "DISCARD", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DO, "DO", false, false, false, 5)
+PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_DOMAIN, "DROP DOMAIN", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_EVENT_TRIGGER, "DROP EVENT TRIGGER", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_EXTENSION, "DROP EXTENSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_FOREIGN_DATA_WRAPPER, "DROP FOREIGN DATA WRAPPER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_FOREIGN_TABLE, "DROP FOREIGN TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_FUNCTION, "DROP FUNCTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_INDEX, "DROP INDEX", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_LANGUAGE, "DROP LANGUAGE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_MATERIALIZED_VIEW, "DROP MATERIALIZED VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR, "DROP OPERATOR", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR_CLASS, "DROP OPERATOR CLASS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_REPLICATION_SLOT, "DROP REPLICATION SLOT", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_ROLE, "DROP ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_ROUTINE, "DROP ROUTINE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_RULE, "DROP RULE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_SCHEMA, "DROP SCHEMA", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_SEQUENCE, "DROP SEQUENCE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_SERVER, "DROP SERVER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_STATISTICS, "DROP STATISTICS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_SUBSCRIPTION, "DROP SUBSCRIPTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TABLE, "DROP TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TABLESPACE, "DROP TABLESPACE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_CONFIGURATION, "DROP TEXT SEARCH CONFIGURATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_DICTIONARY, "DROP TEXT SEARCH DICTIONARY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_PARSER, "DROP TEXT SEARCH PARSER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_TEMPLATE, "DROP TEXT SEARCH TEMPLATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false, 6)
+PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false, -1)
+PG_CMDTAG(CMDTAG_FETCH, "FETCH", false, false, true, 7)
+PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false, -1)
+PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true, 8)
+PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false, 9)
+PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false, -1)
+PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_MOVE, "MOVE", false, false, true, 10)
+PG_CMDTAG(CMDTAG_NOTIFY, "NOTIFY", false, false, false, 11)
+PG_CMDTAG(CMDTAG_PREPARE, "PREPARE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_PREPARE_TRANSACTION, "PREPARE TRANSACTION", false, false, false, -1)
+PG_CMDTAG(CMDTAG_REASSIGN_OWNED, "REASSIGN OWNED", false, false, false, -1)
+PG_CMDTAG(CMDTAG_REFRESH_MATERIALIZED_VIEW, "REFRESH MATERIALIZED VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_REINDEX, "REINDEX", false, false, false, -1)
+PG_CMDTAG(CMDTAG_RELEASE, "RELEASE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_RESET, "RESET", false, false, false, 12)
+PG_CMDTAG(CMDTAG_REVOKE, "REVOKE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_REVOKE_ROLE, "REVOKE ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ROLLBACK, "ROLLBACK", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ROLLBACK_PREPARED, "ROLLBACK PREPARED", false, false, false, -1)
+PG_CMDTAG(CMDTAG_SAVEPOINT, "SAVEPOINT", false, false, false, -1)
+PG_CMDTAG(CMDTAG_SECURITY_LABEL, "SECURITY LABEL", true, false, false, -1)
+PG_CMDTAG(CMDTAG_SELECT, "SELECT", false, false, true, 13)
+PG_CMDTAG(CMDTAG_SELECT_FOR_KEY_SHARE, "SELECT FOR KEY SHARE", false, false, false, 14)
+PG_CMDTAG(CMDTAG_SELECT_FOR_NO_KEY_UPDATE, "SELECT FOR NO KEY UPDATE", false, false, false, 15)
+PG_CMDTAG(CMDTAG_SELECT_FOR_SHARE, "SELECT FOR SHARE", false, false, false, 16)
+PG_CMDTAG(CMDTAG_SELECT_FOR_UPDATE, "SELECT FOR UPDATE", false, false, false, 17)
+PG_CMDTAG(CMDTAG_SELECT_INTO, "SELECT INTO", true, false, false, -1)
+PG_CMDTAG(CMDTAG_SET, "SET", false, false, false, 18)
+PG_CMDTAG(CMDTAG_SET_CONSTRAINTS, "SET CONSTRAINTS", false, false, false, -1)
+PG_CMDTAG(CMDTAG_SHOW, "SHOW", false, false, false, 19)
+PG_CMDTAG(CMDTAG_START_TRANSACTION, "START TRANSACTION", false, false, false, -1)
+PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false, 20)
+PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true, 21)
+PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false, -1)
+
+/* Keep this one more than the highest localstats value above */
+#ifndef NUM_LOCAL_STATS
+#define NUM_LOCAL_STATS 22
+#endif
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..f4ba52f617 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -12,7 +12,7 @@ subdir = src/test
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
-SUBDIRS = perl regress isolation modules authentication recovery subscription
+SUBDIRS = perl regress isolation modules authentication recovery cmdstats subscription
# Test suites that are not safe by default but can be run if selected
# by the user via the whitespace-separated list in variable
diff --git a/src/test/cmdstats/Makefile b/src/test/cmdstats/Makefile
new file mode 100644
index 0000000000..5464c433f4
--- /dev/null
+++ b/src/test/cmdstats/Makefile
@@ -0,0 +1,25 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/cmdstats
+#
+# Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/cmdstats/Makefile
+#
+#-------------------------------------------------------------------------
+
+EXTRA_INSTALL=contrib/test_decoding
+
+subdir = src/test/cmdstats
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
+
+clean distclean maintainer-clean:
+ rm -rf tmp_check
diff --git a/src/test/cmdstats/README b/src/test/cmdstats/README
new file mode 100644
index 0000000000..7219c4fd07
--- /dev/null
+++ b/src/test/cmdstats/README
@@ -0,0 +1,23 @@
+src/test/cmdstats/README
+
+Regression tests for cmdstats statistics subsystem
+=============================================
+
+This directory contains a test suite for per command statistics.
+
+Running the tests
+=================
+
+NOTE: You must have given the --enable-tap-tests argument to configure.
+
+Run
+ make check
+or
+ make installcheck
+You can use "make installcheck" if you previously did "make install".
+In that case, the code in the installation tree is tested. With
+"make check", a temporary installation tree is built from the current
+sources and then tested.
+
+Either way, this test initializes, starts, and stops several test Postgres
+clusters.
diff --git a/src/test/cmdstats/t/001_guc_settings.pl b/src/test/cmdstats/t/001_guc_settings.pl
new file mode 100644
index 0000000000..e2cdfa4a25
--- /dev/null
+++ b/src/test/cmdstats/t/001_guc_settings.pl
@@ -0,0 +1,31 @@
+# Minimal test of cmdstats GUC default settings
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 3;
+
+my ($node, $result);
+
+$node = get_new_node("cmdstats_guc_test");
+$node->init;
+$node->start;
+
+# Check that by default cmdstats_tracking is off
+$result = $node->safe_psql('postgres', "SHOW cmdstats_tracking");
+is($result, 'off', 'check default setting of cmdstats_tracking');
+
+# Verify that a mere reload cannot change cmdstats_tracking
+$node->append_conf('postgresql.conf', "cmdstats_tracking = true");
+$node->reload;
+$result = $node->safe_psql('postgres', "SHOW cmdstats_tracking");
+is($result, 'off', 'check default setting of cmdstats_tracking');
+
+# Verify that a restart does change cmdstats_tracking
+$node->restart;
+$result = $node->safe_psql('postgres', "SHOW cmdstats_tracking");
+is($result, 'on', 'check default setting of cmdstats_tracking');
+
+$node->stop;
+$node->teardown_node;
+$node->clean_node;
diff --git a/src/test/cmdstats/t/002_stats.pl b/src/test/cmdstats/t/002_stats.pl
new file mode 100644
index 0000000000..8366399e83
--- /dev/null
+++ b/src/test/cmdstats/t/002_stats.pl
@@ -0,0 +1,53 @@
+# Test of cmdstats system view
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 10;
+
+my ($node, $result);
+
+$node = get_new_node("cmdstats_test");
+$node->init;
+$node->append_conf('postgresql.conf', "cmdstats_tracking = true");
+$node->start;
+
+# Reset the stats. Beware that this in itself is a SELECT statement, and it gets counted *after* the reset.
+$result = $node->safe_psql('postgres', "SELECT pg_stat_reset_shared('cmdstats')");
+
+# Check that we see the one select from above. Note that this, too, is a SELECT that gets counted *after* it runs
+$result = $node->safe_psql('postgres', "SELECT tag || ':' || cnt::text FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, 'SELECT:1', 'have run one SELECT statement');
+
+# Check that we see both selects from above
+$result = $node->safe_psql('postgres', "SELECT tag || ':' || cnt::text FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, 'SELECT:2', 'have run two SELECT statements');
+
+# Check that we don't see any other kinds of statements
+$result = $node->safe_psql('postgres', "SELECT COUNT(DISTINCT tag) FROM pg_command_stats");
+is($result, '1', 'have run only kind of statement');
+
+# Run a CREATE TABLE statement
+$result = $node->safe_psql('postgres', "CREATE TABLE tst (i INTEGER)");
+
+# Check that we now see both SELECT and CREATE TABLE
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, '4', 'have run four SELECT statements');
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'CREATE TABLE'");
+is($result, '1', 'have run one CREATE TABLE statement');
+$result = $node->safe_psql('postgres', "SELECT COUNT(DISTINCT tag) FROM pg_command_stats");
+is($result, '2', 'have run two kinds of statements');
+
+# Reset all counters. Note that the SELECT which resets the counters will itself get counted
+$node->safe_psql('postgres', "SELECT * FROM pg_stat_reset_shared('cmdstats')");
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, '1', 'have run one SELECT statement since resetting the counters');
+
+# Check that multi-statement commands are all counted, not just one of them
+$node->safe_psql('postgres', "BEGIN; SELECT 1; ROLLBACK; BEGIN; SELECT 2; ROLLBACK; BEGIN; SELECT 3; ROLLBACK");
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, '5', 'have run five SELECT statements since resetting the counters');
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'BEGIN'");
+is($result, '3', 'have run three BEGIN statements');
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'ROLLBACK'");
+is($result, '3', 'have run three ROLLBACK statements');
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index c7304611c3..5665981dca 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1324,6 +1324,9 @@ pg_available_extensions| SELECT e.name,
e.comment
FROM (pg_available_extensions() e(name, default_version, comment)
LEFT JOIN pg_extension x ON ((e.name = x.extname)));
+pg_command_stats| SELECT s.tag,
+ s.cnt
+ FROM pg_command_stats_data() s(tag, cnt);
pg_config| SELECT pg_config.name,
pg_config.setting
FROM pg_config() pg_config(name, setting);
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index b01e58b98c..4b873eedc4 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -201,4 +201,25 @@ FROM prevstats AS pr;
DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
DROP TABLE prevstats;
+-- Minimal test of pg_command_stats. Since cmdstats_tracking defaults to
+-- off, and cannot be changed without a cluster restart, there is nothing we can
+-- do to interrogate its behavior here. But we can at least verify that it
+-- exists, has the right columns, and does nothing unexpected when we attempt to
+-- reset its counts
+SELECT * FROM pg_command_stats;
+ tag | cnt
+-----+-----
+(0 rows)
+
+SELECT * FROM pg_stat_reset_shared('cmdstats');
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+SELECT * FROM pg_command_stats;
+ tag | cnt
+-----+-----
+(0 rows)
+
-- End of Stats Test
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index feaaee6326..90ad262021 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -176,4 +176,14 @@ FROM prevstats AS pr;
DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
DROP TABLE prevstats;
+
+-- Minimal test of pg_command_stats. Since cmdstats_tracking defaults to
+-- off, and cannot be changed without a cluster restart, there is nothing we can
+-- do to interrogate its behavior here. But we can at least verify that it
+-- exists, has the right columns, and does nothing unexpected when we attempt to
+-- reset its counts
+
+SELECT * FROM pg_command_stats;
+SELECT * FROM pg_stat_reset_shared('cmdstats');
+SELECT * FROM pg_command_stats;
-- End of Stats Test
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e216de9570..2ef3ea681c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -342,6 +342,7 @@ Clump
ClusterInfo
ClusterStmt
CmdType
+CmdStatsShmemStruct
CoalesceExpr
CoerceParamHook
CoerceToDomain
--
2.21.1 (Apple Git-122.3)
On Mar 4, 2020, at 7:43 PM, Mark Dilger <mark.dilger@enterprisedb.com> wrote:
Hackers,
as mentioned in [1], I have created an implementation of command counter statistics very similar in purpose to the one already pending in the commitfest going by the name "pg_stat_sql". I don't really care if this implementation is seen as building on that one or as separate, but I was worried about hijacking that thread, so I'm starting this thead. There are elements of this patch that borrowed from that one, so if this is accepted, authorship should reflect that.
See the work by Haribabu Kommi (haribabu) at https://commitfest.postgresql.org/27/790/
The two main differences are that
(1) This implementation is based on commandtags as enums, not strings and
(2) This implementation uses techniques to reduce lock contentionI think (2) is the more important part.
[1] /messages/by-id/28F46159-3764-421B-904B-004DEA339310@enterprisedb.com
<v1-0001-Implementing-the-cmdstats-subsystem.patch>
—
Rebased patch against master caa3c4242cf86322e2ed0c86199e6462a2c41565.
Attachments:
v1-0001-Implementing-the-cmdstats-subsystem.patchapplication/octet-stream; name=v1-0001-Implementing-the-cmdstats-subsystem.patch; x-unix-mode=0644Download
From ba2f2f3dae82189ef455547a569bfa5747d30b0b Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Wed, 4 Mar 2020 14:39:46 -0800
Subject: [PATCH v1] Implementing the cmdstats subsystem.
This implements a subsystem for tracking the number of times a type of command
has been run in a database cluster, since startup or since the last time the
counts were reset, whichever is newer.
This work is based on and borrows from work by Haribabu Kommi (haribabu) as
reflected in the patch submission:
SQL statements statistics counter view (pg_stat_sql)
which itself was reviewed by dilip kumar (dilip.kumar), Vinayak Pokale (vinpokale)
and probably others.
---
src/backend/catalog/system_views.sql | 7 +
src/backend/postmaster/Makefile | 1 +
src/backend/postmaster/cmdstats.c | 447 +++++++++++++++++++++++
src/backend/postmaster/pgstat.c | 7 +
src/backend/storage/ipc/ipci.c | 3 +
src/backend/storage/lmgr/lwlocknames.txt | 1 +
src/backend/tcop/cmdtag.c | 5 +-
src/backend/tcop/dest.c | 4 +
src/backend/utils/adt/pgstatfuncs.c | 62 ++++
src/backend/utils/misc/guc.c | 13 +
src/include/catalog/pg_proc.dat | 6 +
src/include/postmaster/cmdstats.h | 68 ++++
src/include/tcop/cmdtag.h | 6 +-
src/include/tcop/cmdtaglist.h | 391 ++++++++++----------
src/test/Makefile | 2 +-
src/test/cmdstats/Makefile | 25 ++
src/test/cmdstats/README | 23 ++
src/test/cmdstats/t/001_guc_settings.pl | 31 ++
src/test/cmdstats/t/002_stats.pl | 53 +++
src/test/regress/expected/rules.out | 3 +
src/test/regress/expected/stats.out | 21 ++
src/test/regress/sql/stats.sql | 10 +
src/tools/pgindent/typedefs.list | 1 +
23 files changed, 993 insertions(+), 197 deletions(-)
create mode 100644 src/backend/postmaster/cmdstats.c
create mode 100644 src/include/postmaster/cmdstats.h
create mode 100644 src/test/cmdstats/Makefile
create mode 100644 src/test/cmdstats/README
create mode 100644 src/test/cmdstats/t/001_guc_settings.pl
create mode 100644 src/test/cmdstats/t/002_stats.pl
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46912..b27f6abbf9 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -290,6 +290,13 @@ CREATE VIEW pg_stats_ext WITH (security_barrier) AS
WHERE NOT has_column_privilege(c.oid, a.attnum, 'select') )
AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+CREATE VIEW pg_command_stats AS
+ SELECT s.tag, s.cnt
+ FROM pg_command_stats_data() AS s(tag, cnt);
+
+REVOKE EXECUTE ON FUNCTION pg_command_stats_data() FROM PUBLIC;
+REVOKE ALL ON pg_command_stats FROM PUBLIC;
+
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL on pg_statistic_ext_data FROM public;
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index bfdf6a833d..d23580a457 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -17,6 +17,7 @@ OBJS = \
bgworker.o \
bgwriter.o \
checkpointer.o \
+ cmdstats.o \
fork_process.o \
interrupt.o \
pgarch.o \
diff --git a/src/backend/postmaster/cmdstats.c b/src/backend/postmaster/cmdstats.c
new file mode 100644
index 0000000000..a16d1e9d0c
--- /dev/null
+++ b/src/backend/postmaster/cmdstats.c
@@ -0,0 +1,447 @@
+/* ----------
+ * cmdstats.c
+ *
+ * Copyright (c) 2001-2020, PostgreSQL Global Development Group
+ *
+ * src/backend/postmaster/cmdstats.c
+ * ----------
+ */
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "commands/dbcommands.h"
+#include "miscadmin.h"
+#include "port/atomics.h"
+#include "postmaster/cmdstats.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "utils/acl.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
+
+
+/*
+ * Create a mapping from a CommandTag to its offset in the per-backend stats
+ * (if any), else defaulting to -1. There will be an entry in this array for
+ * every CommandTag.
+ */
+typedef struct LocalStatsOffset {
+ int offset;
+} LocalStatsOffset;
+
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, localstats) \
+ [tag] = { .offset = localstats },
+
+const LocalStatsOffset local_stats_offset[] = {
+#include "tcop/cmdtaglist.h"
+ [COMMAND_TAG_NEXTTAG] = { .offset = -1 }
+};
+
+#undef PG_CMDTAG
+
+static void
+merge_local_accum(CmdStats *pg_restrict stats, const LocalStatsAccum *pg_restrict accum)
+{
+ CommandTag tag;
+ uint64 *dst = (uint64 *) stats->cnt;
+ const uint64 *src = (const uint64 *) accum->cnt;
+
+ for (tag = FIRST_CMDTAG; tag <= LAST_CMDTAG; tag++)
+ {
+ int offset = local_stats_offset[tag].offset;
+ if (offset >= 0)
+ dst[tag] += src[offset];
+ }
+}
+
+static void
+rollup_local_stats(CmdStats *pg_restrict stats, LocalCmdStats *pg_restrict localstats)
+{
+ CommandTag tag;
+ uint64 *dst = (uint64 *) stats->cnt;
+ pg_atomic_uint32 *src = (pg_atomic_uint32 *) localstats->cnt;
+
+ LWLockAcquire(CmdStatsLock, LW_EXCLUSIVE);
+ /*
+ * We need to read the src[] array element and zero it in the same atomic
+ * operation. We hold an exclusive lock, but backends who merely update the
+ * value of their own count don't try to take the lock, so we get no protection
+ * against concurrent updates by them.
+ */
+ for (tag = FIRST_CMDTAG; tag <= LAST_CMDTAG; tag++)
+ {
+ int offset = local_stats_offset[tag].offset;
+ if (offset >= 0)
+ dst[tag] += pg_atomic_exchange_u32(&(src[offset]), 0);
+ }
+ LWLockRelease(CmdStatsLock);
+}
+
+/* ----------
+ * GUC parameters
+ * ----------
+ */
+bool cmdstats_tracking = false;
+
+/*
+ * This structure holds counters in shared memory tracking how many commands of
+ * a given type have been run.
+ *
+ * CmdStatsShmem->cmdstats
+ * -------------------------------
+ * A single shared array of uint64 counts reserved for counting per commandtag
+ * how many operations of that commandtag have been executed to completion.
+ * Reading and writing these counts requires lwlocks. The size of this array
+ * is proportional to NUM_CMDSTATS_COUNTS. These counts may underrepresent the
+ * true totals if the backends are keeping local per-backend counts.
+ *
+ * CmdStatsShmem->localcmdstats
+ * ------------------------------------
+ * For the subset of commands that may be quick and be run in rapid succession,
+ * to avoid adding the locking overhead to each run of the command, separate
+ * counters are kept per backend. These local per-backend counters can be
+ * updated by the owning backend through atomics without the use of locking.
+ * This requires shared memory that scales proportional to MaxBackends *
+ * NUM_LOCAL_STATS. Owing to the impact on the amount of shared memory
+ * required, this treatment is reserved only to the fastest commands which
+ * might otherwise suffer a measurable performance hit.
+ *
+ * LOCKING AND ATOMIC READS
+ * ------------------------
+ *
+ * Both the shared array of counts and the local per-backend counts are stored
+ * in shared memory. The "local per-backend" counts are equally visible to all
+ * backends. By convention, backends may read and write their own per-backend
+ * counts, but may only read counts from other backends. Note that this
+ * restriction holds even for backends holding the lwlock in exclusive mode.
+ * Even then, the backend holding the lock must not modify the counts of any
+ * other backend.
+ *
+ * Atomic reads and writes are supported on all architectures for uint32, but
+ * only on a subset of architectures for uint64. To make the implementation
+ * the same on all architectures, the backends track their counts as uint32
+ * variables. It may be tempting to upgrade these counts to uint64 if atomic
+ * support were universal, but that would double the amount of shared memory
+ * for command statistics used per backend process. It may also be tempting to
+ * reduce to a uint16 in order to reduce the per backend shared memory usage,
+ * but there is no atomics support for uint16.
+ *
+ * At any moment, the true clusterwide count per command is the sum of the
+ * shared counter for that command and all backend local counters for that
+ * command, if any. Since uint32 is small enough to overflow, we keep a shared
+ * uint64 counter per command tag for all command tags, including those which
+ * get special per-backend counters. This implementation assumes uint64 counts
+ * will not overflow during the lifetime of a running cluster.
+ *
+ * Whenever a backend's uint32 counts are about to overflow, the backend must
+ * acquire the shared CmdStatsLock exclusively, merge its own counts into the
+ * shared counts, and then reset its own counts to zero. Other backends which
+ * are simultaneously updating their own uint32 counts are not blocked by this,
+ * but if another backend were to simultaneously need to merge counts into the
+ * shared counts, it would have to wait for the lock. Since merging counts
+ * only happens once every 2^32 commands per backend, backends are not expected
+ * to block each other in this way sufficiently often to be of concern.
+ *
+ * Backends totaling up the counts to return to a caller as part of executing
+ * one of the command stats system views must acquire the CmdStatsLock and hold
+ * it while tallying the shared counts and all the backend counts together.
+ * Each concurrently running backend may update its counts while this lock is
+ * held, but no backend may merge its counts into the shared counts nor reset
+ * its counts to zero. Since the command stats system views make no
+ * transaction isolation level guarantees about the stability of the counts,
+ * this seems sufficient, so long as the atomicity of changes to the counts is
+ * carefully handled.
+ */
+typedef struct
+{
+ CmdStats *cmdstats; /* Array of cluster totals */
+ LocalCmdStats *localcmdstats; /* Array of backend totals */
+}
+#if defined(pg_attribute_aligned)
+ pg_attribute_aligned(MAXIMUM_ALIGNOF)
+#endif
+CmdStatsShmemStruct;
+
+static CmdStatsShmemStruct *CmdStatsShmem;
+
+static Size
+command_stats_struct_size(void)
+{
+ return MAXALIGN(sizeof(CmdStatsShmemStruct));
+}
+
+static Size
+shared_cmdstats_size(void)
+{
+ return MAXALIGN(sizeof(CmdStats));
+}
+
+static Size
+local_cmdstats_size(void)
+{
+ return mul_size(MAXALIGN(sizeof(LocalCmdStats)), MaxBackends);
+}
+
+/*
+ * CmdStatsShmemSize
+ * Compute space needed for cmdstats-related shared memory
+ */
+Size
+CmdStatsShmemSize(void)
+{
+ Size size;
+
+ if (!cmdstats_tracking)
+ return 0;
+ size = command_stats_struct_size(); /* The struct itself */
+ size = add_size(size, shared_cmdstats_size()); /* cmdstats */
+ size = add_size(size, local_cmdstats_size()); /* localcmdstats */
+ return size;
+}
+
+/*
+ * CmdStatsShmemInit
+ * Allocate and initialize cmdstats-related shared memory
+ */
+void
+CmdStatsShmemInit(void)
+{
+ bool found;
+ Size shmemsize = CmdStatsShmemSize();
+
+ if (shmemsize == 0)
+ {
+ CmdStatsShmem = NULL;
+ return;
+ }
+
+ CmdStatsShmem = (CmdStatsShmemStruct *)
+ ShmemInitStruct("CmdStats Data", shmemsize, &found);
+
+ if (!IsUnderPostmaster)
+ {
+ char *next; /* really a void pointer, but char for portability */
+ int bkend, idx;
+
+ /*
+ * We should only be called at postmaster startup, before any
+ * backends are looking at the CmdStatsShmem, so acquiring
+ * the lock is probably not required. There doesn't seem to be
+ * any reason to skip the lock, though, since we only have to do
+ * this once.
+ */
+ LWLockAcquire(CmdStatsLock, LW_EXCLUSIVE);
+
+ /*
+ * Each pointer field in the struct points to memory contiguous with
+ * the struct itself. Each such field is MAXALIGNED, but other than
+ * the padding that might introduce, the fields are in-order and back
+ * to back.
+ */
+ memset(CmdStatsShmem, 0, shmemsize);
+ next = (char*)CmdStatsShmem + command_stats_struct_size();
+ CmdStatsShmem->cmdstats = (CmdStats *)next;
+ next = next + shared_cmdstats_size();
+ CmdStatsShmem->localcmdstats = (LocalCmdStats *)next;
+ next = next + local_cmdstats_size();
+
+ /* Initialize all the atomic variables before anybody can use them */
+ for (bkend = 0; bkend < MaxBackends; bkend++)
+ for (idx = 0; idx < NUM_LOCAL_STATS; idx++)
+ pg_atomic_init_u32(
+ &(CmdStatsShmem->localcmdstats[bkend].cnt[idx]), 0);
+
+ /* It is now safe for other backends to get the lock */
+ LWLockRelease(CmdStatsLock);
+
+ /*
+ * For sanity, assert that we used precisely the amount of memory that
+ * we allocated. There wouldn't be much harm in allocating a bit more
+ * than necessary, but we did not do so intentionally, so it would be
+ * indicative of a memory size calculation error if things don't match.
+ */
+ Assert(next == (char *)CmdStatsShmem + shmemsize);
+
+ elog(DEBUG1, "cmdstats finished initializing shared memory of size "
+ "%llu ", (long long unsigned int) shmemsize);
+ }
+ else
+ Assert(found);
+}
+
+CmdStats *
+cmdstats_shared_tally(void)
+{
+ int backend,
+ localidx,
+ tagidx;
+ LocalCmdStats *local_stats;
+ pg_atomic_uint32 *bkend_local_array;
+ const CmdStats *shared_stats;
+ const uint64 *shared_array;
+ CmdStats *result_stats;
+ uint64 *result_array;
+ LocalStatsAccum local_accum;
+ uint64 *accum_array;
+
+
+ Assert(cmdstats_tracking);
+ Assert(CmdStatsShmem);
+
+ shared_stats = (const CmdStats *) CmdStatsShmem->cmdstats;
+ shared_array = (const uint64 *) shared_stats->cnt;
+ local_stats = (LocalCmdStats *) CmdStatsShmem->localcmdstats;
+
+ result_stats = (CmdStats *) palloc0(sizeof(CmdStats));
+ result_array = (uint64 *) result_stats->cnt;
+
+ memset(&local_accum, 0, sizeof(local_accum));
+ accum_array = (uint64*) &(local_accum.cnt[0]);
+
+ LWLockAcquire(CmdStatsLock, LW_SHARED);
+ for (backend = 0; backend < MaxBackends; backend++)
+ {
+ bkend_local_array = local_stats[backend].cnt;
+ for (localidx = 0; localidx < NUM_LOCAL_STATS; localidx++)
+ accum_array[localidx] += pg_atomic_read_u32(&(bkend_local_array[localidx]));
+ }
+ for (tagidx = 0; tagidx < NUM_CMDSTATS_COUNTS; tagidx++)
+ result_array[tagidx] += shared_array[tagidx];
+ merge_local_accum(result_stats, &local_accum);
+ LWLockRelease(CmdStatsLock);
+
+ return result_stats;
+}
+
+void
+cmdstats_rollup_localstats(LocalCmdStats *localstats)
+{
+ Assert(cmdstats_tracking);
+ Assert(CmdStatsShmem);
+
+ rollup_local_stats(CmdStatsShmem->cmdstats, localstats);
+}
+
+void
+cmdstats_increment(CommandTag commandtag)
+{
+ int local_stats_idx;
+
+ /* Should not get here unless cmdstats_tracking is true */
+ Assert(cmdstats_tracking);
+ Assert(CmdStatsShmem);
+ Assert(commandtag >= FIRST_CMDTAG && commandtag < LAST_CMDTAG);
+
+ /*
+ * If the commandtag has per-backend counters, we'll update the per-backend
+ * counter belonging to this backend, otherwise we'll update the shared
+ * counter used by all backends. Note that updating the shared counter
+ * when we could have updated the per-backend counter is not a correctness
+ * problem, only a performance problem. The per-backend counters
+ * eventually get rolled into the shared counters anyway.
+ *
+ * There is no good reason why a postmaster should ever call this function,
+ * so we complain loudly if it does. Having the postmaster update a
+ * per-backend counter would be inappropriate, but if all shared counters
+ * have been properly initialized, the postmaster could in theory safely
+ * increment the shared counter for the commandtag. But if the postmaster
+ * is calling this function it suggests that something has gone awry, and
+ * we can't really trust that the shared memory has been properly
+ * initialized yet, so a hard error seems more appropriate.
+ */
+ local_stats_idx = local_stats_offset[commandtag].offset;
+ if (local_stats_idx < 0)
+ {
+ /*
+ * There are no per-backend counters for this commandtag, so we must
+ * hold an exclusive lock while incrementing the shared counter.
+ * We don't expect this to be performed by the postmaster, but we don't
+ * really care, which is why we're not checking IsUnderPostmaster here.
+ */
+ LWLockAcquire(CmdStatsLock, LW_EXCLUSIVE);
+ CmdStatsShmem->cmdstats->cnt[commandtag]++;
+ LWLockRelease(CmdStatsLock);
+ }
+ else if (IsUnderPostmaster)
+ {
+ /*
+ * There are per-backend counters for this commandtag, so we must
+ * actually be a backend to have one assigned to us (hence the
+ * IsUnderPostmaster check), and we have exclusive ownership of the
+ * counter so we don't need to take the lock. But since we might
+ * increment our counter while others are simultaneously reading our
+ * counter, we must use atomic operations.
+ */
+ int bkend;
+ pg_atomic_uint32 *cnt;
+
+ /* Assert that we're called by a valid backend */
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ /* Assert that our argument is within bounds */
+ Assert(local_stats_idx < NUM_LOCAL_STATS);
+
+ /*
+ * MyBackendId runs from [1..MaxBackends] inclusive, but our
+ * indexes only operate from [0..MaxBackends-1]
+ */
+ bkend = MyBackendId - 1;
+
+ /* We have a local stats counter for This commandtag. */
+ cnt = &(CmdStatsShmem->localcmdstats[bkend].cnt[local_stats_idx]);
+
+ /*
+ * We need to verify that our counter will not overflow when we
+ * increment it. We don't have to worry about anybody else changing
+ * our per-backend counter, so there is no race condition between
+ * checking for overflow and later incrementing.
+ *
+ * The atomic read is overkill, since nobody else can change the cnt.
+ * In most spots we need to read and write these values atomically,
+ * so we're just following that pattern here for good measure.
+ */
+ if (pg_atomic_read_u32(cnt) >= PG_UINT32_MAX)
+ /* Harvest and zero all our backend's stats. This takes a lock */
+ cmdstats_rollup_localstats(&CmdStatsShmem->localcmdstats[bkend]);
+
+ /*
+ * Increment our count atomically. This needs to be atomic since
+ * other backends could be reading the count simultaneously.
+ */
+ pg_atomic_fetch_add_u32(cnt, 1);
+ }
+ else
+ {
+ elog(ERROR, "cmdstats_increment called by postmaster");
+ }
+}
+
+void
+reset_cmdstats(void)
+{
+ int backend,
+ localidx,
+ tagidx;
+ LocalCmdStats *local_stats;
+ pg_atomic_uint32 *bkend_local_array;
+ CmdStats *shared_stats;
+ uint64 *shared_array;
+
+ Assert(cmdstats_tracking);
+ Assert(CmdStatsShmem);
+
+ shared_stats = (CmdStats *) CmdStatsShmem->cmdstats;
+ shared_array = (uint64 *) shared_stats->cnt;
+ local_stats = (LocalCmdStats *) CmdStatsShmem->localcmdstats;
+
+ LWLockAcquire(CmdStatsLock, LW_EXCLUSIVE);
+ for (backend = 0; backend < MaxBackends; backend++)
+ {
+ bkend_local_array = local_stats[backend].cnt;
+ for (localidx = 0; localidx < NUM_LOCAL_STATS; localidx++)
+ pg_atomic_write_u32(&(bkend_local_array[localidx]), 0);
+ }
+ for (tagidx = 0; tagidx < NUM_CMDSTATS_COUNTS; tagidx++)
+ shared_array[tagidx] = 0;
+ LWLockRelease(CmdStatsLock);
+}
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 462b4d7e06..0db0943e21 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -48,6 +48,7 @@
#include "pg_trace.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
+#include "postmaster/cmdstats.h"
#include "postmaster/fork_process.h"
#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
@@ -1337,6 +1338,12 @@ pgstat_reset_shared_counters(const char *target)
msg.m_resettarget = RESET_ARCHIVER;
else if (strcmp(target, "bgwriter") == 0)
msg.m_resettarget = RESET_BGWRITER;
+ else if (strcmp(target, "cmdstats") == 0)
+ {
+ if (cmdstats_tracking)
+ reset_cmdstats();
+ return;
+ }
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d59cd..1e336cb7ce 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -27,6 +27,7 @@
#include "postmaster/autovacuum.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/bgwriter.h"
+#include "postmaster/cmdstats.h"
#include "postmaster/postmaster.h"
#include "replication/logicallauncher.h"
#include "replication/origin.h"
@@ -138,6 +139,7 @@ CreateSharedMemoryAndSemaphores(void)
size = add_size(size, ProcSignalShmemSize());
size = add_size(size, CheckpointerShmemSize());
size = add_size(size, AutoVacuumShmemSize());
+ size = add_size(size, CmdStatsShmemSize());
size = add_size(size, ReplicationSlotsShmemSize());
size = add_size(size, ReplicationOriginShmemSize());
size = add_size(size, WalSndShmemSize());
@@ -250,6 +252,7 @@ CreateSharedMemoryAndSemaphores(void)
ProcSignalShmemInit();
CheckpointerShmemInit();
AutoVacuumShmemInit();
+ CmdStatsShmemInit();
ReplicationSlotsShmemInit();
ReplicationOriginShmemInit();
WalSndShmemInit();
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index db47843229..9c76e30051 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -49,3 +49,4 @@ MultiXactTruncationLock 41
OldSnapshotTimeMapLock 42
LogicalRepWorkerLock 43
CLogTruncationLock 44
+CmdStatsLock 45
diff --git a/src/backend/tcop/cmdtag.c b/src/backend/tcop/cmdtag.c
index b9fbff612f..7d173db2f5 100644
--- a/src/backend/tcop/cmdtag.c
+++ b/src/backend/tcop/cmdtag.c
@@ -23,10 +23,11 @@ typedef struct CommandTagBehavior
const bool event_trigger_ok;
const bool table_rewrite_ok;
const bool display_rowcount;
+ const int local_stats_idx;
} CommandTagBehavior;
-#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt) \
- { name, evtrgok, rwrok, rowcnt },
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, localstats) \
+ { name, evtrgok, rwrok, rowcnt, localstats },
const CommandTagBehavior tag_behavior[COMMAND_TAG_NEXTTAG] = {
#include "tcop/cmdtaglist.h"
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index 7208751ec7..abace92b96 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -39,6 +39,7 @@
#include "executor/tstoreReceiver.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
+#include "postmaster/cmdstats.h"
#include "utils/portal.h"
@@ -169,6 +170,9 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o
CommandTag tag;
const char *tagname;
+ if (cmdstats_tracking)
+ cmdstats_increment(qc->commandTag);
+
switch (dest)
{
case DestRemote:
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 54d2673254..0331c9e7aa 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -23,6 +23,7 @@
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/bgworker_internals.h"
+#include "postmaster/cmdstats.h"
#include "postmaster/postmaster.h"
#include "storage/proc.h"
#include "storage/procarray.h"
@@ -1984,3 +1985,64 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS)
/* Returns the record as Datum */
PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
}
+
+Datum
+pg_command_stats_data(PG_FUNCTION_ARGS)
+{
+ TupleDesc tupdesc;
+ Datum values[2] = { 0, 0 };
+ bool nulls[2] = { false, false };
+ ReturnSetInfo *rsi;
+ MemoryContext old_ctx;
+ Tuplestorestate *tuple_store;
+ CommandTag tag;
+
+ rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* Check to see if caller supports us returning a tuplestore */
+ if (rsi == NULL || !IsA(rsi, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsi->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not "
+ "allowed in this context")));
+
+ rsi->returnMode = SFRM_Materialize;
+
+ /* 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");
+
+ /* Build tuplestore to hold the result rows */
+ old_ctx = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+ tuple_store =
+ tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ false, work_mem);
+ rsi->setDesc = tupdesc;
+ rsi->setResult = tuple_store;
+
+ MemoryContextSwitchTo(old_ctx);
+
+ if (cmdstats_tracking)
+ {
+ CmdStats *tally = cmdstats_shared_tally();
+ for (tag = FIRST_CMDTAG; tally != NULL && tag <= LAST_CMDTAG; tag++)
+ {
+ HeapTuple tuple;
+
+ if (tally->cnt[tag] == 0)
+ continue;
+
+ values[0] = CStringGetTextDatum(GetCommandTagName(tag));
+ values[1] = Int64GetDatum(tally->cnt[tag]);
+
+ tuple = heap_form_tuple(tupdesc, values, nulls);
+ tuplestore_puttuple(tuple_store, tuple);
+ }
+ }
+ PG_RETURN_NULL();
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index dbecc00fef..897e5e757f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -62,6 +62,7 @@
#include "postmaster/autovacuum.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/bgwriter.h"
+#include "postmaster/cmdstats.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "postmaster/walwriter.h"
@@ -1483,6 +1484,18 @@ static struct config_bool ConfigureNamesBool[] =
false,
NULL, NULL, NULL
},
+ {
+ {"cmdstats_tracking", PGC_POSTMASTER, STATS_COLLECTOR,
+ gettext_noop("Collects statistics of commands by type."),
+ gettext_noop("For each type of command (INSERT, UPDATE, DELETE, "
+ "CREATE TABLE, etc), tracks statistics about the "
+ "commands of that type which have been run."),
+ GUC_SUPERUSER_ONLY
+ },
+ &cmdstats_tracking,
+ false,
+ NULL, NULL, NULL
+ },
{
{"update_process_title", PGC_SUSET, PROCESS_TITLE,
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 07a86c7b7b..91f1f45b0b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5379,6 +5379,12 @@
proargmodes => '{o,o,o,o,o,o,o}',
proargnames => '{archived_count,last_archived_wal,last_archived_time,failed_count,last_failed_wal,last_failed_time,stats_reset}',
prosrc => 'pg_stat_get_archiver' },
+{ oid => '8450', descr => 'statistics: information about sql commands',
+ proname => 'pg_command_stats_data', prorows => '100', proretset => 't',
+ proisstrict => 'f', provolatile => 'v', proparallel => 'r',
+ prorettype => 'record', proargtypes => '', proallargtypes => '{text,int8}',
+ proargmodes => '{o,o}', proargnames => '{tag,count}',
+ prosrc => 'pg_command_stats_data' },
{ oid => '2769',
descr => 'statistics: number of timed checkpoints started by the bgwriter',
proname => 'pg_stat_get_bgwriter_timed_checkpoints', provolatile => 's',
diff --git a/src/include/postmaster/cmdstats.h b/src/include/postmaster/cmdstats.h
new file mode 100644
index 0000000000..c1a08dcb0c
--- /dev/null
+++ b/src/include/postmaster/cmdstats.h
@@ -0,0 +1,68 @@
+/* ----------
+ * pgstat.h
+ *
+ * Declarations for PostgreSQL command statistics
+ *
+ * Copyright (c) 2001-2020, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/cmdstats.h
+ * ----------
+ */
+#ifndef CMDSTATS_H
+#define CMDSTATS_H
+
+#include "postgres.h"
+
+#include "tcop/cmdtag.h"
+
+extern PGDLLIMPORT bool cmdstats_tracking;
+
+/*-------------
+ * Command Stats shmem structs.
+ *-------------
+ */
+/*
+ * For each command tag, we track how many commands completed
+ * for that type.
+ */
+#define NUM_CMDSTATS_COUNTS ((uint32)NUM_CMDTAGS)
+
+/*
+ * CmdStats is used for holding the shared counts per CommandTag for all tags.
+ */
+typedef struct CmdStats {
+ uint64 cnt[NUM_CMDSTATS_COUNTS];
+} CmdStats;
+
+/*
+ * LocalCmdStats is used for holding the per-backend local counts for just
+ * the subset of CommandTags which have per-backend counts. There are
+ * NUM_LOCAL_STATS < NUM_CMDTAGS number of those. Indexing into this
+ * structure for a given CommandTag requires looking up the appropriate
+ * offset. See local_stats_offset for details.
+ */
+typedef struct LocalCmdStats {
+ pg_atomic_uint32 cnt[NUM_LOCAL_STATS];
+} LocalCmdStats;
+
+/*
+ * LocalStatsAccum is used for totalling up the per-backend counts. We cannot
+ * simply use a LocalCmdStats struct for that, as the totals across backends
+ * may overflow a uint32. Thus, we have only NUM_LOCAL_STATS counts, but use a
+ * uint64 per count.
+ */
+typedef struct LocalStatsAccum {
+ uint64 cnt[NUM_LOCAL_STATS];
+} LocalStatsAccum;
+
+/* shared memory stuff */
+extern int NumCmdStats(void);
+extern Size CmdStatsPerBackendShmemSize(void);
+extern Size CmdStatsShmemSize(void);
+extern void CmdStatsShmemInit(void);
+extern CmdStats *cmdstats_shared_tally(void);
+extern void cmdstats_rollup_localstats(LocalCmdStats *localstats);
+extern void cmdstats_increment(CommandTag commandtag);
+extern void reset_cmdstats(void);
+
+#endif /* CMDSTATS_H */
diff --git a/src/include/tcop/cmdtag.h b/src/include/tcop/cmdtag.h
index f75a91e7c8..5795db9ba3 100644
--- a/src/include/tcop/cmdtag.h
+++ b/src/include/tcop/cmdtag.h
@@ -14,7 +14,7 @@
#define CMDTAG_H
-#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt) \
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, localstats) \
tag,
typedef enum CommandTag
@@ -23,6 +23,10 @@ typedef enum CommandTag
COMMAND_TAG_NEXTTAG
} CommandTag;
+#define FIRST_CMDTAG ((CommandTag)0)
+#define LAST_CMDTAG ((CommandTag)(COMMAND_TAG_NEXTTAG - 1))
+#define NUM_CMDTAGS ((int)COMMAND_TAG_NEXTTAG)
+
#undef PG_CMDTAG
typedef struct QueryCompletion
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d28145a50d..67016cdf95 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -23,196 +23,201 @@
* textual name, so that we can bsearch on it; see GetCommandTagEnum().
*/
-/* symbol name, textual name, event_trigger_ok, table_rewrite_ok, rowcount, last_oid */
-PG_CMDTAG(CMDTAG_UNKNOWN, "???", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ACCESS_METHOD, "ALTER ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_AGGREGATE, "ALTER AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CAST, "ALTER CAST", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_COLLATION, "ALTER COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CONVERSION, "ALTER CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DATABASE, "ALTER DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DEFAULT_PRIVILEGES, "ALTER DEFAULT PRIVILEGES", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DOMAIN, "ALTER DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_EVENT_TRIGGER, "ALTER EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_EXTENSION, "ALTER EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FOREIGN_DATA_WRAPPER, "ALTER FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FOREIGN_TABLE, "ALTER FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FUNCTION, "ALTER FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_INDEX, "ALTER INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_LANGUAGE, "ALTER LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_LARGE_OBJECT, "ALTER LARGE OBJECT", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_MATERIALIZED_VIEW, "ALTER MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR, "ALTER OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_POLICY, "ALTER POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ROLE, "ALTER ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ROUTINE, "ALTER ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_RULE, "ALTER RULE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SCHEMA, "ALTER SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SEQUENCE, "ALTER SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SERVER, "ALTER SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_STATISTICS, "ALTER STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SUBSCRIPTION, "ALTER SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SYSTEM, "ALTER SYSTEM", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TABLE, "ALTER TABLE", true, true, false)
-PG_CMDTAG(CMDTAG_ALTER_TABLESPACE, "ALTER TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION, "ALTER TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY, "ALTER TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_PARSER, "ALTER TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_TEMPLATE, "ALTER TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false)
-PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false)
-PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false)
-PG_CMDTAG(CMDTAG_CALL, "CALL", false, false, false)
-PG_CMDTAG(CMDTAG_CHECKPOINT, "CHECKPOINT", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE, "CLOSE", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE_CURSOR, "CLOSE CURSOR", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE_CURSOR_ALL, "CLOSE CURSOR ALL", false, false, false)
-PG_CMDTAG(CMDTAG_CLUSTER, "CLUSTER", false, false, false)
-PG_CMDTAG(CMDTAG_COMMENT, "COMMENT", true, false, false)
-PG_CMDTAG(CMDTAG_COMMIT, "COMMIT", false, false, false)
-PG_CMDTAG(CMDTAG_COMMIT_PREPARED, "COMMIT PREPARED", false, false, false)
-PG_CMDTAG(CMDTAG_COPY, "COPY", false, false, true)
-PG_CMDTAG(CMDTAG_COPY_FROM, "COPY FROM", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_DOMAIN, "CREATE DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_EVENT_TRIGGER, "CREATE EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_EXTENSION, "CREATE EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FOREIGN_DATA_WRAPPER, "CREATE FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FOREIGN_TABLE, "CREATE FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FUNCTION, "CREATE FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_INDEX, "CREATE INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_LANGUAGE, "CREATE LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_MATERIALIZED_VIEW, "CREATE MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR, "CREATE OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_POLICY, "CREATE POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ROLE, "CREATE ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ROUTINE, "CREATE ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_RULE, "CREATE RULE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SCHEMA, "CREATE SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SEQUENCE, "CREATE SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SERVER, "CREATE SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_STATISTICS, "CREATE STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SUBSCRIPTION, "CREATE SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLE, "CREATE TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLE_AS, "CREATE TABLE AS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLESPACE, "CREATE TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_CONFIGURATION, "CREATE TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_DICTIONARY, "CREATE TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_PARSER, "CREATE TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_TEMPLATE, "CREATE TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false)
-PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false)
-PG_CMDTAG(CMDTAG_DECLARE_CURSOR, "DECLARE CURSOR", false, false, false)
-PG_CMDTAG(CMDTAG_DELETE, "DELETE", false, false, true)
-PG_CMDTAG(CMDTAG_DISCARD, "DISCARD", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false)
-PG_CMDTAG(CMDTAG_DO, "DO", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_DOMAIN, "DROP DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_EVENT_TRIGGER, "DROP EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_EXTENSION, "DROP EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FOREIGN_DATA_WRAPPER, "DROP FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FOREIGN_TABLE, "DROP FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FUNCTION, "DROP FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_INDEX, "DROP INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_LANGUAGE, "DROP LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_MATERIALIZED_VIEW, "DROP MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR, "DROP OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR_CLASS, "DROP OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_REPLICATION_SLOT, "DROP REPLICATION SLOT", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ROLE, "DROP ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ROUTINE, "DROP ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_RULE, "DROP RULE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SCHEMA, "DROP SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SEQUENCE, "DROP SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SERVER, "DROP SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_STATISTICS, "DROP STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SUBSCRIPTION, "DROP SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TABLE, "DROP TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TABLESPACE, "DROP TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_CONFIGURATION, "DROP TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_DICTIONARY, "DROP TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_PARSER, "DROP TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_TEMPLATE, "DROP TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false)
-PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false)
-PG_CMDTAG(CMDTAG_FETCH, "FETCH", false, false, true)
-PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false)
-PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true)
-PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false)
-PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false)
-PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false)
-PG_CMDTAG(CMDTAG_MOVE, "MOVE", false, false, true)
-PG_CMDTAG(CMDTAG_NOTIFY, "NOTIFY", false, false, false)
-PG_CMDTAG(CMDTAG_PREPARE, "PREPARE", false, false, false)
-PG_CMDTAG(CMDTAG_PREPARE_TRANSACTION, "PREPARE TRANSACTION", false, false, false)
-PG_CMDTAG(CMDTAG_REASSIGN_OWNED, "REASSIGN OWNED", false, false, false)
-PG_CMDTAG(CMDTAG_REFRESH_MATERIALIZED_VIEW, "REFRESH MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_REINDEX, "REINDEX", false, false, false)
-PG_CMDTAG(CMDTAG_RELEASE, "RELEASE", false, false, false)
-PG_CMDTAG(CMDTAG_RESET, "RESET", false, false, false)
-PG_CMDTAG(CMDTAG_REVOKE, "REVOKE", true, false, false)
-PG_CMDTAG(CMDTAG_REVOKE_ROLE, "REVOKE ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_ROLLBACK, "ROLLBACK", false, false, false)
-PG_CMDTAG(CMDTAG_ROLLBACK_PREPARED, "ROLLBACK PREPARED", false, false, false)
-PG_CMDTAG(CMDTAG_SAVEPOINT, "SAVEPOINT", false, false, false)
-PG_CMDTAG(CMDTAG_SECURITY_LABEL, "SECURITY LABEL", true, false, false)
-PG_CMDTAG(CMDTAG_SELECT, "SELECT", false, false, true)
-PG_CMDTAG(CMDTAG_SELECT_FOR_KEY_SHARE, "SELECT FOR KEY SHARE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_NO_KEY_UPDATE, "SELECT FOR NO KEY UPDATE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_SHARE, "SELECT FOR SHARE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_UPDATE, "SELECT FOR UPDATE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_INTO, "SELECT INTO", true, false, false)
-PG_CMDTAG(CMDTAG_SET, "SET", false, false, false)
-PG_CMDTAG(CMDTAG_SET_CONSTRAINTS, "SET CONSTRAINTS", false, false, false)
-PG_CMDTAG(CMDTAG_SHOW, "SHOW", false, false, false)
-PG_CMDTAG(CMDTAG_START_TRANSACTION, "START TRANSACTION", false, false, false)
-PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
-PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
-PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
-PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+/* symbol name, textual name, event_trigger_ok, table_rewrite_ok, rowcount, localstats */
+PG_CMDTAG(CMDTAG_UNKNOWN, "???", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_ACCESS_METHOD, "ALTER ACCESS METHOD", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_AGGREGATE, "ALTER AGGREGATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_CAST, "ALTER CAST", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_COLLATION, "ALTER COLLATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_CONVERSION, "ALTER CONVERSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_DATABASE, "ALTER DATABASE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_DEFAULT_PRIVILEGES, "ALTER DEFAULT PRIVILEGES", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_DOMAIN, "ALTER DOMAIN", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_EVENT_TRIGGER, "ALTER EVENT TRIGGER", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_EXTENSION, "ALTER EXTENSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_FOREIGN_DATA_WRAPPER, "ALTER FOREIGN DATA WRAPPER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_FOREIGN_TABLE, "ALTER FOREIGN TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_FUNCTION, "ALTER FUNCTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_INDEX, "ALTER INDEX", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_LANGUAGE, "ALTER LANGUAGE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_LARGE_OBJECT, "ALTER LARGE OBJECT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_MATERIALIZED_VIEW, "ALTER MATERIALIZED VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR, "ALTER OPERATOR", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_POLICY, "ALTER POLICY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_ROLE, "ALTER ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_ROUTINE, "ALTER ROUTINE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_RULE, "ALTER RULE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SCHEMA, "ALTER SCHEMA", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SEQUENCE, "ALTER SEQUENCE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SERVER, "ALTER SERVER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_STATISTICS, "ALTER STATISTICS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SUBSCRIPTION, "ALTER SUBSCRIPTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SYSTEM, "ALTER SYSTEM", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TABLE, "ALTER TABLE", true, true, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TABLESPACE, "ALTER TABLESPACE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION, "ALTER TEXT SEARCH CONFIGURATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY, "ALTER TEXT SEARCH DICTIONARY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_PARSER, "ALTER TEXT SEARCH PARSER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_TEMPLATE, "ALTER TEXT SEARCH TEMPLATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false, 0)
+PG_CMDTAG(CMDTAG_CALL, "CALL", false, false, false, 1)
+PG_CMDTAG(CMDTAG_CHECKPOINT, "CHECKPOINT", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CLOSE, "CLOSE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CLOSE_CURSOR, "CLOSE CURSOR", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CLOSE_CURSOR_ALL, "CLOSE CURSOR ALL", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CLUSTER, "CLUSTER", false, false, false, -1)
+PG_CMDTAG(CMDTAG_COMMENT, "COMMENT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_COMMIT, "COMMIT", false, false, false, 2)
+PG_CMDTAG(CMDTAG_COMMIT_PREPARED, "COMMIT PREPARED", false, false, false, 3)
+PG_CMDTAG(CMDTAG_COPY, "COPY", false, false, true, -1)
+PG_CMDTAG(CMDTAG_COPY_FROM, "COPY FROM", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_DOMAIN, "CREATE DOMAIN", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_EVENT_TRIGGER, "CREATE EVENT TRIGGER", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_EXTENSION, "CREATE EXTENSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_FOREIGN_DATA_WRAPPER, "CREATE FOREIGN DATA WRAPPER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_FOREIGN_TABLE, "CREATE FOREIGN TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_FUNCTION, "CREATE FUNCTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_INDEX, "CREATE INDEX", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_LANGUAGE, "CREATE LANGUAGE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_MATERIALIZED_VIEW, "CREATE MATERIALIZED VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR, "CREATE OPERATOR", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_POLICY, "CREATE POLICY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_ROLE, "CREATE ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_ROUTINE, "CREATE ROUTINE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_RULE, "CREATE RULE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_SCHEMA, "CREATE SCHEMA", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_SEQUENCE, "CREATE SEQUENCE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_SERVER, "CREATE SERVER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_STATISTICS, "CREATE STATISTICS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_SUBSCRIPTION, "CREATE SUBSCRIPTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TABLE, "CREATE TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TABLE_AS, "CREATE TABLE AS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TABLESPACE, "CREATE TABLESPACE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_CONFIGURATION, "CREATE TEXT SEARCH CONFIGURATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_DICTIONARY, "CREATE TEXT SEARCH DICTIONARY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_PARSER, "CREATE TEXT SEARCH PARSER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_TEMPLATE, "CREATE TEXT SEARCH TEMPLATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DECLARE_CURSOR, "DECLARE CURSOR", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DELETE, "DELETE", false, false, true, 4)
+PG_CMDTAG(CMDTAG_DISCARD, "DISCARD", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DO, "DO", false, false, false, 5)
+PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_DOMAIN, "DROP DOMAIN", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_EVENT_TRIGGER, "DROP EVENT TRIGGER", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_EXTENSION, "DROP EXTENSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_FOREIGN_DATA_WRAPPER, "DROP FOREIGN DATA WRAPPER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_FOREIGN_TABLE, "DROP FOREIGN TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_FUNCTION, "DROP FUNCTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_INDEX, "DROP INDEX", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_LANGUAGE, "DROP LANGUAGE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_MATERIALIZED_VIEW, "DROP MATERIALIZED VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR, "DROP OPERATOR", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR_CLASS, "DROP OPERATOR CLASS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_REPLICATION_SLOT, "DROP REPLICATION SLOT", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_ROLE, "DROP ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_ROUTINE, "DROP ROUTINE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_RULE, "DROP RULE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_SCHEMA, "DROP SCHEMA", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_SEQUENCE, "DROP SEQUENCE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_SERVER, "DROP SERVER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_STATISTICS, "DROP STATISTICS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_SUBSCRIPTION, "DROP SUBSCRIPTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TABLE, "DROP TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TABLESPACE, "DROP TABLESPACE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_CONFIGURATION, "DROP TEXT SEARCH CONFIGURATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_DICTIONARY, "DROP TEXT SEARCH DICTIONARY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_PARSER, "DROP TEXT SEARCH PARSER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_TEMPLATE, "DROP TEXT SEARCH TEMPLATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false, 6)
+PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false, -1)
+PG_CMDTAG(CMDTAG_FETCH, "FETCH", false, false, true, 7)
+PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false, -1)
+PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true, 8)
+PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false, 9)
+PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false, -1)
+PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_MOVE, "MOVE", false, false, true, 10)
+PG_CMDTAG(CMDTAG_NOTIFY, "NOTIFY", false, false, false, 11)
+PG_CMDTAG(CMDTAG_PREPARE, "PREPARE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_PREPARE_TRANSACTION, "PREPARE TRANSACTION", false, false, false, -1)
+PG_CMDTAG(CMDTAG_REASSIGN_OWNED, "REASSIGN OWNED", false, false, false, -1)
+PG_CMDTAG(CMDTAG_REFRESH_MATERIALIZED_VIEW, "REFRESH MATERIALIZED VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_REINDEX, "REINDEX", false, false, false, -1)
+PG_CMDTAG(CMDTAG_RELEASE, "RELEASE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_RESET, "RESET", false, false, false, 12)
+PG_CMDTAG(CMDTAG_REVOKE, "REVOKE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_REVOKE_ROLE, "REVOKE ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ROLLBACK, "ROLLBACK", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ROLLBACK_PREPARED, "ROLLBACK PREPARED", false, false, false, -1)
+PG_CMDTAG(CMDTAG_SAVEPOINT, "SAVEPOINT", false, false, false, -1)
+PG_CMDTAG(CMDTAG_SECURITY_LABEL, "SECURITY LABEL", true, false, false, -1)
+PG_CMDTAG(CMDTAG_SELECT, "SELECT", false, false, true, 13)
+PG_CMDTAG(CMDTAG_SELECT_FOR_KEY_SHARE, "SELECT FOR KEY SHARE", false, false, false, 14)
+PG_CMDTAG(CMDTAG_SELECT_FOR_NO_KEY_UPDATE, "SELECT FOR NO KEY UPDATE", false, false, false, 15)
+PG_CMDTAG(CMDTAG_SELECT_FOR_SHARE, "SELECT FOR SHARE", false, false, false, 16)
+PG_CMDTAG(CMDTAG_SELECT_FOR_UPDATE, "SELECT FOR UPDATE", false, false, false, 17)
+PG_CMDTAG(CMDTAG_SELECT_INTO, "SELECT INTO", true, false, false, -1)
+PG_CMDTAG(CMDTAG_SET, "SET", false, false, false, 18)
+PG_CMDTAG(CMDTAG_SET_CONSTRAINTS, "SET CONSTRAINTS", false, false, false, -1)
+PG_CMDTAG(CMDTAG_SHOW, "SHOW", false, false, false, 19)
+PG_CMDTAG(CMDTAG_START_TRANSACTION, "START TRANSACTION", false, false, false, -1)
+PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false, 20)
+PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true, 21)
+PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false, -1)
+
+/* Keep this one more than the highest localstats value above */
+#ifndef NUM_LOCAL_STATS
+#define NUM_LOCAL_STATS 22
+#endif
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..f4ba52f617 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -12,7 +12,7 @@ subdir = src/test
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
-SUBDIRS = perl regress isolation modules authentication recovery subscription
+SUBDIRS = perl regress isolation modules authentication recovery cmdstats subscription
# Test suites that are not safe by default but can be run if selected
# by the user via the whitespace-separated list in variable
diff --git a/src/test/cmdstats/Makefile b/src/test/cmdstats/Makefile
new file mode 100644
index 0000000000..5464c433f4
--- /dev/null
+++ b/src/test/cmdstats/Makefile
@@ -0,0 +1,25 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/cmdstats
+#
+# Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/cmdstats/Makefile
+#
+#-------------------------------------------------------------------------
+
+EXTRA_INSTALL=contrib/test_decoding
+
+subdir = src/test/cmdstats
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
+
+clean distclean maintainer-clean:
+ rm -rf tmp_check
diff --git a/src/test/cmdstats/README b/src/test/cmdstats/README
new file mode 100644
index 0000000000..7219c4fd07
--- /dev/null
+++ b/src/test/cmdstats/README
@@ -0,0 +1,23 @@
+src/test/cmdstats/README
+
+Regression tests for cmdstats statistics subsystem
+=============================================
+
+This directory contains a test suite for per command statistics.
+
+Running the tests
+=================
+
+NOTE: You must have given the --enable-tap-tests argument to configure.
+
+Run
+ make check
+or
+ make installcheck
+You can use "make installcheck" if you previously did "make install".
+In that case, the code in the installation tree is tested. With
+"make check", a temporary installation tree is built from the current
+sources and then tested.
+
+Either way, this test initializes, starts, and stops several test Postgres
+clusters.
diff --git a/src/test/cmdstats/t/001_guc_settings.pl b/src/test/cmdstats/t/001_guc_settings.pl
new file mode 100644
index 0000000000..e2cdfa4a25
--- /dev/null
+++ b/src/test/cmdstats/t/001_guc_settings.pl
@@ -0,0 +1,31 @@
+# Minimal test of cmdstats GUC default settings
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 3;
+
+my ($node, $result);
+
+$node = get_new_node("cmdstats_guc_test");
+$node->init;
+$node->start;
+
+# Check that by default cmdstats_tracking is off
+$result = $node->safe_psql('postgres', "SHOW cmdstats_tracking");
+is($result, 'off', 'check default setting of cmdstats_tracking');
+
+# Verify that a mere reload cannot change cmdstats_tracking
+$node->append_conf('postgresql.conf', "cmdstats_tracking = true");
+$node->reload;
+$result = $node->safe_psql('postgres', "SHOW cmdstats_tracking");
+is($result, 'off', 'check default setting of cmdstats_tracking');
+
+# Verify that a restart does change cmdstats_tracking
+$node->restart;
+$result = $node->safe_psql('postgres', "SHOW cmdstats_tracking");
+is($result, 'on', 'check default setting of cmdstats_tracking');
+
+$node->stop;
+$node->teardown_node;
+$node->clean_node;
diff --git a/src/test/cmdstats/t/002_stats.pl b/src/test/cmdstats/t/002_stats.pl
new file mode 100644
index 0000000000..8366399e83
--- /dev/null
+++ b/src/test/cmdstats/t/002_stats.pl
@@ -0,0 +1,53 @@
+# Test of cmdstats system view
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 10;
+
+my ($node, $result);
+
+$node = get_new_node("cmdstats_test");
+$node->init;
+$node->append_conf('postgresql.conf', "cmdstats_tracking = true");
+$node->start;
+
+# Reset the stats. Beware that this in itself is a SELECT statement, and it gets counted *after* the reset.
+$result = $node->safe_psql('postgres', "SELECT pg_stat_reset_shared('cmdstats')");
+
+# Check that we see the one select from above. Note that this, too, is a SELECT that gets counted *after* it runs
+$result = $node->safe_psql('postgres', "SELECT tag || ':' || cnt::text FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, 'SELECT:1', 'have run one SELECT statement');
+
+# Check that we see both selects from above
+$result = $node->safe_psql('postgres', "SELECT tag || ':' || cnt::text FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, 'SELECT:2', 'have run two SELECT statements');
+
+# Check that we don't see any other kinds of statements
+$result = $node->safe_psql('postgres', "SELECT COUNT(DISTINCT tag) FROM pg_command_stats");
+is($result, '1', 'have run only kind of statement');
+
+# Run a CREATE TABLE statement
+$result = $node->safe_psql('postgres', "CREATE TABLE tst (i INTEGER)");
+
+# Check that we now see both SELECT and CREATE TABLE
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, '4', 'have run four SELECT statements');
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'CREATE TABLE'");
+is($result, '1', 'have run one CREATE TABLE statement');
+$result = $node->safe_psql('postgres', "SELECT COUNT(DISTINCT tag) FROM pg_command_stats");
+is($result, '2', 'have run two kinds of statements');
+
+# Reset all counters. Note that the SELECT which resets the counters will itself get counted
+$node->safe_psql('postgres', "SELECT * FROM pg_stat_reset_shared('cmdstats')");
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, '1', 'have run one SELECT statement since resetting the counters');
+
+# Check that multi-statement commands are all counted, not just one of them
+$node->safe_psql('postgres', "BEGIN; SELECT 1; ROLLBACK; BEGIN; SELECT 2; ROLLBACK; BEGIN; SELECT 3; ROLLBACK");
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, '5', 'have run five SELECT statements since resetting the counters');
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'BEGIN'");
+is($result, '3', 'have run three BEGIN statements');
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'ROLLBACK'");
+is($result, '3', 'have run three ROLLBACK statements');
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index c7304611c3..5665981dca 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1324,6 +1324,9 @@ pg_available_extensions| SELECT e.name,
e.comment
FROM (pg_available_extensions() e(name, default_version, comment)
LEFT JOIN pg_extension x ON ((e.name = x.extname)));
+pg_command_stats| SELECT s.tag,
+ s.cnt
+ FROM pg_command_stats_data() s(tag, cnt);
pg_config| SELECT pg_config.name,
pg_config.setting
FROM pg_config() pg_config(name, setting);
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index b01e58b98c..4b873eedc4 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -201,4 +201,25 @@ FROM prevstats AS pr;
DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
DROP TABLE prevstats;
+-- Minimal test of pg_command_stats. Since cmdstats_tracking defaults to
+-- off, and cannot be changed without a cluster restart, there is nothing we can
+-- do to interrogate its behavior here. But we can at least verify that it
+-- exists, has the right columns, and does nothing unexpected when we attempt to
+-- reset its counts
+SELECT * FROM pg_command_stats;
+ tag | cnt
+-----+-----
+(0 rows)
+
+SELECT * FROM pg_stat_reset_shared('cmdstats');
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+SELECT * FROM pg_command_stats;
+ tag | cnt
+-----+-----
+(0 rows)
+
-- End of Stats Test
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index feaaee6326..90ad262021 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -176,4 +176,14 @@ FROM prevstats AS pr;
DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
DROP TABLE prevstats;
+
+-- Minimal test of pg_command_stats. Since cmdstats_tracking defaults to
+-- off, and cannot be changed without a cluster restart, there is nothing we can
+-- do to interrogate its behavior here. But we can at least verify that it
+-- exists, has the right columns, and does nothing unexpected when we attempt to
+-- reset its counts
+
+SELECT * FROM pg_command_stats;
+SELECT * FROM pg_stat_reset_shared('cmdstats');
+SELECT * FROM pg_command_stats;
-- End of Stats Test
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e216de9570..2ef3ea681c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -342,6 +342,7 @@ Clump
ClusterInfo
ClusterStmt
CmdType
+CmdStatsShmemStruct
CoalesceExpr
CoerceParamHook
CoerceToDomain
--
2.21.1 (Apple Git-122.3)
Show quoted text
On Jun 2, 2020, at 9:58 AM, Mark Dilger <mark.dilger@enterprisedb.com> wrote:
On Mar 4, 2020, at 7:43 PM, Mark Dilger <mark.dilger@enterprisedb.com> wrote:
Hackers,
as mentioned in [1], I have created an implementation of command counter statistics very similar in purpose to the one already pending in the commitfest going by the name "pg_stat_sql". I don't really care if this implementation is seen as building on that one or as separate, but I was worried about hijacking that thread, so I'm starting this thead. There are elements of this patch that borrowed from that one, so if this is accepted, authorship should reflect that.
See the work by Haribabu Kommi (haribabu) at https://commitfest.postgresql.org/27/790/
The two main differences are that
(1) This implementation is based on commandtags as enums, not strings and
(2) This implementation uses techniques to reduce lock contentionI think (2) is the more important part.
[1] /messages/by-id/28F46159-3764-421B-904B-004DEA339310@enterprisedb.com
<v1-0001-Implementing-the-cmdstats-subsystem.patch>
—Rebased patch against master caa3c4242cf86322e2ed0c86199e6462a2c41565.
Attachments:
v2-0001-Implementing-the-cmdstats-subsystem.patchapplication/octet-stream; name=v2-0001-Implementing-the-cmdstats-subsystem.patch; x-unix-mode=0644Download
From e9a216ee26fc3fc8f4b7228c334b9ec0d2890720 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Tue, 2 Jun 2020 09:15:17 -0700
Subject: [PATCH v2] Implementing the cmdstats subsystem.
This implements a subsystem for tracking the number of times a type
of command has been run in a database cluster, since startup or
since the last time the counts were reset, whichever is newer.
This work is based on and borrows from work by Haribabu Kommi
(haribabu) as reflected in the patch submission:
SQL statements statistics counter view (pg_stat_sql)
which itself was reviewed by dilip kumar (dilip.kumar), Vinayak
Pokale (vinpokale) and probably others.
---
src/backend/catalog/system_views.sql | 7 +
src/backend/postmaster/Makefile | 1 +
src/backend/postmaster/cmdstats.c | 447 +++++++++++++++++++++++
src/backend/postmaster/pgstat.c | 7 +
src/backend/storage/ipc/ipci.c | 3 +
src/backend/storage/lmgr/lwlocknames.txt | 1 +
src/backend/tcop/cmdtag.c | 5 +-
src/backend/tcop/dest.c | 4 +
src/backend/utils/adt/pgstatfuncs.c | 62 ++++
src/backend/utils/misc/guc.c | 13 +
src/include/catalog/pg_proc.dat | 6 +
src/include/postmaster/cmdstats.h | 68 ++++
src/include/tcop/cmdtag.h | 6 +-
src/include/tcop/cmdtaglist.h | 391 ++++++++++----------
src/test/Makefile | 2 +-
src/test/cmdstats/Makefile | 25 ++
src/test/cmdstats/README | 23 ++
src/test/cmdstats/t/001_guc_settings.pl | 31 ++
src/test/cmdstats/t/002_stats.pl | 53 +++
src/test/regress/expected/rules.out | 3 +
src/test/regress/expected/stats.out | 21 ++
src/test/regress/sql/stats.sql | 10 +
src/tools/pgindent/typedefs.list | 1 +
23 files changed, 993 insertions(+), 197 deletions(-)
create mode 100644 src/backend/postmaster/cmdstats.c
create mode 100644 src/include/postmaster/cmdstats.h
create mode 100644 src/test/cmdstats/Makefile
create mode 100644 src/test/cmdstats/README
create mode 100644 src/test/cmdstats/t/001_guc_settings.pl
create mode 100644 src/test/cmdstats/t/002_stats.pl
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 56420bbc9d..c84dde5ac0 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -290,6 +290,13 @@ CREATE VIEW pg_stats_ext WITH (security_barrier) AS
WHERE NOT has_column_privilege(c.oid, a.attnum, 'select') )
AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+CREATE VIEW pg_command_stats AS
+ SELECT s.tag, s.cnt
+ FROM pg_command_stats_data() AS s(tag, cnt);
+
+REVOKE EXECUTE ON FUNCTION pg_command_stats_data() FROM PUBLIC;
+REVOKE ALL ON pg_command_stats FROM PUBLIC;
+
-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
REVOKE ALL on pg_statistic_ext_data FROM public;
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index bfdf6a833d..d23580a457 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -17,6 +17,7 @@ OBJS = \
bgworker.o \
bgwriter.o \
checkpointer.o \
+ cmdstats.o \
fork_process.o \
interrupt.o \
pgarch.o \
diff --git a/src/backend/postmaster/cmdstats.c b/src/backend/postmaster/cmdstats.c
new file mode 100644
index 0000000000..a16d1e9d0c
--- /dev/null
+++ b/src/backend/postmaster/cmdstats.c
@@ -0,0 +1,447 @@
+/* ----------
+ * cmdstats.c
+ *
+ * Copyright (c) 2001-2020, PostgreSQL Global Development Group
+ *
+ * src/backend/postmaster/cmdstats.c
+ * ----------
+ */
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "commands/dbcommands.h"
+#include "miscadmin.h"
+#include "port/atomics.h"
+#include "postmaster/cmdstats.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "utils/acl.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
+
+
+/*
+ * Create a mapping from a CommandTag to its offset in the per-backend stats
+ * (if any), else defaulting to -1. There will be an entry in this array for
+ * every CommandTag.
+ */
+typedef struct LocalStatsOffset {
+ int offset;
+} LocalStatsOffset;
+
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, localstats) \
+ [tag] = { .offset = localstats },
+
+const LocalStatsOffset local_stats_offset[] = {
+#include "tcop/cmdtaglist.h"
+ [COMMAND_TAG_NEXTTAG] = { .offset = -1 }
+};
+
+#undef PG_CMDTAG
+
+static void
+merge_local_accum(CmdStats *pg_restrict stats, const LocalStatsAccum *pg_restrict accum)
+{
+ CommandTag tag;
+ uint64 *dst = (uint64 *) stats->cnt;
+ const uint64 *src = (const uint64 *) accum->cnt;
+
+ for (tag = FIRST_CMDTAG; tag <= LAST_CMDTAG; tag++)
+ {
+ int offset = local_stats_offset[tag].offset;
+ if (offset >= 0)
+ dst[tag] += src[offset];
+ }
+}
+
+static void
+rollup_local_stats(CmdStats *pg_restrict stats, LocalCmdStats *pg_restrict localstats)
+{
+ CommandTag tag;
+ uint64 *dst = (uint64 *) stats->cnt;
+ pg_atomic_uint32 *src = (pg_atomic_uint32 *) localstats->cnt;
+
+ LWLockAcquire(CmdStatsLock, LW_EXCLUSIVE);
+ /*
+ * We need to read the src[] array element and zero it in the same atomic
+ * operation. We hold an exclusive lock, but backends who merely update the
+ * value of their own count don't try to take the lock, so we get no protection
+ * against concurrent updates by them.
+ */
+ for (tag = FIRST_CMDTAG; tag <= LAST_CMDTAG; tag++)
+ {
+ int offset = local_stats_offset[tag].offset;
+ if (offset >= 0)
+ dst[tag] += pg_atomic_exchange_u32(&(src[offset]), 0);
+ }
+ LWLockRelease(CmdStatsLock);
+}
+
+/* ----------
+ * GUC parameters
+ * ----------
+ */
+bool cmdstats_tracking = false;
+
+/*
+ * This structure holds counters in shared memory tracking how many commands of
+ * a given type have been run.
+ *
+ * CmdStatsShmem->cmdstats
+ * -------------------------------
+ * A single shared array of uint64 counts reserved for counting per commandtag
+ * how many operations of that commandtag have been executed to completion.
+ * Reading and writing these counts requires lwlocks. The size of this array
+ * is proportional to NUM_CMDSTATS_COUNTS. These counts may underrepresent the
+ * true totals if the backends are keeping local per-backend counts.
+ *
+ * CmdStatsShmem->localcmdstats
+ * ------------------------------------
+ * For the subset of commands that may be quick and be run in rapid succession,
+ * to avoid adding the locking overhead to each run of the command, separate
+ * counters are kept per backend. These local per-backend counters can be
+ * updated by the owning backend through atomics without the use of locking.
+ * This requires shared memory that scales proportional to MaxBackends *
+ * NUM_LOCAL_STATS. Owing to the impact on the amount of shared memory
+ * required, this treatment is reserved only to the fastest commands which
+ * might otherwise suffer a measurable performance hit.
+ *
+ * LOCKING AND ATOMIC READS
+ * ------------------------
+ *
+ * Both the shared array of counts and the local per-backend counts are stored
+ * in shared memory. The "local per-backend" counts are equally visible to all
+ * backends. By convention, backends may read and write their own per-backend
+ * counts, but may only read counts from other backends. Note that this
+ * restriction holds even for backends holding the lwlock in exclusive mode.
+ * Even then, the backend holding the lock must not modify the counts of any
+ * other backend.
+ *
+ * Atomic reads and writes are supported on all architectures for uint32, but
+ * only on a subset of architectures for uint64. To make the implementation
+ * the same on all architectures, the backends track their counts as uint32
+ * variables. It may be tempting to upgrade these counts to uint64 if atomic
+ * support were universal, but that would double the amount of shared memory
+ * for command statistics used per backend process. It may also be tempting to
+ * reduce to a uint16 in order to reduce the per backend shared memory usage,
+ * but there is no atomics support for uint16.
+ *
+ * At any moment, the true clusterwide count per command is the sum of the
+ * shared counter for that command and all backend local counters for that
+ * command, if any. Since uint32 is small enough to overflow, we keep a shared
+ * uint64 counter per command tag for all command tags, including those which
+ * get special per-backend counters. This implementation assumes uint64 counts
+ * will not overflow during the lifetime of a running cluster.
+ *
+ * Whenever a backend's uint32 counts are about to overflow, the backend must
+ * acquire the shared CmdStatsLock exclusively, merge its own counts into the
+ * shared counts, and then reset its own counts to zero. Other backends which
+ * are simultaneously updating their own uint32 counts are not blocked by this,
+ * but if another backend were to simultaneously need to merge counts into the
+ * shared counts, it would have to wait for the lock. Since merging counts
+ * only happens once every 2^32 commands per backend, backends are not expected
+ * to block each other in this way sufficiently often to be of concern.
+ *
+ * Backends totaling up the counts to return to a caller as part of executing
+ * one of the command stats system views must acquire the CmdStatsLock and hold
+ * it while tallying the shared counts and all the backend counts together.
+ * Each concurrently running backend may update its counts while this lock is
+ * held, but no backend may merge its counts into the shared counts nor reset
+ * its counts to zero. Since the command stats system views make no
+ * transaction isolation level guarantees about the stability of the counts,
+ * this seems sufficient, so long as the atomicity of changes to the counts is
+ * carefully handled.
+ */
+typedef struct
+{
+ CmdStats *cmdstats; /* Array of cluster totals */
+ LocalCmdStats *localcmdstats; /* Array of backend totals */
+}
+#if defined(pg_attribute_aligned)
+ pg_attribute_aligned(MAXIMUM_ALIGNOF)
+#endif
+CmdStatsShmemStruct;
+
+static CmdStatsShmemStruct *CmdStatsShmem;
+
+static Size
+command_stats_struct_size(void)
+{
+ return MAXALIGN(sizeof(CmdStatsShmemStruct));
+}
+
+static Size
+shared_cmdstats_size(void)
+{
+ return MAXALIGN(sizeof(CmdStats));
+}
+
+static Size
+local_cmdstats_size(void)
+{
+ return mul_size(MAXALIGN(sizeof(LocalCmdStats)), MaxBackends);
+}
+
+/*
+ * CmdStatsShmemSize
+ * Compute space needed for cmdstats-related shared memory
+ */
+Size
+CmdStatsShmemSize(void)
+{
+ Size size;
+
+ if (!cmdstats_tracking)
+ return 0;
+ size = command_stats_struct_size(); /* The struct itself */
+ size = add_size(size, shared_cmdstats_size()); /* cmdstats */
+ size = add_size(size, local_cmdstats_size()); /* localcmdstats */
+ return size;
+}
+
+/*
+ * CmdStatsShmemInit
+ * Allocate and initialize cmdstats-related shared memory
+ */
+void
+CmdStatsShmemInit(void)
+{
+ bool found;
+ Size shmemsize = CmdStatsShmemSize();
+
+ if (shmemsize == 0)
+ {
+ CmdStatsShmem = NULL;
+ return;
+ }
+
+ CmdStatsShmem = (CmdStatsShmemStruct *)
+ ShmemInitStruct("CmdStats Data", shmemsize, &found);
+
+ if (!IsUnderPostmaster)
+ {
+ char *next; /* really a void pointer, but char for portability */
+ int bkend, idx;
+
+ /*
+ * We should only be called at postmaster startup, before any
+ * backends are looking at the CmdStatsShmem, so acquiring
+ * the lock is probably not required. There doesn't seem to be
+ * any reason to skip the lock, though, since we only have to do
+ * this once.
+ */
+ LWLockAcquire(CmdStatsLock, LW_EXCLUSIVE);
+
+ /*
+ * Each pointer field in the struct points to memory contiguous with
+ * the struct itself. Each such field is MAXALIGNED, but other than
+ * the padding that might introduce, the fields are in-order and back
+ * to back.
+ */
+ memset(CmdStatsShmem, 0, shmemsize);
+ next = (char*)CmdStatsShmem + command_stats_struct_size();
+ CmdStatsShmem->cmdstats = (CmdStats *)next;
+ next = next + shared_cmdstats_size();
+ CmdStatsShmem->localcmdstats = (LocalCmdStats *)next;
+ next = next + local_cmdstats_size();
+
+ /* Initialize all the atomic variables before anybody can use them */
+ for (bkend = 0; bkend < MaxBackends; bkend++)
+ for (idx = 0; idx < NUM_LOCAL_STATS; idx++)
+ pg_atomic_init_u32(
+ &(CmdStatsShmem->localcmdstats[bkend].cnt[idx]), 0);
+
+ /* It is now safe for other backends to get the lock */
+ LWLockRelease(CmdStatsLock);
+
+ /*
+ * For sanity, assert that we used precisely the amount of memory that
+ * we allocated. There wouldn't be much harm in allocating a bit more
+ * than necessary, but we did not do so intentionally, so it would be
+ * indicative of a memory size calculation error if things don't match.
+ */
+ Assert(next == (char *)CmdStatsShmem + shmemsize);
+
+ elog(DEBUG1, "cmdstats finished initializing shared memory of size "
+ "%llu ", (long long unsigned int) shmemsize);
+ }
+ else
+ Assert(found);
+}
+
+CmdStats *
+cmdstats_shared_tally(void)
+{
+ int backend,
+ localidx,
+ tagidx;
+ LocalCmdStats *local_stats;
+ pg_atomic_uint32 *bkend_local_array;
+ const CmdStats *shared_stats;
+ const uint64 *shared_array;
+ CmdStats *result_stats;
+ uint64 *result_array;
+ LocalStatsAccum local_accum;
+ uint64 *accum_array;
+
+
+ Assert(cmdstats_tracking);
+ Assert(CmdStatsShmem);
+
+ shared_stats = (const CmdStats *) CmdStatsShmem->cmdstats;
+ shared_array = (const uint64 *) shared_stats->cnt;
+ local_stats = (LocalCmdStats *) CmdStatsShmem->localcmdstats;
+
+ result_stats = (CmdStats *) palloc0(sizeof(CmdStats));
+ result_array = (uint64 *) result_stats->cnt;
+
+ memset(&local_accum, 0, sizeof(local_accum));
+ accum_array = (uint64*) &(local_accum.cnt[0]);
+
+ LWLockAcquire(CmdStatsLock, LW_SHARED);
+ for (backend = 0; backend < MaxBackends; backend++)
+ {
+ bkend_local_array = local_stats[backend].cnt;
+ for (localidx = 0; localidx < NUM_LOCAL_STATS; localidx++)
+ accum_array[localidx] += pg_atomic_read_u32(&(bkend_local_array[localidx]));
+ }
+ for (tagidx = 0; tagidx < NUM_CMDSTATS_COUNTS; tagidx++)
+ result_array[tagidx] += shared_array[tagidx];
+ merge_local_accum(result_stats, &local_accum);
+ LWLockRelease(CmdStatsLock);
+
+ return result_stats;
+}
+
+void
+cmdstats_rollup_localstats(LocalCmdStats *localstats)
+{
+ Assert(cmdstats_tracking);
+ Assert(CmdStatsShmem);
+
+ rollup_local_stats(CmdStatsShmem->cmdstats, localstats);
+}
+
+void
+cmdstats_increment(CommandTag commandtag)
+{
+ int local_stats_idx;
+
+ /* Should not get here unless cmdstats_tracking is true */
+ Assert(cmdstats_tracking);
+ Assert(CmdStatsShmem);
+ Assert(commandtag >= FIRST_CMDTAG && commandtag < LAST_CMDTAG);
+
+ /*
+ * If the commandtag has per-backend counters, we'll update the per-backend
+ * counter belonging to this backend, otherwise we'll update the shared
+ * counter used by all backends. Note that updating the shared counter
+ * when we could have updated the per-backend counter is not a correctness
+ * problem, only a performance problem. The per-backend counters
+ * eventually get rolled into the shared counters anyway.
+ *
+ * There is no good reason why a postmaster should ever call this function,
+ * so we complain loudly if it does. Having the postmaster update a
+ * per-backend counter would be inappropriate, but if all shared counters
+ * have been properly initialized, the postmaster could in theory safely
+ * increment the shared counter for the commandtag. But if the postmaster
+ * is calling this function it suggests that something has gone awry, and
+ * we can't really trust that the shared memory has been properly
+ * initialized yet, so a hard error seems more appropriate.
+ */
+ local_stats_idx = local_stats_offset[commandtag].offset;
+ if (local_stats_idx < 0)
+ {
+ /*
+ * There are no per-backend counters for this commandtag, so we must
+ * hold an exclusive lock while incrementing the shared counter.
+ * We don't expect this to be performed by the postmaster, but we don't
+ * really care, which is why we're not checking IsUnderPostmaster here.
+ */
+ LWLockAcquire(CmdStatsLock, LW_EXCLUSIVE);
+ CmdStatsShmem->cmdstats->cnt[commandtag]++;
+ LWLockRelease(CmdStatsLock);
+ }
+ else if (IsUnderPostmaster)
+ {
+ /*
+ * There are per-backend counters for this commandtag, so we must
+ * actually be a backend to have one assigned to us (hence the
+ * IsUnderPostmaster check), and we have exclusive ownership of the
+ * counter so we don't need to take the lock. But since we might
+ * increment our counter while others are simultaneously reading our
+ * counter, we must use atomic operations.
+ */
+ int bkend;
+ pg_atomic_uint32 *cnt;
+
+ /* Assert that we're called by a valid backend */
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ /* Assert that our argument is within bounds */
+ Assert(local_stats_idx < NUM_LOCAL_STATS);
+
+ /*
+ * MyBackendId runs from [1..MaxBackends] inclusive, but our
+ * indexes only operate from [0..MaxBackends-1]
+ */
+ bkend = MyBackendId - 1;
+
+ /* We have a local stats counter for This commandtag. */
+ cnt = &(CmdStatsShmem->localcmdstats[bkend].cnt[local_stats_idx]);
+
+ /*
+ * We need to verify that our counter will not overflow when we
+ * increment it. We don't have to worry about anybody else changing
+ * our per-backend counter, so there is no race condition between
+ * checking for overflow and later incrementing.
+ *
+ * The atomic read is overkill, since nobody else can change the cnt.
+ * In most spots we need to read and write these values atomically,
+ * so we're just following that pattern here for good measure.
+ */
+ if (pg_atomic_read_u32(cnt) >= PG_UINT32_MAX)
+ /* Harvest and zero all our backend's stats. This takes a lock */
+ cmdstats_rollup_localstats(&CmdStatsShmem->localcmdstats[bkend]);
+
+ /*
+ * Increment our count atomically. This needs to be atomic since
+ * other backends could be reading the count simultaneously.
+ */
+ pg_atomic_fetch_add_u32(cnt, 1);
+ }
+ else
+ {
+ elog(ERROR, "cmdstats_increment called by postmaster");
+ }
+}
+
+void
+reset_cmdstats(void)
+{
+ int backend,
+ localidx,
+ tagidx;
+ LocalCmdStats *local_stats;
+ pg_atomic_uint32 *bkend_local_array;
+ CmdStats *shared_stats;
+ uint64 *shared_array;
+
+ Assert(cmdstats_tracking);
+ Assert(CmdStatsShmem);
+
+ shared_stats = (CmdStats *) CmdStatsShmem->cmdstats;
+ shared_array = (uint64 *) shared_stats->cnt;
+ local_stats = (LocalCmdStats *) CmdStatsShmem->localcmdstats;
+
+ LWLockAcquire(CmdStatsLock, LW_EXCLUSIVE);
+ for (backend = 0; backend < MaxBackends; backend++)
+ {
+ bkend_local_array = local_stats[backend].cnt;
+ for (localidx = 0; localidx < NUM_LOCAL_STATS; localidx++)
+ pg_atomic_write_u32(&(bkend_local_array[localidx]), 0);
+ }
+ for (tagidx = 0; tagidx < NUM_CMDSTATS_COUNTS; tagidx++)
+ shared_array[tagidx] = 0;
+ LWLockRelease(CmdStatsLock);
+}
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index d7f99d9944..131ea421c2 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -48,6 +48,7 @@
#include "pg_trace.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
+#include "postmaster/cmdstats.h"
#include "postmaster/fork_process.h"
#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
@@ -1370,6 +1371,12 @@ pgstat_reset_shared_counters(const char *target)
msg.m_resettarget = RESET_ARCHIVER;
else if (strcmp(target, "bgwriter") == 0)
msg.m_resettarget = RESET_BGWRITER;
+ else if (strcmp(target, "cmdstats") == 0)
+ {
+ if (cmdstats_tracking)
+ reset_cmdstats();
+ return;
+ }
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d59cd..1e336cb7ce 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -27,6 +27,7 @@
#include "postmaster/autovacuum.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/bgwriter.h"
+#include "postmaster/cmdstats.h"
#include "postmaster/postmaster.h"
#include "replication/logicallauncher.h"
#include "replication/origin.h"
@@ -138,6 +139,7 @@ CreateSharedMemoryAndSemaphores(void)
size = add_size(size, ProcSignalShmemSize());
size = add_size(size, CheckpointerShmemSize());
size = add_size(size, AutoVacuumShmemSize());
+ size = add_size(size, CmdStatsShmemSize());
size = add_size(size, ReplicationSlotsShmemSize());
size = add_size(size, ReplicationOriginShmemSize());
size = add_size(size, WalSndShmemSize());
@@ -250,6 +252,7 @@ CreateSharedMemoryAndSemaphores(void)
ProcSignalShmemInit();
CheckpointerShmemInit();
AutoVacuumShmemInit();
+ CmdStatsShmemInit();
ReplicationSlotsShmemInit();
ReplicationOriginShmemInit();
WalSndShmemInit();
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index e6985e8eed..29c08e4268 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -50,3 +50,4 @@ MultiXactTruncationLock 41
OldSnapshotTimeMapLock 42
LogicalRepWorkerLock 43
XactTruncationLock 44
+CmdStatsLock 45
diff --git a/src/backend/tcop/cmdtag.c b/src/backend/tcop/cmdtag.c
index b9fbff612f..7d173db2f5 100644
--- a/src/backend/tcop/cmdtag.c
+++ b/src/backend/tcop/cmdtag.c
@@ -23,10 +23,11 @@ typedef struct CommandTagBehavior
const bool event_trigger_ok;
const bool table_rewrite_ok;
const bool display_rowcount;
+ const int local_stats_idx;
} CommandTagBehavior;
-#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt) \
- { name, evtrgok, rwrok, rowcnt },
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, localstats) \
+ { name, evtrgok, rwrok, rowcnt, localstats },
const CommandTagBehavior tag_behavior[COMMAND_TAG_NEXTTAG] = {
#include "tcop/cmdtaglist.h"
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index 7208751ec7..abace92b96 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -39,6 +39,7 @@
#include "executor/tstoreReceiver.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
+#include "postmaster/cmdstats.h"
#include "utils/portal.h"
@@ -169,6 +170,9 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o
CommandTag tag;
const char *tagname;
+ if (cmdstats_tracking)
+ cmdstats_increment(qc->commandTag);
+
switch (dest)
{
case DestRemote:
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 2aff739466..380df78bb2 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -23,6 +23,7 @@
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/bgworker_internals.h"
+#include "postmaster/cmdstats.h"
#include "postmaster/postmaster.h"
#include "storage/proc.h"
#include "storage/procarray.h"
@@ -2092,3 +2093,64 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS)
/* Returns the record as Datum */
PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
}
+
+Datum
+pg_command_stats_data(PG_FUNCTION_ARGS)
+{
+ TupleDesc tupdesc;
+ Datum values[2] = { 0, 0 };
+ bool nulls[2] = { false, false };
+ ReturnSetInfo *rsi;
+ MemoryContext old_ctx;
+ Tuplestorestate *tuple_store;
+ CommandTag tag;
+
+ rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* Check to see if caller supports us returning a tuplestore */
+ if (rsi == NULL || !IsA(rsi, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsi->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not "
+ "allowed in this context")));
+
+ rsi->returnMode = SFRM_Materialize;
+
+ /* 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");
+
+ /* Build tuplestore to hold the result rows */
+ old_ctx = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+ tuple_store =
+ tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ false, work_mem);
+ rsi->setDesc = tupdesc;
+ rsi->setResult = tuple_store;
+
+ MemoryContextSwitchTo(old_ctx);
+
+ if (cmdstats_tracking)
+ {
+ CmdStats *tally = cmdstats_shared_tally();
+ for (tag = FIRST_CMDTAG; tally != NULL && tag <= LAST_CMDTAG; tag++)
+ {
+ HeapTuple tuple;
+
+ if (tally->cnt[tag] == 0)
+ continue;
+
+ values[0] = CStringGetTextDatum(GetCommandTagName(tag));
+ values[1] = Int64GetDatum(tally->cnt[tag]);
+
+ tuple = heap_form_tuple(tupdesc, values, nulls);
+ tuplestore_puttuple(tuple_store, tuple);
+ }
+ }
+ PG_RETURN_NULL();
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 2f3e0a70e0..97ba761965 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -63,6 +63,7 @@
#include "postmaster/autovacuum.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/bgwriter.h"
+#include "postmaster/cmdstats.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "postmaster/walwriter.h"
@@ -1506,6 +1507,18 @@ static struct config_bool ConfigureNamesBool[] =
false,
NULL, NULL, NULL
},
+ {
+ {"cmdstats_tracking", PGC_POSTMASTER, STATS_COLLECTOR,
+ gettext_noop("Collects statistics of commands by type."),
+ gettext_noop("For each type of command (INSERT, UPDATE, DELETE, "
+ "CREATE TABLE, etc), tracks statistics about the "
+ "commands of that type which have been run."),
+ GUC_SUPERUSER_ONLY
+ },
+ &cmdstats_tracking,
+ false,
+ NULL, NULL, NULL
+ },
{
{"update_process_title", PGC_SUSET, PROCESS_TITLE,
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 61f2c2f5b4..20c3dd0e68 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5420,6 +5420,12 @@
proargmodes => '{o,o,o,o,o,o,o}',
proargnames => '{archived_count,last_archived_wal,last_archived_time,failed_count,last_failed_wal,last_failed_time,stats_reset}',
prosrc => 'pg_stat_get_archiver' },
+{ oid => '8450', descr => 'statistics: information about sql commands',
+ proname => 'pg_command_stats_data', prorows => '100', proretset => 't',
+ proisstrict => 'f', provolatile => 'v', proparallel => 'r',
+ prorettype => 'record', proargtypes => '', proallargtypes => '{text,int8}',
+ proargmodes => '{o,o}', proargnames => '{tag,count}',
+ prosrc => 'pg_command_stats_data' },
{ oid => '2769',
descr => 'statistics: number of timed checkpoints started by the bgwriter',
proname => 'pg_stat_get_bgwriter_timed_checkpoints', provolatile => 's',
diff --git a/src/include/postmaster/cmdstats.h b/src/include/postmaster/cmdstats.h
new file mode 100644
index 0000000000..c1a08dcb0c
--- /dev/null
+++ b/src/include/postmaster/cmdstats.h
@@ -0,0 +1,68 @@
+/* ----------
+ * pgstat.h
+ *
+ * Declarations for PostgreSQL command statistics
+ *
+ * Copyright (c) 2001-2020, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/cmdstats.h
+ * ----------
+ */
+#ifndef CMDSTATS_H
+#define CMDSTATS_H
+
+#include "postgres.h"
+
+#include "tcop/cmdtag.h"
+
+extern PGDLLIMPORT bool cmdstats_tracking;
+
+/*-------------
+ * Command Stats shmem structs.
+ *-------------
+ */
+/*
+ * For each command tag, we track how many commands completed
+ * for that type.
+ */
+#define NUM_CMDSTATS_COUNTS ((uint32)NUM_CMDTAGS)
+
+/*
+ * CmdStats is used for holding the shared counts per CommandTag for all tags.
+ */
+typedef struct CmdStats {
+ uint64 cnt[NUM_CMDSTATS_COUNTS];
+} CmdStats;
+
+/*
+ * LocalCmdStats is used for holding the per-backend local counts for just
+ * the subset of CommandTags which have per-backend counts. There are
+ * NUM_LOCAL_STATS < NUM_CMDTAGS number of those. Indexing into this
+ * structure for a given CommandTag requires looking up the appropriate
+ * offset. See local_stats_offset for details.
+ */
+typedef struct LocalCmdStats {
+ pg_atomic_uint32 cnt[NUM_LOCAL_STATS];
+} LocalCmdStats;
+
+/*
+ * LocalStatsAccum is used for totalling up the per-backend counts. We cannot
+ * simply use a LocalCmdStats struct for that, as the totals across backends
+ * may overflow a uint32. Thus, we have only NUM_LOCAL_STATS counts, but use a
+ * uint64 per count.
+ */
+typedef struct LocalStatsAccum {
+ uint64 cnt[NUM_LOCAL_STATS];
+} LocalStatsAccum;
+
+/* shared memory stuff */
+extern int NumCmdStats(void);
+extern Size CmdStatsPerBackendShmemSize(void);
+extern Size CmdStatsShmemSize(void);
+extern void CmdStatsShmemInit(void);
+extern CmdStats *cmdstats_shared_tally(void);
+extern void cmdstats_rollup_localstats(LocalCmdStats *localstats);
+extern void cmdstats_increment(CommandTag commandtag);
+extern void reset_cmdstats(void);
+
+#endif /* CMDSTATS_H */
diff --git a/src/include/tcop/cmdtag.h b/src/include/tcop/cmdtag.h
index f75a91e7c8..5795db9ba3 100644
--- a/src/include/tcop/cmdtag.h
+++ b/src/include/tcop/cmdtag.h
@@ -14,7 +14,7 @@
#define CMDTAG_H
-#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt) \
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, localstats) \
tag,
typedef enum CommandTag
@@ -23,6 +23,10 @@ typedef enum CommandTag
COMMAND_TAG_NEXTTAG
} CommandTag;
+#define FIRST_CMDTAG ((CommandTag)0)
+#define LAST_CMDTAG ((CommandTag)(COMMAND_TAG_NEXTTAG - 1))
+#define NUM_CMDTAGS ((int)COMMAND_TAG_NEXTTAG)
+
#undef PG_CMDTAG
typedef struct QueryCompletion
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 8ef0f55e74..ac34f67be5 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -23,196 +23,201 @@
* textual name, so that we can bsearch on it; see GetCommandTagEnum().
*/
-/* symbol name, textual name, event_trigger_ok, table_rewrite_ok, rowcount */
-PG_CMDTAG(CMDTAG_UNKNOWN, "???", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ACCESS_METHOD, "ALTER ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_AGGREGATE, "ALTER AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CAST, "ALTER CAST", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_COLLATION, "ALTER COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CONVERSION, "ALTER CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DATABASE, "ALTER DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DEFAULT_PRIVILEGES, "ALTER DEFAULT PRIVILEGES", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DOMAIN, "ALTER DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_EVENT_TRIGGER, "ALTER EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_EXTENSION, "ALTER EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FOREIGN_DATA_WRAPPER, "ALTER FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FOREIGN_TABLE, "ALTER FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FUNCTION, "ALTER FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_INDEX, "ALTER INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_LANGUAGE, "ALTER LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_LARGE_OBJECT, "ALTER LARGE OBJECT", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_MATERIALIZED_VIEW, "ALTER MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR, "ALTER OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_POLICY, "ALTER POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ROLE, "ALTER ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ROUTINE, "ALTER ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_RULE, "ALTER RULE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SCHEMA, "ALTER SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SEQUENCE, "ALTER SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SERVER, "ALTER SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_STATISTICS, "ALTER STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SUBSCRIPTION, "ALTER SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SYSTEM, "ALTER SYSTEM", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TABLE, "ALTER TABLE", true, true, false)
-PG_CMDTAG(CMDTAG_ALTER_TABLESPACE, "ALTER TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION, "ALTER TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY, "ALTER TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_PARSER, "ALTER TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_TEMPLATE, "ALTER TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false)
-PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false)
-PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false)
-PG_CMDTAG(CMDTAG_CALL, "CALL", false, false, false)
-PG_CMDTAG(CMDTAG_CHECKPOINT, "CHECKPOINT", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE, "CLOSE", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE_CURSOR, "CLOSE CURSOR", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE_CURSOR_ALL, "CLOSE CURSOR ALL", false, false, false)
-PG_CMDTAG(CMDTAG_CLUSTER, "CLUSTER", false, false, false)
-PG_CMDTAG(CMDTAG_COMMENT, "COMMENT", true, false, false)
-PG_CMDTAG(CMDTAG_COMMIT, "COMMIT", false, false, false)
-PG_CMDTAG(CMDTAG_COMMIT_PREPARED, "COMMIT PREPARED", false, false, false)
-PG_CMDTAG(CMDTAG_COPY, "COPY", false, false, true)
-PG_CMDTAG(CMDTAG_COPY_FROM, "COPY FROM", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_DOMAIN, "CREATE DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_EVENT_TRIGGER, "CREATE EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_EXTENSION, "CREATE EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FOREIGN_DATA_WRAPPER, "CREATE FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FOREIGN_TABLE, "CREATE FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FUNCTION, "CREATE FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_INDEX, "CREATE INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_LANGUAGE, "CREATE LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_MATERIALIZED_VIEW, "CREATE MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR, "CREATE OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_POLICY, "CREATE POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ROLE, "CREATE ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ROUTINE, "CREATE ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_RULE, "CREATE RULE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SCHEMA, "CREATE SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SEQUENCE, "CREATE SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SERVER, "CREATE SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_STATISTICS, "CREATE STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SUBSCRIPTION, "CREATE SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLE, "CREATE TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLE_AS, "CREATE TABLE AS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLESPACE, "CREATE TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_CONFIGURATION, "CREATE TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_DICTIONARY, "CREATE TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_PARSER, "CREATE TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_TEMPLATE, "CREATE TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false)
-PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false)
-PG_CMDTAG(CMDTAG_DECLARE_CURSOR, "DECLARE CURSOR", false, false, false)
-PG_CMDTAG(CMDTAG_DELETE, "DELETE", false, false, true)
-PG_CMDTAG(CMDTAG_DISCARD, "DISCARD", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false)
-PG_CMDTAG(CMDTAG_DO, "DO", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_DOMAIN, "DROP DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_EVENT_TRIGGER, "DROP EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_EXTENSION, "DROP EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FOREIGN_DATA_WRAPPER, "DROP FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FOREIGN_TABLE, "DROP FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FUNCTION, "DROP FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_INDEX, "DROP INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_LANGUAGE, "DROP LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_MATERIALIZED_VIEW, "DROP MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR, "DROP OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR_CLASS, "DROP OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_REPLICATION_SLOT, "DROP REPLICATION SLOT", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ROLE, "DROP ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ROUTINE, "DROP ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_RULE, "DROP RULE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SCHEMA, "DROP SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SEQUENCE, "DROP SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SERVER, "DROP SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_STATISTICS, "DROP STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SUBSCRIPTION, "DROP SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TABLE, "DROP TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TABLESPACE, "DROP TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_CONFIGURATION, "DROP TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_DICTIONARY, "DROP TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_PARSER, "DROP TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_TEMPLATE, "DROP TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false)
-PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false)
-PG_CMDTAG(CMDTAG_FETCH, "FETCH", false, false, true)
-PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false)
-PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true)
-PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false)
-PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false)
-PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false)
-PG_CMDTAG(CMDTAG_MOVE, "MOVE", false, false, true)
-PG_CMDTAG(CMDTAG_NOTIFY, "NOTIFY", false, false, false)
-PG_CMDTAG(CMDTAG_PREPARE, "PREPARE", false, false, false)
-PG_CMDTAG(CMDTAG_PREPARE_TRANSACTION, "PREPARE TRANSACTION", false, false, false)
-PG_CMDTAG(CMDTAG_REASSIGN_OWNED, "REASSIGN OWNED", false, false, false)
-PG_CMDTAG(CMDTAG_REFRESH_MATERIALIZED_VIEW, "REFRESH MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_REINDEX, "REINDEX", false, false, false)
-PG_CMDTAG(CMDTAG_RELEASE, "RELEASE", false, false, false)
-PG_CMDTAG(CMDTAG_RESET, "RESET", false, false, false)
-PG_CMDTAG(CMDTAG_REVOKE, "REVOKE", true, false, false)
-PG_CMDTAG(CMDTAG_REVOKE_ROLE, "REVOKE ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_ROLLBACK, "ROLLBACK", false, false, false)
-PG_CMDTAG(CMDTAG_ROLLBACK_PREPARED, "ROLLBACK PREPARED", false, false, false)
-PG_CMDTAG(CMDTAG_SAVEPOINT, "SAVEPOINT", false, false, false)
-PG_CMDTAG(CMDTAG_SECURITY_LABEL, "SECURITY LABEL", true, false, false)
-PG_CMDTAG(CMDTAG_SELECT, "SELECT", false, false, true)
-PG_CMDTAG(CMDTAG_SELECT_FOR_KEY_SHARE, "SELECT FOR KEY SHARE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_NO_KEY_UPDATE, "SELECT FOR NO KEY UPDATE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_SHARE, "SELECT FOR SHARE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_UPDATE, "SELECT FOR UPDATE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_INTO, "SELECT INTO", true, false, false)
-PG_CMDTAG(CMDTAG_SET, "SET", false, false, false)
-PG_CMDTAG(CMDTAG_SET_CONSTRAINTS, "SET CONSTRAINTS", false, false, false)
-PG_CMDTAG(CMDTAG_SHOW, "SHOW", false, false, false)
-PG_CMDTAG(CMDTAG_START_TRANSACTION, "START TRANSACTION", false, false, false)
-PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
-PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
-PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
-PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+/* symbol name, textual name, event_trigger_ok, table_rewrite_ok, rowcount, localstats */
+PG_CMDTAG(CMDTAG_UNKNOWN, "???", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_ACCESS_METHOD, "ALTER ACCESS METHOD", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_AGGREGATE, "ALTER AGGREGATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_CAST, "ALTER CAST", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_COLLATION, "ALTER COLLATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_CONVERSION, "ALTER CONVERSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_DATABASE, "ALTER DATABASE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_DEFAULT_PRIVILEGES, "ALTER DEFAULT PRIVILEGES", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_DOMAIN, "ALTER DOMAIN", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_EVENT_TRIGGER, "ALTER EVENT TRIGGER", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_EXTENSION, "ALTER EXTENSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_FOREIGN_DATA_WRAPPER, "ALTER FOREIGN DATA WRAPPER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_FOREIGN_TABLE, "ALTER FOREIGN TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_FUNCTION, "ALTER FUNCTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_INDEX, "ALTER INDEX", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_LANGUAGE, "ALTER LANGUAGE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_LARGE_OBJECT, "ALTER LARGE OBJECT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_MATERIALIZED_VIEW, "ALTER MATERIALIZED VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR, "ALTER OPERATOR", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_POLICY, "ALTER POLICY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_ROLE, "ALTER ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_ROUTINE, "ALTER ROUTINE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_RULE, "ALTER RULE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SCHEMA, "ALTER SCHEMA", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SEQUENCE, "ALTER SEQUENCE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SERVER, "ALTER SERVER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_STATISTICS, "ALTER STATISTICS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SUBSCRIPTION, "ALTER SUBSCRIPTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SYSTEM, "ALTER SYSTEM", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TABLE, "ALTER TABLE", true, true, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TABLESPACE, "ALTER TABLESPACE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION, "ALTER TEXT SEARCH CONFIGURATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY, "ALTER TEXT SEARCH DICTIONARY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_PARSER, "ALTER TEXT SEARCH PARSER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_TEMPLATE, "ALTER TEXT SEARCH TEMPLATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false, 0)
+PG_CMDTAG(CMDTAG_CALL, "CALL", false, false, false, 1)
+PG_CMDTAG(CMDTAG_CHECKPOINT, "CHECKPOINT", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CLOSE, "CLOSE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CLOSE_CURSOR, "CLOSE CURSOR", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CLOSE_CURSOR_ALL, "CLOSE CURSOR ALL", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CLUSTER, "CLUSTER", false, false, false, -1)
+PG_CMDTAG(CMDTAG_COMMENT, "COMMENT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_COMMIT, "COMMIT", false, false, false, 2)
+PG_CMDTAG(CMDTAG_COMMIT_PREPARED, "COMMIT PREPARED", false, false, false, 3)
+PG_CMDTAG(CMDTAG_COPY, "COPY", false, false, true, -1)
+PG_CMDTAG(CMDTAG_COPY_FROM, "COPY FROM", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_DOMAIN, "CREATE DOMAIN", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_EVENT_TRIGGER, "CREATE EVENT TRIGGER", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_EXTENSION, "CREATE EXTENSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_FOREIGN_DATA_WRAPPER, "CREATE FOREIGN DATA WRAPPER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_FOREIGN_TABLE, "CREATE FOREIGN TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_FUNCTION, "CREATE FUNCTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_INDEX, "CREATE INDEX", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_LANGUAGE, "CREATE LANGUAGE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_MATERIALIZED_VIEW, "CREATE MATERIALIZED VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR, "CREATE OPERATOR", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_POLICY, "CREATE POLICY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_ROLE, "CREATE ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_ROUTINE, "CREATE ROUTINE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_RULE, "CREATE RULE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_SCHEMA, "CREATE SCHEMA", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_SEQUENCE, "CREATE SEQUENCE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_SERVER, "CREATE SERVER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_STATISTICS, "CREATE STATISTICS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_SUBSCRIPTION, "CREATE SUBSCRIPTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TABLE, "CREATE TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TABLE_AS, "CREATE TABLE AS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TABLESPACE, "CREATE TABLESPACE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_CONFIGURATION, "CREATE TEXT SEARCH CONFIGURATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_DICTIONARY, "CREATE TEXT SEARCH DICTIONARY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_PARSER, "CREATE TEXT SEARCH PARSER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_TEMPLATE, "CREATE TEXT SEARCH TEMPLATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DECLARE_CURSOR, "DECLARE CURSOR", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DELETE, "DELETE", false, false, true, 4)
+PG_CMDTAG(CMDTAG_DISCARD, "DISCARD", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DO, "DO", false, false, false, 5)
+PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_DOMAIN, "DROP DOMAIN", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_EVENT_TRIGGER, "DROP EVENT TRIGGER", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_EXTENSION, "DROP EXTENSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_FOREIGN_DATA_WRAPPER, "DROP FOREIGN DATA WRAPPER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_FOREIGN_TABLE, "DROP FOREIGN TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_FUNCTION, "DROP FUNCTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_INDEX, "DROP INDEX", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_LANGUAGE, "DROP LANGUAGE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_MATERIALIZED_VIEW, "DROP MATERIALIZED VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR, "DROP OPERATOR", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR_CLASS, "DROP OPERATOR CLASS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_REPLICATION_SLOT, "DROP REPLICATION SLOT", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_ROLE, "DROP ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_ROUTINE, "DROP ROUTINE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_RULE, "DROP RULE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_SCHEMA, "DROP SCHEMA", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_SEQUENCE, "DROP SEQUENCE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_SERVER, "DROP SERVER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_STATISTICS, "DROP STATISTICS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_SUBSCRIPTION, "DROP SUBSCRIPTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TABLE, "DROP TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TABLESPACE, "DROP TABLESPACE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_CONFIGURATION, "DROP TEXT SEARCH CONFIGURATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_DICTIONARY, "DROP TEXT SEARCH DICTIONARY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_PARSER, "DROP TEXT SEARCH PARSER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_TEMPLATE, "DROP TEXT SEARCH TEMPLATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false, 6)
+PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false, -1)
+PG_CMDTAG(CMDTAG_FETCH, "FETCH", false, false, true, 7)
+PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false, -1)
+PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true, 8)
+PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false, 9)
+PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false, -1)
+PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_MOVE, "MOVE", false, false, true, 10)
+PG_CMDTAG(CMDTAG_NOTIFY, "NOTIFY", false, false, false, 11)
+PG_CMDTAG(CMDTAG_PREPARE, "PREPARE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_PREPARE_TRANSACTION, "PREPARE TRANSACTION", false, false, false, -1)
+PG_CMDTAG(CMDTAG_REASSIGN_OWNED, "REASSIGN OWNED", false, false, false, -1)
+PG_CMDTAG(CMDTAG_REFRESH_MATERIALIZED_VIEW, "REFRESH MATERIALIZED VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_REINDEX, "REINDEX", false, false, false, -1)
+PG_CMDTAG(CMDTAG_RELEASE, "RELEASE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_RESET, "RESET", false, false, false, 12)
+PG_CMDTAG(CMDTAG_REVOKE, "REVOKE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_REVOKE_ROLE, "REVOKE ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ROLLBACK, "ROLLBACK", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ROLLBACK_PREPARED, "ROLLBACK PREPARED", false, false, false, -1)
+PG_CMDTAG(CMDTAG_SAVEPOINT, "SAVEPOINT", false, false, false, -1)
+PG_CMDTAG(CMDTAG_SECURITY_LABEL, "SECURITY LABEL", true, false, false, -1)
+PG_CMDTAG(CMDTAG_SELECT, "SELECT", false, false, true, 13)
+PG_CMDTAG(CMDTAG_SELECT_FOR_KEY_SHARE, "SELECT FOR KEY SHARE", false, false, false, 14)
+PG_CMDTAG(CMDTAG_SELECT_FOR_NO_KEY_UPDATE, "SELECT FOR NO KEY UPDATE", false, false, false, 15)
+PG_CMDTAG(CMDTAG_SELECT_FOR_SHARE, "SELECT FOR SHARE", false, false, false, 16)
+PG_CMDTAG(CMDTAG_SELECT_FOR_UPDATE, "SELECT FOR UPDATE", false, false, false, 17)
+PG_CMDTAG(CMDTAG_SELECT_INTO, "SELECT INTO", true, false, false, -1)
+PG_CMDTAG(CMDTAG_SET, "SET", false, false, false, 18)
+PG_CMDTAG(CMDTAG_SET_CONSTRAINTS, "SET CONSTRAINTS", false, false, false, -1)
+PG_CMDTAG(CMDTAG_SHOW, "SHOW", false, false, false, 19)
+PG_CMDTAG(CMDTAG_START_TRANSACTION, "START TRANSACTION", false, false, false, -1)
+PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false, 20)
+PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true, 21)
+PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false, -1)
+
+/* Keep this one more than the highest localstats value above */
+#ifndef NUM_LOCAL_STATS
+#define NUM_LOCAL_STATS 22
+#endif
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..f4ba52f617 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -12,7 +12,7 @@ subdir = src/test
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
-SUBDIRS = perl regress isolation modules authentication recovery subscription
+SUBDIRS = perl regress isolation modules authentication recovery cmdstats subscription
# Test suites that are not safe by default but can be run if selected
# by the user via the whitespace-separated list in variable
diff --git a/src/test/cmdstats/Makefile b/src/test/cmdstats/Makefile
new file mode 100644
index 0000000000..5464c433f4
--- /dev/null
+++ b/src/test/cmdstats/Makefile
@@ -0,0 +1,25 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/cmdstats
+#
+# Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/cmdstats/Makefile
+#
+#-------------------------------------------------------------------------
+
+EXTRA_INSTALL=contrib/test_decoding
+
+subdir = src/test/cmdstats
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
+
+clean distclean maintainer-clean:
+ rm -rf tmp_check
diff --git a/src/test/cmdstats/README b/src/test/cmdstats/README
new file mode 100644
index 0000000000..7219c4fd07
--- /dev/null
+++ b/src/test/cmdstats/README
@@ -0,0 +1,23 @@
+src/test/cmdstats/README
+
+Regression tests for cmdstats statistics subsystem
+=============================================
+
+This directory contains a test suite for per command statistics.
+
+Running the tests
+=================
+
+NOTE: You must have given the --enable-tap-tests argument to configure.
+
+Run
+ make check
+or
+ make installcheck
+You can use "make installcheck" if you previously did "make install".
+In that case, the code in the installation tree is tested. With
+"make check", a temporary installation tree is built from the current
+sources and then tested.
+
+Either way, this test initializes, starts, and stops several test Postgres
+clusters.
diff --git a/src/test/cmdstats/t/001_guc_settings.pl b/src/test/cmdstats/t/001_guc_settings.pl
new file mode 100644
index 0000000000..e2cdfa4a25
--- /dev/null
+++ b/src/test/cmdstats/t/001_guc_settings.pl
@@ -0,0 +1,31 @@
+# Minimal test of cmdstats GUC default settings
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 3;
+
+my ($node, $result);
+
+$node = get_new_node("cmdstats_guc_test");
+$node->init;
+$node->start;
+
+# Check that by default cmdstats_tracking is off
+$result = $node->safe_psql('postgres', "SHOW cmdstats_tracking");
+is($result, 'off', 'check default setting of cmdstats_tracking');
+
+# Verify that a mere reload cannot change cmdstats_tracking
+$node->append_conf('postgresql.conf', "cmdstats_tracking = true");
+$node->reload;
+$result = $node->safe_psql('postgres', "SHOW cmdstats_tracking");
+is($result, 'off', 'check default setting of cmdstats_tracking');
+
+# Verify that a restart does change cmdstats_tracking
+$node->restart;
+$result = $node->safe_psql('postgres', "SHOW cmdstats_tracking");
+is($result, 'on', 'check default setting of cmdstats_tracking');
+
+$node->stop;
+$node->teardown_node;
+$node->clean_node;
diff --git a/src/test/cmdstats/t/002_stats.pl b/src/test/cmdstats/t/002_stats.pl
new file mode 100644
index 0000000000..8366399e83
--- /dev/null
+++ b/src/test/cmdstats/t/002_stats.pl
@@ -0,0 +1,53 @@
+# Test of cmdstats system view
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 10;
+
+my ($node, $result);
+
+$node = get_new_node("cmdstats_test");
+$node->init;
+$node->append_conf('postgresql.conf', "cmdstats_tracking = true");
+$node->start;
+
+# Reset the stats. Beware that this in itself is a SELECT statement, and it gets counted *after* the reset.
+$result = $node->safe_psql('postgres', "SELECT pg_stat_reset_shared('cmdstats')");
+
+# Check that we see the one select from above. Note that this, too, is a SELECT that gets counted *after* it runs
+$result = $node->safe_psql('postgres', "SELECT tag || ':' || cnt::text FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, 'SELECT:1', 'have run one SELECT statement');
+
+# Check that we see both selects from above
+$result = $node->safe_psql('postgres', "SELECT tag || ':' || cnt::text FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, 'SELECT:2', 'have run two SELECT statements');
+
+# Check that we don't see any other kinds of statements
+$result = $node->safe_psql('postgres', "SELECT COUNT(DISTINCT tag) FROM pg_command_stats");
+is($result, '1', 'have run only kind of statement');
+
+# Run a CREATE TABLE statement
+$result = $node->safe_psql('postgres', "CREATE TABLE tst (i INTEGER)");
+
+# Check that we now see both SELECT and CREATE TABLE
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, '4', 'have run four SELECT statements');
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'CREATE TABLE'");
+is($result, '1', 'have run one CREATE TABLE statement');
+$result = $node->safe_psql('postgres', "SELECT COUNT(DISTINCT tag) FROM pg_command_stats");
+is($result, '2', 'have run two kinds of statements');
+
+# Reset all counters. Note that the SELECT which resets the counters will itself get counted
+$node->safe_psql('postgres', "SELECT * FROM pg_stat_reset_shared('cmdstats')");
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, '1', 'have run one SELECT statement since resetting the counters');
+
+# Check that multi-statement commands are all counted, not just one of them
+$node->safe_psql('postgres', "BEGIN; SELECT 1; ROLLBACK; BEGIN; SELECT 2; ROLLBACK; BEGIN; SELECT 3; ROLLBACK");
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, '5', 'have run five SELECT statements since resetting the counters');
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'BEGIN'");
+is($result, '3', 'have run three BEGIN statements');
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'ROLLBACK'");
+is($result, '3', 'have run three ROLLBACK statements');
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index b813e32215..fb57f11718 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1324,6 +1324,9 @@ pg_available_extensions| SELECT e.name,
e.comment
FROM (pg_available_extensions() e(name, default_version, comment)
LEFT JOIN pg_extension x ON ((e.name = x.extname)));
+pg_command_stats| SELECT s.tag,
+ s.cnt
+ FROM pg_command_stats_data() s(tag, cnt);
pg_config| SELECT pg_config.name,
pg_config.setting
FROM pg_config() pg_config(name, setting);
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index b01e58b98c..4b873eedc4 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -201,4 +201,25 @@ FROM prevstats AS pr;
DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
DROP TABLE prevstats;
+-- Minimal test of pg_command_stats. Since cmdstats_tracking defaults to
+-- off, and cannot be changed without a cluster restart, there is nothing we can
+-- do to interrogate its behavior here. But we can at least verify that it
+-- exists, has the right columns, and does nothing unexpected when we attempt to
+-- reset its counts
+SELECT * FROM pg_command_stats;
+ tag | cnt
+-----+-----
+(0 rows)
+
+SELECT * FROM pg_stat_reset_shared('cmdstats');
+ pg_stat_reset_shared
+----------------------
+
+(1 row)
+
+SELECT * FROM pg_command_stats;
+ tag | cnt
+-----+-----
+(0 rows)
+
-- End of Stats Test
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index feaaee6326..90ad262021 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -176,4 +176,14 @@ FROM prevstats AS pr;
DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
DROP TABLE prevstats;
+
+-- Minimal test of pg_command_stats. Since cmdstats_tracking defaults to
+-- off, and cannot be changed without a cluster restart, there is nothing we can
+-- do to interrogate its behavior here. But we can at least verify that it
+-- exists, has the right columns, and does nothing unexpected when we attempt to
+-- reset its counts
+
+SELECT * FROM pg_command_stats;
+SELECT * FROM pg_stat_reset_shared('cmdstats');
+SELECT * FROM pg_command_stats;
-- End of Stats Test
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 05c5e9c752..d31e07acde 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -349,6 +349,7 @@ Clump
ClusterInfo
ClusterStmt
CmdType
+CmdStatsShmemStruct
CoalesceExpr
CoerceParamHook
CoerceToDomain
--
2.21.1 (Apple Git-122.3)
On Wed, Mar 4, 2020 at 10:43 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
The two main differences are that
(1) This implementation is based on commandtags as enums, not strings and
(2) This implementation uses techniques to reduce lock contentionI think (2) is the more important part.
My spidey sense is tingling here, telling me that we need some actual
benchmarking. Like, suppose we test the two patches under normal cases
and under cases that are constructed to be as bad as possible for each
of them. Or suppose we test this patch with the lock mitigation
strategies and then remove the mitigations for some inexpensive
command (e.g. SHOW) and then use pgbench to spam that command. Like
you, I suspect that the locking mitigations are important in some
workloads, but it would be good to have some figures to back that out,
as well as to find out whether there's still too much overhead.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Wed, Jun 10, 2020 at 01:45:02PM -0400, Robert Haas wrote:
My spidey sense is tingling here, telling me that we need some actual
benchmarking. Like, suppose we test the two patches under normal cases
and under cases that are constructed to be as bad as possible for each
of them. Or suppose we test this patch with the lock mitigation
strategies and then remove the mitigations for some inexpensive
command (e.g. SHOW) and then use pgbench to spam that command. Like
you, I suspect that the locking mitigations are important in some
workloads, but it would be good to have some figures to back that out,
as well as to find out whether there's still too much overhead.
This patch has not received any replies after this comment for three
months, so I am marking it as returned with feedback. I agree that
this should be benchmarked carefully.
--
Michael
On Tue, Mar 03, 2020 at 11:50:04PM -0300, Alvaro Herrera wrote:
So, who is updating this patch for the new cmdtaglist.h stuff?
This patch had no activity for months, so I am marking it as returned
with feedback.
--
Michael
On 2020-Sep-17, Michael Paquier wrote:
On Wed, Jun 10, 2020 at 01:45:02PM -0400, Robert Haas wrote:
My spidey sense is tingling here, telling me that we need some actual
benchmarking.This patch has not received any replies after this comment for three
months, so I am marking it as returned with feedback. I agree that
this should be benchmarked carefully.
It seems fine to mark the patch as RwF at the end of commitfest, but
that's two weeks away. I don't understand what is accomplished by doing
it ahead of time, other than alienating the patch authors.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Sep 17, 2020, at 5:40 AM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
On 2020-Sep-17, Michael Paquier wrote:
On Wed, Jun 10, 2020 at 01:45:02PM -0400, Robert Haas wrote:
My spidey sense is tingling here, telling me that we need some actual
benchmarking.This patch has not received any replies after this comment for three
months, so I am marking it as returned with feedback. I agree that
this should be benchmarked carefully.
Yes, I have prioritized a couple other patches over this one, with benchmarking this patch lower down my priority list. Thank you, Michael, for the reminder!
It seems fine to mark the patch as RwF at the end of commitfest, but
that's two weeks away. I don't understand what is accomplished by doing
it ahead of time, other than alienating the patch authors.
Thanks, Álvaro! I do not feel alienated, and I hope Haribabu Kommi does not either.
Haribabu, do you have an opinion on which of the two patches should proceed? I don't recall seeing a response from you about whether you liked what I did with your patch. Perhaps you were waiting on the benchmarking results?
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Thu, Sep 17, 2020 at 08:03:55AM -0700, Mark Dilger wrote:
Yes, I have prioritized a couple other patches over this one, with
benchmarking this patch lower down my priority list. Thank you,
Michael, for the reminder!
Based on the low level of activity and the fact that the patch was
marked as waiting on author for a couple of weeks, it looks like
little could be achieved by the end of the CF, and the attention was
elsewhere, so it looked better (and it still does IMO) to give more
attention to the remaining 170~ patches that are still lying on the CF
app.
--
Michael
On 2020-Sep-18, Michael Paquier wrote:
Based on the low level of activity and the fact that the patch was
marked as waiting on author for a couple of weeks, it looks like
little could be achieved by the end of the CF, and the attention was
elsewhere, so it looked better (and it still does IMO) to give more
attention to the remaining 170~ patches that are still lying on the CF
app.
"A couple of weeks" of inactivity is not sufficient, in my view, to boot
a patch out of the commitfest process. Whenever the patch is
resurrected, it will be a new entry which won't have the history that it
had accumulated in the long time since it was created -- which biases
it against other new patches.
I'm not really arguing about this one patch only, but more generally
about the process you're following. The problem is that you as CFM are
imposing your personal priorities on the whole process by punting
patches ahead of time -- you are saying that nobody else should be
looking at updating this patch because it is now closed. It seems fair
to do so at the end of the commitfest, but it does not seem fair to do
it when it's still mid-cycle.
Putting also in perspective the history that the patch had prior to your
unfairly closing it, there was a lot of effort in getting it reviewed to
a good state and updated by the author -- yet a single unsubstantiated
comment, without itself a lot of effort on the reviewer's part, that
"maybe it has a perf drawback" is sufficient to get it booted? It
doesn't seem appropriate.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Mon, Sep 21, 2020 at 9:41 AM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
"A couple of weeks" of inactivity is not sufficient, in my view, to boot
a patch out of the commitfest process. Whenever the patch is
resurrected, it will be a new entry which won't have the history that it
had accumulated in the long time since it was created -- which biases
it against other new patches.
As Michael said, it had been inactive for three *months*. I don't
think there's a big problem here. I think that the redesign of the
CommitFest application encourages carrying things along from CF to CF
forever, instead of getting rid of things that aren't wanted or aren't
making any progress. That's work we don't need. There ought to be a
way to mark a patch RwF when nothing's happen that lets it be revived
later if and when someone gets around to resubmitting, but I think
that's not possible now.
Then, too, since we have email integration now, maybe we ought to do
that automatically if the thread isn't getting updated and the patch
is setting there waiting on author. It's a real waste of CFM time to
chase down and kick out obviously-inactive patches, and if the CFM is
going to get flack for it then that's even worse. Like, do we have 170
patches now because we have more activity than a few years ago, or
just because we've become more reluctant to boot things? I don't know,
but to the extent it's from unwillingness to boot things, I think
that's bad. Discouraging contributors is not good, but wasting CFM and
commiter time is also bad. You've got to draw a line someplace or
you'll go nuts.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Mon, Sep 21, 2020 at 10:41:46AM -0300, Alvaro Herrera wrote:
"A couple of weeks" of inactivity is not sufficient, in my view, to boot
a patch out of the commitfest process. Whenever the patch is
resurrected, it will be a new entry which won't have the history that it
had accumulated in the long time since it was created -- which biases
it against other new patches.
Any of the patches that have been marked as RwF last week have been
waiting on author since at least the end of the commit fest of July,
with zero actions taken on them. In my experience, this points
strongly to the fact that these are unlikely going to be worked on
actively in a short time frame.
I'm not really arguing about this one patch only, but more generally
about the process you're following. The problem is that you as CFM are
imposing your personal priorities on the whole process by punting
patches ahead of time -- you are saying that nobody else should be
looking at updating this patch because it is now closed. It seems fair
to do so at the end of the commitfest, but it does not seem fair to do
it when it's still mid-cycle.
I think that this remark is a bit unfair. When it comes to any patch
in the commit fest, and I have managed a couple of them over the
years, I have tried to keep a neutral view for all of them, meaning
that I only let the numbers speak by themselves, mostly:
- When has a patch lastly been updated.
- What's the current state in the CF app If the patch had no reviews
and still in "Needs review", simply switch it to "waiting on author".
If the patch has been waiting on author for more than two weeks, it
would get marked as RwF at the end of the CF. There are also cases
where the status of the patch is incorrect, mostly that a patch
waiting on author because of a review was not updated as such in the
CF app. In this case, and in the middle of a CF, this would get get
marked as Rwf, but the author would get a reminder of that.
Any of the patches that have been discarded by me last week were
waiting in the stack for much, much, longer than that, and I think
that it does not help reviewers and committers in keeping this stuff
longer than necessary because this mainly bloats the CF app in a
non-necessary way, hiding patches that should deserve more attention.
Some of those patches have been left idle for half a year.
Another point that I'd like to make here is that my intention was to
lift the curve here (a term quite fashionable this year particularly,
isn't it?), due to the high number of patches to handle in a single
CF, and the shortage of hands actually doing this kind of work.
Then, let's be clear about one last thing. If at *any* moment people
here feel that at least the state of *one* patch of this CF has been
changed based on some of my "personal" priority views, I will
immediately stop any action as CFM and resign from it. So, if that's
the case, please feel free to speak up here or on any of the threads
related to those patches.
Putting also in perspective the history that the patch had prior to your
unfairly closing it, there was a lot of effort in getting it reviewed to
a good state and updated by the author -- yet a single unsubstantiated
comment, without itself a lot of effort on the reviewer's part, that
"maybe it has a perf drawback" is sufficient to get it booted? It
doesn't seem appropriate.
I agree on the fact that not being able to recreate a new CF entry
that keeps the history of an old one, with all the past threads or
annotations, could be helpful in some cases. Now, I have seen as well
cases where a patch got around for so long with a very long thread
made things confusing, and that people also like starting a new thread
with a summary of the past state as a first message to attempt
clarifying things. So both have advantages.
--
Michael
On Wed, Sep 23, 2020 at 10:41:10AM +0900, Michael Paquier wrote:
I think that this remark is a bit unfair. When it comes to any patch
in the commit fest, and I have managed a couple of them over the
years, I have tried to keep a neutral view for all of them, meaning
that I only let the numbers speak by themselves, mostly:
- When has a patch lastly been updated.
- What's the current state in the CF app If the patch had no reviews
and still in "Needs review", simply switch it to "waiting on author".
Sorry, forgot to mention here that this is in the case where a patch
does not apply anymore or that the CF bot complains, requiring a check
of the patch from the author.
If the patch has been waiting on author for more than two weeks, it
would get marked as RwF at the end of the CF. There are also cases
where the status of the patch is incorrect, mostly that a patch
waiting on author because of a review was not updated as such in the
CF app. In this case, and in the middle of a CF, this would get get
marked as Rwf, but the author would get a reminder of that.
Last sentence here is incorrect as well: s/get get/not get/, and the
author would be reminded to update his/her patch.
--
Michael
On Mar 4, 2020, at 7:43 PM, Mark Dilger <mark.dilger@enterprisedb.com> wrote:
as mentioned in [1], I have created an implementation of command
counter statistics very similar in purpose to the one already
pending in the commitfest going by the name "pg_stat_sql". I don't
really care if this implementation is seen as building on that one
or as separate, but I was worried about hijacking that thread, so
I'm starting this thead. There are elements of this patch that
borrowed from that one, so if this is accepted, authorship should
reflect that.
Hello, was this abandoned or is there another thread somewhere? The
patch in the email I'm replying to doesn't apply, but the conflicts
don't look very bad.
--
�lvaro Herrera Valdivia, Chile
On May 28, 2021, at 6:42 AM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
On Mar 4, 2020, at 7:43 PM, Mark Dilger <mark.dilger@enterprisedb.com> wrote:
as mentioned in [1], I have created an implementation of command
counter statistics very similar in purpose to the one already
pending in the commitfest going by the name "pg_stat_sql". I don't
really care if this implementation is seen as building on that one
or as separate, but I was worried about hijacking that thread, so
I'm starting this thead. There are elements of this patch that
borrowed from that one, so if this is accepted, authorship should
reflect that.Hello, was this abandoned or is there another thread somewhere? The
patch in the email I'm replying to doesn't apply, but the conflicts
don't look very bad.
I abandoned it for v14, choosing instead to work on amcheck features. Would you like me to take this up again for v15?
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company