On Fri, Mar 20, 2020 at 05:09:05PM +0300, Sergei Kornilov wrote:
> Hello
>
> Yet another is missed in docs: total_time
Oh good catch! I rechecked many time the field, and totally missed that the
documentation is referring to the view, which has an additional column, and not
the function. Attached v9 fixes that.
> I specifically verified that the new loaded library works with the old
> version of the extension in the database. I have not noticed issues here.
Thanks for those extra checks.
> > 2.2% is a bit large but I think it is still acceptable because people using
> > this feature
> > might take account that some overhead will happen for additional calling of
> > a
> > gettime function.
>
> I will be happy even with 10% overhead due to enabled track_planning... (but
> in this case disabled by default) log_min_duration_statement = 0 with log
> parsing is much more expensive.
> I think 1-2% is acceptable and we can set track_planning = on by default as
> patch does.
>
> > * Rows for executor time are changed from {total/min/max/mean/stddev}_time
> > to {total/min/max/mean/stddev}_exec_time.
>
> Maybe release it as 2.0 version instead of minor update 1.18?
I don't have an opinion on that, I'd be fine with any version. I kept 1.18 in
the patch for now.
>From 16c888f7861300d08d21f3ebb97af4fad3ec1fa5 Mon Sep 17 00:00:00 2001
From: Julien Rouhaud <[email protected]>
Date: Thu, 28 Mar 2019 13:33:23 +0100
Subject: [PATCH v9 1/2] Pass query string to the planner
---
src/backend/commands/copy.c | 3 ++-
src/backend/commands/createas.c | 3 ++-
src/backend/commands/explain.c | 2 +-
src/backend/commands/extension.c | 2 +-
src/backend/commands/matview.c | 2 +-
src/backend/commands/portalcmds.c | 2 +-
src/backend/executor/functions.c | 1 +
src/backend/optimizer/plan/planner.c | 10 ++++++----
src/backend/tcop/postgres.c | 13 ++++++++-----
src/backend/utils/cache/plancache.c | 3 ++-
src/include/optimizer/optimizer.h | 3 ++-
src/include/optimizer/planner.h | 4 +++-
src/include/tcop/tcopprot.h | 6 ++++--
13 files changed, 34 insertions(+), 20 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index fbde9f88e7..efb1e0d03e 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1580,7 +1580,8 @@ BeginCopy(ParseState *pstate,
}
/* plan the query */
- plan = pg_plan_query(query, CURSOR_OPT_PARALLEL_OK, NULL);
+ plan = pg_plan_query(query, pstate->p_sourcetext,
+ CURSOR_OPT_PARALLEL_OK, NULL);
/*
* With row level security and a user using "COPY relation TO", we
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 3a5676fb39..9febdc5165 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -329,7 +329,8 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
Assert(query->commandType == CMD_SELECT);
/* plan the query */
- plan = pg_plan_query(query, CURSOR_OPT_PARALLEL_OK, params);
+ plan = pg_plan_query(query, pstate->p_sourcetext,
+ CURSOR_OPT_PARALLEL_OK, params);
/*
* Use a snapshot with an updated command ID to ensure this query sees
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index d901dc4a50..fb772aa5cd 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -375,7 +375,7 @@ ExplainOneQuery(Query *query, int cursorOptions,
INSTR_TIME_SET_CURRENT(planstart);
/* plan the query */
- plan = pg_plan_query(query, cursorOptions, params);
+ plan = pg_plan_query(query, queryString, cursorOptions, params);
INSTR_TIME_SET_CURRENT(planduration);
INSTR_TIME_SUBTRACT(planduration, planstart);
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 00cf4ef268..38cbea385a 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -751,7 +751,7 @@ execute_sql_string(const char *sql)
NULL,
0,
NULL);
- stmt_list = pg_plan_queries(stmt_list, CURSOR_OPT_PARALLEL_OK, NULL);
+ stmt_list = pg_plan_queries(stmt_list, sql, CURSOR_OPT_PARALLEL_OK, NULL);
foreach(lc2, stmt_list)
{
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index c3954f3e24..e5a5eef102 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -391,7 +391,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
CHECK_FOR_INTERRUPTS();
/* Plan the query which will generate data for the refresh. */
- plan = pg_plan_query(query, 0, NULL);
+ plan = pg_plan_query(query, queryString, 0, NULL);
/*
* Use a snapshot with an updated command ID to ensure this query sees
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index 40be5069fe..6a2c233615 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -90,7 +90,7 @@ PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo pa
elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
/* Plan the query, applying the specified options */
- plan = pg_plan_query(query, cstmt->options, params);
+ plan = pg_plan_query(query, pstate->p_sourcetext, cstmt->options, params);
/*
* Create a portal and copy the plan and query string into its memory.
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 9b45a8a9a0..243d5cb3a6 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -510,6 +510,7 @@ init_execution_state(List *queryTree_list,
}
else
stmt = pg_plan_query(queryTree,
+ fcache->src,
CURSOR_OPT_PARALLEL_OK,
NULL);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6314..19a9dafb5e 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -264,19 +264,21 @@ static int common_prefix_cmp(const void *a, const void *b);
*
*****************************************************************************/
PlannedStmt *
-planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
+planner(Query *parse, const char *query_text, int cursorOptions,
+ ParamListInfo boundParams)
{
PlannedStmt *result;
if (planner_hook)
- result = (*planner_hook) (parse, cursorOptions, boundParams);
+ result = (*planner_hook) (parse, query_text, cursorOptions, boundParams);
else
- result = standard_planner(parse, cursorOptions, boundParams);
+ result = standard_planner(parse, query_text, cursorOptions, boundParams);
return result;
}
PlannedStmt *
-standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
+standard_planner(Query *parse, const char *querytext, int cursorOptions,
+ ParamListInfo boundParams)
{
PlannedStmt *result;
PlannerGlobal *glob;
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 00c77b66c7..281facc4ed 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -854,7 +854,8 @@ pg_rewrite_query(Query *query)
* This is a thin wrapper around planner() and takes the same parameters.
*/
PlannedStmt *
-pg_plan_query(Query *querytree, int cursorOptions, ParamListInfo boundParams)
+pg_plan_query(Query *querytree, const char *query_text, int cursorOptions,
+ ParamListInfo boundParams)
{
PlannedStmt *plan;
@@ -871,7 +872,7 @@ pg_plan_query(Query *querytree, int cursorOptions, ParamListInfo boundParams)
ResetUsage();
/* call the optimizer */
- plan = planner(querytree, cursorOptions, boundParams);
+ plan = planner(querytree, query_text, cursorOptions, boundParams);
if (log_planner_stats)
ShowUsage("PLANNER STATISTICS");
@@ -939,7 +940,8 @@ pg_plan_query(Query *querytree, int cursorOptions, ParamListInfo boundParams)
* The result is a list of PlannedStmt nodes.
*/
List *
-pg_plan_queries(List *querytrees, int cursorOptions, ParamListInfo boundParams)
+pg_plan_queries(List *querytrees, const char *query_string, int cursorOptions,
+ ParamListInfo boundParams)
{
List *stmt_list = NIL;
ListCell *query_list;
@@ -961,7 +963,8 @@ pg_plan_queries(List *querytrees, int cursorOptions, ParamListInfo boundParams)
}
else
{
- stmt = pg_plan_query(query, cursorOptions, boundParams);
+ stmt = pg_plan_query(query, query_string, cursorOptions,
+ boundParams);
}
stmt_list = lappend(stmt_list, stmt);
@@ -1152,7 +1155,7 @@ exec_simple_query(const char *query_string)
querytree_list = pg_analyze_and_rewrite(parsetree, query_string,
NULL, 0, NULL);
- plantree_list = pg_plan_queries(querytree_list,
+ plantree_list = pg_plan_queries(querytree_list, query_string,
CURSOR_OPT_PARALLEL_OK, NULL);
/*
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index dbae18d68c..899e58b7a0 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -930,7 +930,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
/*
* Generate the plan.
*/
- plist = pg_plan_queries(qlist, plansource->cursor_options, boundParams);
+ plist = pg_plan_queries(qlist, plansource->query_string,
+ plansource->cursor_options, boundParams);
/* Release snapshot if we got one */
if (snapshot_set)
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 5283995df8..302e47aa58 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -102,7 +102,8 @@ typedef enum
extern int force_parallel_mode;
extern bool parallel_leader_participation;
-extern struct PlannedStmt *planner(Query *parse, int cursorOptions,
+extern struct PlannedStmt *planner(Query *parse, const char *query_text,
+ int cursorOptions,
struct ParamListInfoData *boundParams);
extern Expr *expression_planner(Expr *expr);
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 93df62d136..22bcf9c1e8 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -24,6 +24,7 @@
/* Hook for plugins to get control in planner() */
typedef PlannedStmt *(*planner_hook_type) (Query *parse,
+ const char *query_text,
int cursorOptions,
ParamListInfo boundParams);
extern PGDLLIMPORT planner_hook_type planner_hook;
@@ -37,7 +38,8 @@ typedef void (*create_upper_paths_hook_type) (PlannerInfo *root,
extern PGDLLIMPORT create_upper_paths_hook_type create_upper_paths_hook;
-extern PlannedStmt *standard_planner(Query *parse, int cursorOptions,
+extern PlannedStmt *standard_planner(Query *parse, const char *query_text,
+ int cursorOptions,
ParamListInfo boundParams);
extern PlannerInfo *subquery_planner(PlannerGlobal *glob, Query *parse,
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 750ba4a3e2..69c7b16ac7 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -52,9 +52,11 @@ extern List *pg_analyze_and_rewrite_params(RawStmt *parsetree,
ParserSetupHook parserSetup,
void *parserSetupArg,
QueryEnvironment *queryEnv);
-extern PlannedStmt *pg_plan_query(Query *querytree, int cursorOptions,
+extern PlannedStmt *pg_plan_query(Query *querytree, const char *query_text,
+ int cursorOptions,
ParamListInfo boundParams);
-extern List *pg_plan_queries(List *querytrees, int cursorOptions,
+extern List *pg_plan_queries(List *querytrees, const char *query_text,
+ int cursorOptions,
ParamListInfo boundParams);
extern bool check_max_stack_depth(int *newval, void **extra, GucSource source);
--
2.25.1
>From 7948f92d05412986a00f6f3b9066a80223da9c2c Mon Sep 17 00:00:00 2001
From: Julien Rouhaud <[email protected]>
Date: Wed, 27 Mar 2019 11:24:35 +0100
Subject: [PATCH v9 2/2] Add planning counters to pg_stat_statements
---
contrib/pg_stat_statements/Makefile | 3 +-
.../expected/pg_stat_statements.out | 231 ++++++++-----
.../pg_stat_statements--1.7--1.8.sql | 56 ++++
.../pg_stat_statements/pg_stat_statements.c | 308 +++++++++++++-----
.../pg_stat_statements.control | 2 +-
.../sql/pg_stat_statements.sql | 16 +
doc/src/sgml/pgstatstatements.sgml | 89 ++++-
7 files changed, 539 insertions(+), 166 deletions(-)
create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.7--1.8.sql
diff --git a/contrib/pg_stat_statements/Makefile b/contrib/pg_stat_statements/Makefile
index 80042a0905..0f45e85b5b 100644
--- a/contrib/pg_stat_statements/Makefile
+++ b/contrib/pg_stat_statements/Makefile
@@ -6,7 +6,8 @@ OBJS = \
pg_stat_statements.o
EXTENSION = pg_stat_statements
-DATA = pg_stat_statements--1.4.sql pg_stat_statements--1.6--1.7.sql \
+DATA = pg_stat_statements--1.4.sql \
+ pg_stat_statements--1.7--1.8.sql pg_stat_statements--1.6--1.7.sql \
pg_stat_statements--1.5--1.6.sql pg_stat_statements--1.4--1.5.sql \
pg_stat_statements--1.3--1.4.sql pg_stat_statements--1.2--1.3.sql \
pg_stat_statements--1.1--1.2.sql pg_stat_statements--1.0--1.1.sql
diff --git a/contrib/pg_stat_statements/expected/pg_stat_statements.out b/contrib/pg_stat_statements/expected/pg_stat_statements.out
index 6787ec1efd..7612bed6d5 100644
--- a/contrib/pg_stat_statements/expected/pg_stat_statements.out
+++ b/contrib/pg_stat_statements/expected/pg_stat_statements.out
@@ -96,25 +96,26 @@ EXECUTE pgss_test(1);
DEALLOCATE pgss_test;
SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
- query | calls | rows
----------------------------------------------------+-------+------
- PREPARE pgss_test (int) AS SELECT $1, $2 LIMIT $3 | 1 | 1
- SELECT $1 +| 4 | 4
- +| |
- AS "text" | |
- SELECT $1 + $2 | 2 | 2
- SELECT $1 + $2 + $3 AS "add" | 3 | 3
- SELECT $1 AS "float" | 1 | 1
- SELECT $1 AS "int" | 2 | 2
- SELECT $1 AS i UNION SELECT $2 ORDER BY i | 1 | 2
- SELECT $1 || $2 | 1 | 1
- SELECT pg_stat_statements_reset() | 1 | 1
- WITH t(f) AS ( +| 1 | 2
- VALUES ($1), ($2) +| |
- ) +| |
- SELECT f FROM t ORDER BY f | |
- select $1::jsonb ? $2 | 1 | 1
-(11 rows)
+ query | calls | rows
+------------------------------------------------------------------------------+-------+------
+ PREPARE pgss_test (int) AS SELECT $1, $2 LIMIT $3 | 1 | 1
+ SELECT $1 +| 4 | 4
+ +| |
+ AS "text" | |
+ SELECT $1 + $2 | 2 | 2
+ SELECT $1 + $2 + $3 AS "add" | 3 | 3
+ SELECT $1 AS "float" | 1 | 1
+ SELECT $1 AS "int" | 2 | 2
+ SELECT $1 AS i UNION SELECT $2 ORDER BY i | 1 | 2
+ SELECT $1 || $2 | 1 | 1
+ SELECT pg_stat_statements_reset() | 1 | 1
+ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 0 | 0
+ WITH t(f) AS ( +| 1 | 2
+ VALUES ($1), ($2) +| |
+ ) +| |
+ SELECT f FROM t ORDER BY f | |
+ select $1::jsonb ? $2 | 1 | 1
+(12 rows)
--
-- CRUD: INSERT SELECT UPDATE DELETE on test table
@@ -196,18 +197,19 @@ SELECT * FROM test WHERE a IN (1, 2, 3, 4, 5);
(8 rows)
SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
- query | calls | rows
--------------------------------------------------------------+-------+------
- DELETE FROM test WHERE a > $1 | 1 | 1
- INSERT INTO test (a, b) VALUES ($1, $2), ($3, $4), ($5, $6) | 1 | 3
- INSERT INTO test VALUES(generate_series($1, $2), $3) | 1 | 10
- SELECT * FROM test ORDER BY a | 1 | 12
- SELECT * FROM test WHERE a > $1 ORDER BY a | 2 | 4
- SELECT * FROM test WHERE a IN ($1, $2, $3, $4, $5) | 1 | 8
- SELECT pg_stat_statements_reset() | 1 | 1
- UPDATE test SET b = $1 WHERE a = $2 | 6 | 6
- UPDATE test SET b = $1 WHERE a > $2 | 1 | 3
-(9 rows)
+ query | calls | rows
+------------------------------------------------------------------------------+-------+------
+ DELETE FROM test WHERE a > $1 | 1 | 1
+ INSERT INTO test (a, b) VALUES ($1, $2), ($3, $4), ($5, $6) | 1 | 3
+ INSERT INTO test VALUES(generate_series($1, $2), $3) | 1 | 10
+ SELECT * FROM test ORDER BY a | 1 | 12
+ SELECT * FROM test WHERE a > $1 ORDER BY a | 2 | 4
+ SELECT * FROM test WHERE a IN ($1, $2, $3, $4, $5) | 1 | 8
+ SELECT pg_stat_statements_reset() | 1 | 1
+ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 0 | 0
+ UPDATE test SET b = $1 WHERE a = $2 | 6 | 6
+ UPDATE test SET b = $1 WHERE a > $2 | 1 | 3
+(10 rows)
--
-- pg_stat_statements.track = none
@@ -288,13 +290,14 @@ SELECT PLUS_ONE(10);
(1 row)
SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
- query | calls | rows
------------------------------------+-------+------
- SELECT $1::TEXT | 1 | 1
- SELECT PLUS_ONE($1) | 2 | 2
- SELECT PLUS_TWO($1) | 2 | 2
- SELECT pg_stat_statements_reset() | 1 | 1
-(4 rows)
+ query | calls | rows
+------------------------------------------------------------------------------+-------+------
+ SELECT $1::TEXT | 1 | 1
+ SELECT PLUS_ONE($1) | 2 | 2
+ SELECT PLUS_TWO($1) | 2 | 2
+ SELECT pg_stat_statements_reset() | 1 | 1
+ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 0 | 0
+(5 rows)
--
-- pg_stat_statements.track = all
@@ -345,14 +348,15 @@ SELECT PLUS_ONE(1);
(1 row)
SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
- query | calls | rows
------------------------------------+-------+------
- SELECT (i + $2 + $3)::INTEGER | 2 | 2
- SELECT (i + $2)::INTEGER LIMIT $3 | 2 | 2
- SELECT PLUS_ONE($1) | 2 | 2
- SELECT PLUS_TWO($1) | 2 | 2
- SELECT pg_stat_statements_reset() | 1 | 1
-(5 rows)
+ query | calls | rows
+------------------------------------------------------------------------------+-------+------
+ SELECT (i + $2 + $3)::INTEGER | 2 | 2
+ SELECT (i + $2)::INTEGER LIMIT $3 | 2 | 2
+ SELECT PLUS_ONE($1) | 2 | 2
+ SELECT PLUS_TWO($1) | 2 | 2
+ SELECT pg_stat_statements_reset() | 1 | 1
+ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 0 | 0
+(6 rows)
--
-- queries with locking clauses
@@ -437,8 +441,9 @@ SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C";
1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_b
1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_b, pgss_a
1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE SKIP LOCKED
+ 0 | SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C"
1 | SELECT pg_stat_statements_reset()
-(11 rows)
+(12 rows)
DROP TABLE pgss_a, pgss_b CASCADE;
--
@@ -470,17 +475,18 @@ NOTICE: table "test" does not exist, skipping
NOTICE: function plus_one(pg_catalog.int4) does not exist, skipping
DROP FUNCTION PLUS_TWO(INTEGER);
SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
- query | calls | rows
--------------------------------------------+-------+------
- CREATE INDEX test_b ON test(b) | 1 | 0
- DROP FUNCTION IF EXISTS PLUS_ONE(INTEGER) | 1 | 0
- DROP FUNCTION PLUS_ONE(INTEGER) | 1 | 0
- DROP FUNCTION PLUS_TWO(INTEGER) | 1 | 0
- DROP TABLE IF EXISTS test | 3 | 0
- DROP TABLE test | 1 | 0
- SELECT $1 | 1 | 1
- SELECT pg_stat_statements_reset() | 1 | 1
-(8 rows)
+ query | calls | rows
+------------------------------------------------------------------------------+-------+------
+ CREATE INDEX test_b ON test(b) | 1 | 0
+ DROP FUNCTION IF EXISTS PLUS_ONE(INTEGER) | 1 | 0
+ DROP FUNCTION PLUS_ONE(INTEGER) | 1 | 0
+ DROP FUNCTION PLUS_TWO(INTEGER) | 1 | 0
+ DROP TABLE IF EXISTS test | 3 | 0
+ DROP TABLE test | 1 | 0
+ SELECT $1 | 1 | 1
+ SELECT pg_stat_statements_reset() | 1 | 1
+ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 0 | 0
+(9 rows)
--
-- Track user activity and reset them
@@ -522,19 +528,20 @@ SELECT 1+1 AS "TWO";
RESET ROLE;
SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
- query | calls | rows
------------------------------------+-------+------
- CREATE ROLE regress_stats_user1 | 1 | 0
- CREATE ROLE regress_stats_user2 | 1 | 0
- RESET ROLE | 2 | 0
- SELECT $1 AS "ONE" | 1 | 1
- SELECT $1 AS "ONE" | 1 | 1
- SELECT $1+$2 AS "TWO" | 1 | 1
- SELECT $1+$2 AS "TWO" | 1 | 1
- SELECT pg_stat_statements_reset() | 1 | 1
- SET ROLE regress_stats_user1 | 1 | 0
- SET ROLE regress_stats_user2 | 1 | 0
-(10 rows)
+ query | calls | rows
+------------------------------------------------------------------------------+-------+------
+ CREATE ROLE regress_stats_user1 | 1 | 0
+ CREATE ROLE regress_stats_user2 | 1 | 0
+ RESET ROLE | 2 | 0
+ SELECT $1 AS "ONE" | 1 | 1
+ SELECT $1 AS "ONE" | 1 | 1
+ SELECT $1+$2 AS "TWO" | 1 | 1
+ SELECT $1+$2 AS "TWO" | 1 | 1
+ SELECT pg_stat_statements_reset() | 1 | 1
+ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 0 | 0
+ SET ROLE regress_stats_user1 | 1 | 0
+ SET ROLE regress_stats_user2 | 1 | 0
+(11 rows)
--
-- Don't reset anything if any of the parameter is NULL
@@ -557,7 +564,7 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
SELECT $1+$2 AS "TWO" | 1 | 1
SELECT pg_stat_statements_reset($1) | 1 | 1
SELECT pg_stat_statements_reset() | 1 | 1
- SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 1 | 10
+ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 1 | 11
SET ROLE regress_stats_user1 | 1 | 0
SET ROLE regress_stats_user2 | 1 | 0
(12 rows)
@@ -592,7 +599,7 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
WHERE s.query = $2 LIMIT $3)) | |
SELECT pg_stat_statements_reset($1) | 1 | 1
SELECT pg_stat_statements_reset() | 1 | 1
- SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 2 | 22
+ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 2 | 23
SET ROLE regress_stats_user1 | 1 | 0
SET ROLE regress_stats_user2 | 1 | 0
(12 rows)
@@ -624,7 +631,7 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
SELECT pg_stat_statements_reset($1,$2,s.queryid) +| 1 | 2
FROM pg_stat_statements AS s WHERE s.query = $3 | |
SELECT pg_stat_statements_reset() | 1 | 1
- SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 3 | 34
+ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 3 | 35
SET ROLE regress_stats_user1 | 1 | 0
SET ROLE regress_stats_user2 | 1 | 0
(11 rows)
@@ -656,7 +663,7 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
SELECT pg_stat_statements_reset() | 1 | 1
SELECT pg_stat_statements_reset(r.oid) +| 1 | 1
FROM pg_roles AS r WHERE r.rolname = $1 | |
- SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 4 | 45
+ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 4 | 46
SET ROLE regress_stats_user2 | 1 | 0
(10 rows)
@@ -670,14 +677,80 @@ SELECT pg_stat_statements_reset(0,0,0);
(1 row)
SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
- query | calls | rows
-----------------------------------------+-------+------
- SELECT pg_stat_statements_reset(0,0,0) | 1 | 1
-(1 row)
+ query | calls | rows
+------------------------------------------------------------------------------+-------+------
+ SELECT pg_stat_statements_reset(0,0,0) | 1 | 1
+ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 0 | 0
+(2 rows)
--
-- cleanup
--
DROP ROLE regress_stats_user1;
DROP ROLE regress_stats_user2;
+--
+-- [re]plan counting
+--
+SELECT pg_stat_statements_reset();
+ pg_stat_statements_reset
+--------------------------
+
+(1 row)
+
+CREATE TABLE test ();
+PREPARE prep1 AS SELECT COUNT(*) FROM test;;
+EXECUTE prep1;
+ count
+-------
+ 0
+(1 row)
+
+EXECUTE prep1;
+ count
+-------
+ 0
+(1 row)
+
+EXECUTE prep1;
+ count
+-------
+ 0
+(1 row)
+
+ALTER TABLE test ADD COLUMN x int;
+EXECUTE prep1;
+ count
+-------
+ 0
+(1 row)
+
+SELECT 42;
+ ?column?
+----------
+ 42
+(1 row)
+
+SELECT 42;
+ ?column?
+----------
+ 42
+(1 row)
+
+SELECT 42;
+ ?column?
+----------
+ 42
+(1 row)
+
+SELECT query, plans, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
+ query | plans | calls | rows
+-------------------------------------------------------------------------------------+-------+-------+------
+ ALTER TABLE test ADD COLUMN x int | 0 | 1 | 0
+ CREATE TABLE test () | 0 | 1 | 0
+ PREPARE prep1 AS SELECT COUNT(*) FROM test | 2 | 4 | 4
+ SELECT $1 | 3 | 3 | 3
+ SELECT pg_stat_statements_reset() | 0 | 1 | 1
+ SELECT query, plans, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 1 | 0 | 0
+(6 rows)
+
DROP EXTENSION pg_stat_statements;
diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.7--1.8.sql b/contrib/pg_stat_statements/pg_stat_statements--1.7--1.8.sql
new file mode 100644
index 0000000000..5cf719ddc3
--- /dev/null
+++ b/contrib/pg_stat_statements/pg_stat_statements--1.7--1.8.sql
@@ -0,0 +1,56 @@
+/* contrib/pg_stat_statements/pg_stat_statements--1.7--1.8.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.8'" to load this file. \quit
+
+/* First we have to remove them from the extension */
+ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements;
+ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean);
+
+/* Then we can drop them */
+DROP VIEW pg_stat_statements;
+DROP FUNCTION pg_stat_statements(boolean);
+
+/* Now redefine */
+CREATE FUNCTION pg_stat_statements(IN showtext boolean,
+ OUT userid oid,
+ OUT dbid oid,
+ OUT queryid bigint,
+ OUT query text,
+ OUT plans int8,
+ OUT total_plan_time float8,
+ OUT min_plan_time float8,
+ OUT max_plan_time float8,
+ OUT mean_plan_time float8,
+ OUT stddev_plan_time float8,
+ OUT calls int8,
+ OUT total_exec_time float8,
+ OUT min_exec_time float8,
+ OUT max_exec_time float8,
+ OUT mean_exec_time float8,
+ OUT stddev_exec_time float8,
+ OUT rows int8,
+ OUT shared_blks_hit int8,
+ OUT shared_blks_read int8,
+ OUT shared_blks_dirtied int8,
+ OUT shared_blks_written int8,
+ OUT local_blks_hit int8,
+ OUT local_blks_read int8,
+ OUT local_blks_dirtied int8,
+ OUT local_blks_written int8,
+ OUT temp_blks_read int8,
+ OUT temp_blks_written int8,
+ OUT blk_read_time float8,
+ OUT blk_write_time float8
+)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'pg_stat_statements_1_8'
+LANGUAGE C STRICT VOLATILE PARALLEL SAFE;
+
+CREATE VIEW pg_stat_statements AS
+ SELECT *, total_plan_time + total_exec_time AS total_time
+ FROM pg_stat_statements(true);
+
+GRANT SELECT ON pg_stat_statements TO PUBLIC;
+
+
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 20dc8c605b..bb2de5ac9a 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -1,7 +1,8 @@
/*-------------------------------------------------------------------------
*
* pg_stat_statements.c
- * Track statement execution times across a whole database cluster.
+ * Track statement planning and execution times as well as resources
+ * consumption (like block reads, ...) across a whole cluster.
*
* Execution costs are totalled for each distinct source query, and kept in
* a shared hashtable. (We track only as many distinct queries as will fit
@@ -67,6 +68,7 @@
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parsetree.h"
#include "parser/scanner.h"
@@ -109,6 +111,7 @@ static const uint32 PGSS_PG_MAJOR_VERSION = PG_VERSION_NUM / 100;
#define USAGE_DECREASE_FACTOR (0.99) /* decreased every entry_dealloc */
#define STICKY_DECREASE_FACTOR (0.50) /* factor for sticky entries */
#define USAGE_DEALLOC_PERCENT 5 /* free this % of entries at once */
+#define IS_STICKY(c) ((c.calls[PGSS_PLAN] + c.calls[PGSS_EXEC]) == 0)
#define JUMBLE_SIZE 1024 /* query serialization buffer size */
@@ -120,9 +123,22 @@ typedef enum pgssVersion
PGSS_V1_0 = 0,
PGSS_V1_1,
PGSS_V1_2,
- PGSS_V1_3
+ PGSS_V1_3,
+ PGSS_V1_8
} pgssVersion;
+typedef enum pgssStoreKind
+{
+ /*
+ * PGSS_PLAN and PGSS_EXEC must be respectively 0 and 1 as they're used to
+ * reference the underlying values in the arrays in the Counters struct,
+ * and this order is required in pg_stat_statements_internal().
+ */
+ PGSS_PLAN = 0,
+ PGSS_EXEC,
+ PGSS_JUMBLE,
+} pgssStoreKind;
+
/*
* Hashtable key that defines the identity of a hashtable entry. We separate
* queries by user and by database even if they are otherwise identical.
@@ -144,12 +160,12 @@ typedef struct pgssHashKey
*/
typedef struct Counters
{
- int64 calls; /* # of times executed */
- double total_time; /* total execution time, in msec */
- double min_time; /* minimum execution time in msec */
- double max_time; /* maximum execution time in msec */
- double mean_time; /* mean execution time in msec */
- double sum_var_time; /* sum of variances in execution time in msec */
+ int64 calls[2]; /* # of times planned/executed */
+ double total_time[2]; /* total planning/execution time, in msec */
+ double min_time[2]; /* minimum planning/execution time in msec */
+ double max_time[2]; /* maximum planning/execution time in msec */
+ double mean_time[2]; /* mean planning/execution time in msec */
+ double sum_var_time[2]; /* sum of variances in planning/execution time in msec */
int64 rows; /* total # of retrieved or affected rows */
int64 shared_blks_hit; /* # of shared buffer hits */
int64 shared_blks_read; /* # of shared disk blocks read */
@@ -238,6 +254,7 @@ static int nested_level = 0;
/* Saved hook values in case of unload */
static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
+static planner_hook_type prev_planner_hook = NULL;
static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL;
static ExecutorStart_hook_type prev_ExecutorStart = NULL;
static ExecutorRun_hook_type prev_ExecutorRun = NULL;
@@ -268,6 +285,7 @@ static const struct config_enum_entry track_options[] =
static int pgss_max; /* max # statements to track */
static int pgss_track; /* tracking level */
+static bool pgss_track_planning; /* whether to track planning duration */
static bool pgss_track_utility; /* whether to track utility commands */
static bool pgss_save; /* whether to save stats across shutdown */
@@ -293,10 +311,15 @@ PG_FUNCTION_INFO_V1(pg_stat_statements_reset);
PG_FUNCTION_INFO_V1(pg_stat_statements_reset_1_7);
PG_FUNCTION_INFO_V1(pg_stat_statements_1_2);
PG_FUNCTION_INFO_V1(pg_stat_statements_1_3);
+PG_FUNCTION_INFO_V1(pg_stat_statements_1_8);
PG_FUNCTION_INFO_V1(pg_stat_statements);
static void pgss_shmem_startup(void);
static void pgss_shmem_shutdown(int code, Datum arg);
+static PlannedStmt *pgss_planner_hook(Query *parse,
+ const char *query_text,
+ int cursorOptions,
+ ParamListInfo boundParams);
static void pgss_post_parse_analyze(ParseState *pstate, Query *query);
static void pgss_ExecutorStart(QueryDesc *queryDesc, int eflags);
static void pgss_ExecutorRun(QueryDesc *queryDesc,
@@ -311,7 +334,9 @@ static void pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
static uint64 pgss_hash_string(const char *str, int len);
static void pgss_store(const char *query, uint64 queryId,
int query_location, int query_len,
- double total_time, uint64 rows,
+ pgssStoreKind kind,
+ double timing,
+ uint64 rows,
const BufferUsage *bufusage,
pgssJumbleState *jstate);
static void pg_stat_statements_internal(FunctionCallInfo fcinfo,
@@ -341,6 +366,7 @@ static char *generate_normalized_query(pgssJumbleState *jstate, const char *quer
static void fill_in_constant_lengths(pgssJumbleState *jstate, const char *query,
int query_loc);
static int comp_location(const void *a, const void *b);
+static BufferUsage compute_buffer_counters(BufferUsage start, BufferUsage stop);
/*
@@ -399,6 +425,17 @@ _PG_init(void)
NULL,
NULL);
+ DefineCustomBoolVariable("pg_stat_statements.track_planning",
+ "Selects whether planning duration is tracked by pg_stat_statements.",
+ NULL,
+ &pgss_track_planning,
+ true,
+ PGC_SUSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+
DefineCustomBoolVariable("pg_stat_statements.save",
"Save pg_stat_statements statistics across server shutdowns.",
NULL,
@@ -425,6 +462,8 @@ _PG_init(void)
*/
prev_shmem_startup_hook = shmem_startup_hook;
shmem_startup_hook = pgss_shmem_startup;
+ prev_planner_hook = planner_hook;
+ planner_hook = pgss_planner_hook;
prev_post_parse_analyze_hook = post_parse_analyze_hook;
post_parse_analyze_hook = pgss_post_parse_analyze;
prev_ExecutorStart = ExecutorStart_hook;
@@ -448,6 +487,7 @@ _PG_fini(void)
/* Uninstall hooks. */
shmem_startup_hook = prev_shmem_startup_hook;
post_parse_analyze_hook = prev_post_parse_analyze_hook;
+ planner_hook = prev_planner_hook;
ExecutorStart_hook = prev_ExecutorStart;
ExecutorRun_hook = prev_ExecutorRun;
ExecutorFinish_hook = prev_ExecutorFinish;
@@ -602,7 +642,7 @@ pgss_shmem_startup(void)
buffer[temp.query_len] = '\0';
/* Skip loading "sticky" entries */
- if (temp.counters.calls == 0)
+ if (IS_STICKY(temp.counters))
continue;
/* Store the query text */
@@ -838,12 +878,89 @@ pgss_post_parse_analyze(ParseState *pstate, Query *query)
query->queryId,
query->stmt_location,
query->stmt_len,
+ PGSS_JUMBLE,
0,
0,
NULL,
&jstate);
}
+/*
+ * Planner hook: forward to regular planner, but measure planning time.
+ */
+static PlannedStmt *
+pgss_planner_hook(Query *parse,
+ const char *query_text,
+ int cursorOptions,
+ ParamListInfo boundParams)
+{
+ PlannedStmt *result;
+ BufferUsage bufusage_start,
+ bufusage;
+
+ /* We need to track buffer usage as the planner can access them. */
+ bufusage_start = pgBufferUsage;
+
+ /*
+ * We can't process the query if no query_text is provided, as pgss_store
+ * needs it. We also ignore query without queryid, as it would be treated
+ * as a utility statement, which may not be the case.
+ */
+ if (pgss_enabled() && pgss_track_planning && query_text
+ && parse->queryId != UINT64CONST(0))
+ {
+ instr_time start;
+ instr_time duration;
+
+ INSTR_TIME_SET_CURRENT(start);
+
+ nested_level++;
+ PG_TRY();
+ {
+ if (prev_planner_hook)
+ result = prev_planner_hook(parse, query_text, cursorOptions,
+ boundParams);
+ else
+ result = standard_planner(parse, query_text, cursorOptions,
+ boundParams);
+ nested_level--;
+ }
+ PG_CATCH();
+ {
+ nested_level--;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ INSTR_TIME_SET_CURRENT(duration);
+ INSTR_TIME_SUBTRACT(duration, start);
+
+ /* calc differences of buffer counters. */
+ bufusage = compute_buffer_counters(bufusage_start, pgBufferUsage);
+
+ pgss_store(query_text,
+ parse->queryId,
+ parse->stmt_location,
+ parse->stmt_len,
+ PGSS_PLAN,
+ INSTR_TIME_GET_MILLISEC(duration),
+ 0,
+ &bufusage,
+ NULL);
+ }
+ else
+ {
+ if (prev_planner_hook)
+ result = prev_planner_hook(parse, query_text, cursorOptions,
+ boundParams);
+ else
+ result = standard_planner(parse, query_text, cursorOptions,
+ boundParams);
+ }
+
+ return result;
+}
+
/*
* ExecutorStart hook: start up tracking if needed
*/
@@ -941,6 +1058,7 @@ pgss_ExecutorEnd(QueryDesc *queryDesc)
queryId,
queryDesc->plannedstmt->stmt_location,
queryDesc->plannedstmt->stmt_len,
+ PGSS_EXEC,
queryDesc->totaltime->total * 1000.0, /* convert to msec */
queryDesc->estate->es_processed,
&queryDesc->totaltime->bufusage,
@@ -1016,35 +1134,13 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
rows = (qc && qc->commandTag == CMDTAG_COPY) ? qc->nprocessed : 0;
/* calc differences of buffer counters. */
- bufusage.shared_blks_hit =
- pgBufferUsage.shared_blks_hit - bufusage_start.shared_blks_hit;
- bufusage.shared_blks_read =
- pgBufferUsage.shared_blks_read - bufusage_start.shared_blks_read;
- bufusage.shared_blks_dirtied =
- pgBufferUsage.shared_blks_dirtied - bufusage_start.shared_blks_dirtied;
- bufusage.shared_blks_written =
- pgBufferUsage.shared_blks_written - bufusage_start.shared_blks_written;
- bufusage.local_blks_hit =
- pgBufferUsage.local_blks_hit - bufusage_start.local_blks_hit;
- bufusage.local_blks_read =
- pgBufferUsage.local_blks_read - bufusage_start.local_blks_read;
- bufusage.local_blks_dirtied =
- pgBufferUsage.local_blks_dirtied - bufusage_start.local_blks_dirtied;
- bufusage.local_blks_written =
- pgBufferUsage.local_blks_written - bufusage_start.local_blks_written;
- bufusage.temp_blks_read =
- pgBufferUsage.temp_blks_read - bufusage_start.temp_blks_read;
- bufusage.temp_blks_written =
- pgBufferUsage.temp_blks_written - bufusage_start.temp_blks_written;
- bufusage.blk_read_time = pgBufferUsage.blk_read_time;
- INSTR_TIME_SUBTRACT(bufusage.blk_read_time, bufusage_start.blk_read_time);
- bufusage.blk_write_time = pgBufferUsage.blk_write_time;
- INSTR_TIME_SUBTRACT(bufusage.blk_write_time, bufusage_start.blk_write_time);
+ bufusage = compute_buffer_counters(bufusage_start, pgBufferUsage);
pgss_store(queryString,
0, /* signal that it's a utility stmt */
pstmt->stmt_location,
pstmt->stmt_len,
+ PGSS_EXEC,
INSTR_TIME_GET_MILLISEC(duration),
rows,
&bufusage,
@@ -1083,12 +1179,17 @@ pgss_hash_string(const char *str, int len)
*
* If jstate is not NULL then we're trying to create an entry for which
* we have no statistics as yet; we just want to record the normalized
- * query string. total_time, rows, bufusage are ignored in this case.
+ * query string. timing, rows, bufusage are ignored in this case.
+ *
+ * If kind is PGSS_PLAN or PGSS_EXEC, its value is used as the array position
+ * for the arrays in the Counters field.
*/
static void
pgss_store(const char *query, uint64 queryId,
int query_location, int query_len,
- double total_time, uint64 rows,
+ pgssStoreKind kind,
+ double timing,
+ uint64 rows,
const BufferUsage *bufusage,
pgssJumbleState *jstate)
{
@@ -1176,8 +1277,10 @@ pgss_store(const char *query, uint64 queryId,
* in the interval where we don't hold the lock below. That case is
* handled by entry_alloc.)
*/
- if (jstate)
+ if (kind == PGSS_JUMBLE)
{
+ Assert(jstate);
+
LWLockRelease(pgss->lock);
norm_query = generate_normalized_query(jstate, query,
query_location,
@@ -1234,19 +1337,22 @@ pgss_store(const char *query, uint64 queryId,
*/
volatile pgssEntry *e = (volatile pgssEntry *) entry;
+ Assert(kind == PGSS_PLAN || kind == PGSS_EXEC);
+
SpinLockAcquire(&e->mutex);
/* "Unstick" entry if it was previously sticky */
- if (e->counters.calls == 0)
+ if (IS_STICKY(e->counters))
e->counters.usage = USAGE_INIT;
- e->counters.calls += 1;
- e->counters.total_time += total_time;
- if (e->counters.calls == 1)
+ e->counters.calls[kind] += 1;
+ e->counters.total_time[kind] += timing;
+
+ if (e->counters.calls[kind] == 1)
{
- e->counters.min_time = total_time;
- e->counters.max_time = total_time;
- e->counters.mean_time = total_time;
+ e->counters.min_time[kind] = timing;
+ e->counters.max_time[kind] = timing;
+ e->counters.mean_time[kind] = timing;
}
else
{
@@ -1254,18 +1360,18 @@ pgss_store(const char *query, uint64 queryId,
* Welford's method for accurately computing variance. See
* <http://www.johndcook.com/blog/standard_deviation/>
*/
- double old_mean = e->counters.mean_time;
+ double old_mean = e->counters.mean_time[kind];
- e->counters.mean_time +=
- (total_time - old_mean) / e->counters.calls;
- e->counters.sum_var_time +=
- (total_time - old_mean) * (total_time - e->counters.mean_time);
+ e->counters.mean_time[kind] +=
+ (timing - old_mean) / e->counters.calls[kind];
+ e->counters.sum_var_time[kind] +=
+ (timing - old_mean) * (timing - e->counters.mean_time[kind]);
/* calculate min and max time */
- if (e->counters.min_time > total_time)
- e->counters.min_time = total_time;
- if (e->counters.max_time < total_time)
- e->counters.max_time = total_time;
+ if (e->counters.min_time[kind] > timing)
+ e->counters.min_time[kind] = timing;
+ if (e->counters.max_time[kind] < timing)
+ e->counters.max_time[kind] = timing;
}
e->counters.rows += rows;
e->counters.shared_blks_hit += bufusage->shared_blks_hit;
@@ -1280,7 +1386,7 @@ pgss_store(const char *query, uint64 queryId,
e->counters.temp_blks_written += bufusage->temp_blks_written;
e->counters.blk_read_time += INSTR_TIME_GET_MILLISEC(bufusage->blk_read_time);
e->counters.blk_write_time += INSTR_TIME_GET_MILLISEC(bufusage->blk_write_time);
- e->counters.usage += USAGE_EXEC(total_time);
+ e->counters.usage += USAGE_EXEC(timing);
SpinLockRelease(&e->mutex);
}
@@ -1328,7 +1434,8 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS)
#define PG_STAT_STATEMENTS_COLS_V1_1 18
#define PG_STAT_STATEMENTS_COLS_V1_2 19
#define PG_STAT_STATEMENTS_COLS_V1_3 23
-#define PG_STAT_STATEMENTS_COLS 23 /* maximum of above */
+#define PG_STAT_STATEMENTS_COLS_V1_8 29
+#define PG_STAT_STATEMENTS_COLS 29 /* maximum of above */
/*
* Retrieve statement statistics.
@@ -1340,6 +1447,16 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS)
* expected API version is identified by embedding it in the C name of the
* function. Unfortunately we weren't bright enough to do that for 1.1.
*/
+Datum
+pg_stat_statements_1_8(PG_FUNCTION_ARGS)
+{
+ bool showtext = PG_GETARG_BOOL(0);
+
+ pg_stat_statements_internal(fcinfo, PGSS_V1_8, showtext);
+
+ return (Datum) 0;
+}
+
Datum
pg_stat_statements_1_3(PG_FUNCTION_ARGS)
{
@@ -1445,6 +1562,10 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
if (api_version != PGSS_V1_3)
elog(ERROR, "incorrect number of output arguments");
break;
+ case PG_STAT_STATEMENTS_COLS_V1_8:
+ if (api_version != PGSS_V1_8)
+ elog(ERROR, "incorrect number of output arguments");
+ break;
default:
elog(ERROR, "incorrect number of output arguments");
}
@@ -1600,29 +1721,39 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
}
/* Skip entry if unexecuted (ie, it's a pending "sticky" entry) */
- if (tmp.calls == 0)
+ if (IS_STICKY(tmp))
continue;
- values[i++] = Int64GetDatumFast(tmp.calls);
- values[i++] = Float8GetDatumFast(tmp.total_time);
- if (api_version >= PGSS_V1_3)
+ /* Note that we rely on PGSS_PLAN being 0 and PGSS_EXEC being 1. */
+ for (int kind=0; kind < 2; kind++)
{
- values[i++] = Float8GetDatumFast(tmp.min_time);
- values[i++] = Float8GetDatumFast(tmp.max_time);
- values[i++] = Float8GetDatumFast(tmp.mean_time);
+ if (kind == PGSS_EXEC || api_version >= PGSS_V1_8)
+ {
+ values[i++] = Int64GetDatumFast(tmp.calls[kind]);
+ values[i++] = Float8GetDatumFast(tmp.total_time[kind]);
+ }
- /*
- * Note we are calculating the population variance here, not the
- * sample variance, as we have data for the whole population, so
- * Bessel's correction is not used, and we don't divide by
- * tmp.calls - 1.
- */
- if (tmp.calls > 1)
- stddev = sqrt(tmp.sum_var_time / tmp.calls);
- else
- stddev = 0.0;
- values[i++] = Float8GetDatumFast(stddev);
+ if ( (kind == PGSS_EXEC && api_version >= PGSS_V1_3) ||
+ api_version >= PGSS_V1_8)
+ {
+ values[i++] = Float8GetDatumFast(tmp.min_time[kind]);
+ values[i++] = Float8GetDatumFast(tmp.max_time[kind]);
+ values[i++] = Float8GetDatumFast(tmp.mean_time[kind]);
+
+ /*
+ * Note we are calculating the population variance here, not the
+ * sample variance, as we have data for the whole population, so
+ * Bessel's correction is not used, and we don't divide by
+ * tmp.calls - 1.
+ */
+ if (tmp.calls[kind] > 1)
+ stddev = sqrt(tmp.sum_var_time[kind] / tmp.calls[kind]);
+ else
+ stddev = 0.0;
+ values[i++] = Float8GetDatumFast(stddev);
+ }
}
+
values[i++] = Int64GetDatumFast(tmp.rows);
values[i++] = Int64GetDatumFast(tmp.shared_blks_hit);
values[i++] = Int64GetDatumFast(tmp.shared_blks_read);
@@ -1646,6 +1777,7 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
api_version == PGSS_V1_1 ? PG_STAT_STATEMENTS_COLS_V1_1 :
api_version == PGSS_V1_2 ? PG_STAT_STATEMENTS_COLS_V1_2 :
api_version == PGSS_V1_3 ? PG_STAT_STATEMENTS_COLS_V1_3 :
+ api_version == PGSS_V1_8 ? PG_STAT_STATEMENTS_COLS_V1_8 :
-1 /* fail if you forget to update this assert */ ));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -1781,7 +1913,7 @@ entry_dealloc(void)
{
entries[i++] = entry;
/* "Sticky" entries get a different usage decay rate. */
- if (entry->counters.calls == 0)
+ if (IS_STICKY(entry->counters))
entry->counters.usage *= STICKY_DECREASE_FACTOR;
else
entry->counters.usage *= USAGE_DECREASE_FACTOR;
@@ -3249,3 +3381,31 @@ comp_location(const void *a, const void *b)
else
return 0;
}
+
+static BufferUsage
+compute_buffer_counters(BufferUsage start, BufferUsage stop)
+{
+ BufferUsage result;
+
+ result.shared_blks_hit = stop.shared_blks_hit - start.shared_blks_hit;
+ result.shared_blks_read = stop.shared_blks_read - start.shared_blks_read;
+ result.shared_blks_dirtied = stop.shared_blks_dirtied -
+ start.shared_blks_dirtied;
+ result.shared_blks_written = stop.shared_blks_written -
+ start.shared_blks_written;
+ result.local_blks_hit = stop.local_blks_hit - start.local_blks_hit;
+ result.local_blks_read = stop.local_blks_read - start.local_blks_read;
+ result.local_blks_dirtied = stop.local_blks_dirtied -
+ start.local_blks_dirtied;
+ result.local_blks_written = stop.local_blks_written -
+ start.local_blks_written;
+ result.temp_blks_read = stop.temp_blks_read - start.temp_blks_read;
+ result.temp_blks_written = stop.temp_blks_written -
+ start.temp_blks_written;
+ result.blk_read_time = stop.blk_read_time;
+ INSTR_TIME_SUBTRACT(result.blk_read_time, start.blk_read_time);
+ result.blk_write_time = stop.blk_write_time;
+ INSTR_TIME_SUBTRACT(result.blk_write_time, start.blk_write_time);
+
+ return result;
+}
diff --git a/contrib/pg_stat_statements/pg_stat_statements.control b/contrib/pg_stat_statements/pg_stat_statements.control
index 14cb422354..7fb20df886 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.control
+++ b/contrib/pg_stat_statements/pg_stat_statements.control
@@ -1,5 +1,5 @@
# pg_stat_statements extension
comment = 'track execution statistics of all SQL statements executed'
-default_version = '1.7'
+default_version = '1.8'
module_pathname = '$libdir/pg_stat_statements'
relocatable = true
diff --git a/contrib/pg_stat_statements/sql/pg_stat_statements.sql b/contrib/pg_stat_statements/sql/pg_stat_statements.sql
index 8b527070d4..e36e64f633 100644
--- a/contrib/pg_stat_statements/sql/pg_stat_statements.sql
+++ b/contrib/pg_stat_statements/sql/pg_stat_statements.sql
@@ -290,4 +290,20 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
DROP ROLE regress_stats_user1;
DROP ROLE regress_stats_user2;
+--
+-- [re]plan counting
+--
+SELECT pg_stat_statements_reset();
+CREATE TABLE test ();
+PREPARE prep1 AS SELECT COUNT(*) FROM test;;
+EXECUTE prep1;
+EXECUTE prep1;
+EXECUTE prep1;
+ALTER TABLE test ADD COLUMN x int;
+EXECUTE prep1;
+SELECT 42;
+SELECT 42;
+SELECT 42;
+SELECT query, plans, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
+
DROP EXTENSION pg_stat_statements;
diff --git a/doc/src/sgml/pgstatstatements.sgml b/doc/src/sgml/pgstatstatements.sgml
index 26bb82da4a..11de629227 100644
--- a/doc/src/sgml/pgstatstatements.sgml
+++ b/doc/src/sgml/pgstatstatements.sgml
@@ -9,7 +9,8 @@
<para>
The <filename>pg_stat_statements</filename> module provides a means for
- tracking execution statistics of all SQL statements executed by a server.
+ tracking planning and execution statistics of all SQL statements executed by
+ a server.
</para>
<para>
@@ -82,6 +83,48 @@
<entry>Text of a representative statement</entry>
</row>
+ <row>
+ <entry><structfield>plans</structfield></entry>
+ <entry><type>bigint</type></entry>
+ <entry></entry>
+ <entry>Number of times the statement was planned</entry>
+ </row>
+
+ <row>
+ <entry><structfield>total_plan_time</structfield></entry>
+ <entry><type>double precision</type></entry>
+ <entry></entry>
+ <entry>Total time spent planning the statement, in milliseconds</entry>
+ </row>
+
+ <row>
+ <entry><structfield>min_plan_time</structfield></entry>
+ <entry><type>double precision</type></entry>
+ <entry></entry>
+ <entry>Minimum time spent planning the statement, in milliseconds</entry>
+ </row>
+
+ <row>
+ <entry><structfield>max_plan_time</structfield></entry>
+ <entry><type>double precision</type></entry>
+ <entry></entry>
+ <entry>Maximum time spent planning the statement, in milliseconds</entry>
+ </row>
+
+ <row>
+ <entry><structfield>mean_plan_time</structfield></entry>
+ <entry><type>double precision</type></entry>
+ <entry></entry>
+ <entry>Mean time spent planning the statement, in milliseconds</entry>
+ </row>
+
+ <row>
+ <entry><structfield>stddev_plan_time</structfield></entry>
+ <entry><type>double precision</type></entry>
+ <entry></entry>
+ <entry>Population standard deviation of time spent planning the statement, in milliseconds</entry>
+ </row>
+
<row>
<entry><structfield>calls</structfield></entry>
<entry><type>bigint</type></entry>
@@ -90,38 +133,38 @@
</row>
<row>
- <entry><structfield>total_time</structfield></entry>
+ <entry><structfield>total_exec_time</structfield></entry>
<entry><type>double precision</type></entry>
<entry></entry>
- <entry>Total time spent in the statement, in milliseconds</entry>
+ <entry>Total time spent executing the statement, in milliseconds</entry>
</row>
<row>
- <entry><structfield>min_time</structfield></entry>
+ <entry><structfield>min_exec_time</structfield></entry>
<entry><type>double precision</type></entry>
<entry></entry>
- <entry>Minimum time spent in the statement, in milliseconds</entry>
+ <entry>Minimum time spent executing the statement, in milliseconds</entry>
</row>
<row>
- <entry><structfield>max_time</structfield></entry>
+ <entry><structfield>max_exec_time</structfield></entry>
<entry><type>double precision</type></entry>
<entry></entry>
- <entry>Maximum time spent in the statement, in milliseconds</entry>
+ <entry>Maximum time spent executing the statement, in milliseconds</entry>
</row>
<row>
- <entry><structfield>mean_time</structfield></entry>
+ <entry><structfield>mean_exec_time</structfield></entry>
<entry><type>double precision</type></entry>
<entry></entry>
- <entry>Mean time spent in the statement, in milliseconds</entry>
+ <entry>Mean time spent executing the statement, in milliseconds</entry>
</row>
<row>
- <entry><structfield>stddev_time</structfield></entry>
+ <entry><structfield>stddev_exec_time</structfield></entry>
<entry><type>double precision</type></entry>
<entry></entry>
- <entry>Population standard deviation of time spent in the statement, in milliseconds</entry>
+ <entry>Population standard deviation of time spent executing the statement, in milliseconds</entry>
</row>
<row>
@@ -221,6 +264,15 @@
</entry>
</row>
+ <row>
+ <entry><structfield>total_time</structfield></entry>
+ <entry><type>double precision</type></entry>
+ <entry></entry>
+ <entry>
+ Total time spend planning and executing the statement, in milliseconds
+ </entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -431,6 +483,21 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>
+ <varname>pg_stat_statements.track_planning</varname> (<type>boolean</type>)
+ </term>
+
+ <listitem>
+ <para>
+ <varname>pg_stat_statements.track_planning</varname> controls whether
+ planning operations and duration are tracked by the module.
+ The default value is <literal>on</literal>.
+ Only superusers can change this setting.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term>
<varname>pg_stat_statements.track_utility</varname> (<type>boolean</type>)
--
2.25.1