Add stats for min, max, mean, stddev times to pg_stat_statements.
authorAndrew Dunstan <andrew@dunslane.net>
Fri, 27 Mar 2015 19:43:22 +0000 (15:43 -0400)
committerAndrew Dunstan <andrew@dunslane.net>
Fri, 27 Mar 2015 19:43:22 +0000 (15:43 -0400)
The new fields are min_time, max_time, mean_time and stddev_time.

Based on an original patch from Mitsumasa KONDO, modified by me. Reviewed by Petr JelĂ­nek.

contrib/pg_stat_statements/Makefile
contrib/pg_stat_statements/pg_stat_statements--1.2--1.3.sql [new file with mode: 0644]
contrib/pg_stat_statements/pg_stat_statements--1.3.sql [moved from contrib/pg_stat_statements/pg_stat_statements--1.2.sql with 84% similarity]
contrib/pg_stat_statements/pg_stat_statements.c
contrib/pg_stat_statements/pg_stat_statements.control
doc/src/sgml/pgstatstatements.sgml

index 270990964c694070a665cfccb4d80375b9b95302..975a637897cdf33235a42e781fa3ff0f9f77e43d 100644 (file)
@@ -4,8 +4,9 @@ MODULE_big = pg_stat_statements
 OBJS = pg_stat_statements.o $(WIN32RES)
 
 EXTENSION = pg_stat_statements
-DATA = pg_stat_statements--1.2.sql pg_stat_statements--1.1--1.2.sql \
-   pg_stat_statements--1.0--1.1.sql pg_stat_statements--unpackaged--1.0.sql
+DATA = pg_stat_statements--1.3.sql pg_stat_statements--1.2--1.3.sql \
+   pg_stat_statements--1.1--1.2.sql pg_stat_statements--1.0--1.1.sql \
+   pg_stat_statements--unpackaged--1.0.sql
 PGFILEDESC = "pg_stat_statements - execution statistics of SQL statements"
 
 ifdef USE_PGXS
diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.2--1.3.sql b/contrib/pg_stat_statements/pg_stat_statements--1.2--1.3.sql
new file mode 100644 (file)
index 0000000..a56f151
--- /dev/null
@@ -0,0 +1,47 @@
+/* contrib/pg_stat_statements/pg_stat_statements--1.2--1.3.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.3'" to load this file. \quit
+
+/* First we have to remove them from the extension */
+ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements;
+ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean);
+
+/* Then we can drop them */
+DROP VIEW pg_stat_statements;
+DROP FUNCTION pg_stat_statements(boolean);
+
+/* Now redefine */
+CREATE FUNCTION pg_stat_statements(IN showtext boolean,
+    OUT userid oid,
+    OUT dbid oid,
+    OUT queryid bigint,
+    OUT query text,
+    OUT calls int8,
+    OUT total_time float8,
+    OUT min_time float8,
+    OUT max_time float8,
+    OUT mean_time float8,
+    OUT stddev_time float8,
+    OUT rows int8,
+    OUT shared_blks_hit int8,
+    OUT shared_blks_read int8,
+    OUT shared_blks_dirtied int8,
+    OUT shared_blks_written int8,
+    OUT local_blks_hit int8,
+    OUT local_blks_read int8,
+    OUT local_blks_dirtied int8,
+    OUT local_blks_written int8,
+    OUT temp_blks_read int8,
+    OUT temp_blks_written int8,
+    OUT blk_read_time float8,
+    OUT blk_write_time float8
+)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'pg_stat_statements_1_3'
+LANGUAGE C STRICT VOLATILE;
+
+CREATE VIEW pg_stat_statements AS
+  SELECT * FROM pg_stat_statements(true);
+
+GRANT SELECT ON pg_stat_statements TO PUBLIC;
similarity index 84%
rename from contrib/pg_stat_statements/pg_stat_statements--1.2.sql
rename to contrib/pg_stat_statements/pg_stat_statements--1.3.sql
index 5bfa9a55d03e7a0e212aa759e0962a0b40e64716..92ed0571e975b004fdad4e0e5465d503ef8679c3 100644 (file)
@@ -1,4 +1,4 @@
-/* contrib/pg_stat_statements/pg_stat_statements--1.2.sql */
+/* contrib/pg_stat_statements/pg_stat_statements--1.3.sql */
 
 -- complain if script is sourced in psql, rather than via CREATE EXTENSION
 \echo Use "CREATE EXTENSION pg_stat_statements" to load this file. \quit
@@ -16,6 +16,10 @@ CREATE FUNCTION pg_stat_statements(IN showtext boolean,
     OUT query text,
     OUT calls int8,
     OUT total_time float8,
+    OUT min_time float8,
+    OUT max_time float8,
+    OUT mean_time float8,
+    OUT stddev_time float8,
     OUT rows int8,
     OUT shared_blks_hit int8,
     OUT shared_blks_read int8,
@@ -31,7 +35,7 @@ CREATE FUNCTION pg_stat_statements(IN showtext boolean,
     OUT blk_write_time float8
 )
 RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'pg_stat_statements_1_2'
+AS 'MODULE_PATHNAME', 'pg_stat_statements_1_3'
 LANGUAGE C STRICT VOLATILE;
 
 -- Register a view on the function for ease of use.
index 95616b36914d52f710d64299c3207fe3e9508fd8..ec0846d0a84fbee42e0b0980094ff7413acb9a97 100644 (file)
@@ -115,7 +115,8 @@ typedef enum pgssVersion
 {
    PGSS_V1_0 = 0,
    PGSS_V1_1,
-   PGSS_V1_2
+   PGSS_V1_2,
+   PGSS_V1_3
 } pgssVersion;
 
 /*
@@ -136,6 +137,10 @@ typedef struct Counters
 {
    int64       calls;          /* # of times executed */
    double      total_time;     /* total execution time, in msec */
+   double      min_time;       /* minimim 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       rows;           /* total # of retrieved or affected rows */
    int64       shared_blks_hit;    /* # of shared buffer hits */
    int64       shared_blks_read;       /* # of shared disk blocks read */
@@ -274,6 +279,7 @@ void        _PG_fini(void);
 
 PG_FUNCTION_INFO_V1(pg_stat_statements_reset);
 PG_FUNCTION_INFO_V1(pg_stat_statements_1_2);
+PG_FUNCTION_INFO_V1(pg_stat_statements_1_3);
 PG_FUNCTION_INFO_V1(pg_stat_statements);
 
 static void pgss_shmem_startup(void);
@@ -320,6 +326,7 @@ static char *generate_normalized_query(pgssJumbleState *jstate, const char *quer
                          int *query_len_p, int encoding);
 static void fill_in_constant_lengths(pgssJumbleState *jstate, const char *query);
 static int comp_location(const void *a, const void *b);
+static inline double sqrtd(const double x);
 
 
 /*
@@ -1215,6 +1222,32 @@ pgss_store(const char *query, uint32 queryId,
 
        e->counters.calls += 1;
        e->counters.total_time += total_time;
+       if (e->counters.calls == 1)
+       {
+           e->counters.min_time = total_time;
+           e->counters.max_time = total_time;
+           e->counters.mean_time = total_time;
+       }
+       else
+       {
+           /*
+            * Welford's method for accurately computing variance.
+            * See <http://www.johndcook.com/blog/standard_deviation/>
+            */
+           double old_mean = e->counters.mean_time;
+
+           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);
+
+           /* 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;
+
+       }
        e->counters.rows += rows;
        e->counters.shared_blks_hit += bufusage->shared_blks_hit;
        e->counters.shared_blks_read += bufusage->shared_blks_read;
@@ -1259,7 +1292,8 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS)
 #define PG_STAT_STATEMENTS_COLS_V1_0   14
 #define PG_STAT_STATEMENTS_COLS_V1_1   18
 #define PG_STAT_STATEMENTS_COLS_V1_2   19
-#define PG_STAT_STATEMENTS_COLS            19      /* maximum of above */
+#define PG_STAT_STATEMENTS_COLS_V1_3   23
+#define PG_STAT_STATEMENTS_COLS            23      /* maximum of above */
 
 /*
  * Retrieve statement statistics.
@@ -1271,6 +1305,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_3(PG_FUNCTION_ARGS)
+{
+   bool        showtext = PG_GETARG_BOOL(0);
+
+   pg_stat_statements_internal(fcinfo, PGSS_V1_3, showtext);
+
+   return (Datum) 0;
+}
+
 Datum
 pg_stat_statements_1_2(PG_FUNCTION_ARGS)
 {
@@ -1360,6 +1404,10 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
            if (api_version != PGSS_V1_2)
                elog(ERROR, "incorrect number of output arguments");
            break;
+       case PG_STAT_STATEMENTS_COLS_V1_3:
+           if (api_version != PGSS_V1_3)
+               elog(ERROR, "incorrect number of output arguments");
+           break;
        default:
            elog(ERROR, "incorrect number of output arguments");
    }
@@ -1519,6 +1567,23 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
 
        values[i++] = Int64GetDatumFast(tmp.calls);
        values[i++] = Float8GetDatumFast(tmp.total_time);
+       if (api_version >= PGSS_V1_3)
+       {
+           values[i++] = Float8GetDatumFast(tmp.min_time);
+           values[i++] = Float8GetDatumFast(tmp.max_time);
+           values[i++] = Float8GetDatumFast(tmp.mean_time);
+           /*
+            * 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)
+               values[i++] =
+                   Float8GetDatumFast(sqrtd(tmp.sum_var_time / tmp.calls));
+           else
+               values[i++] = Float8GetDatumFast(0.0);
+       }
        values[i++] = Int64GetDatumFast(tmp.rows);
        values[i++] = Int64GetDatumFast(tmp.shared_blks_hit);
        values[i++] = Int64GetDatumFast(tmp.shared_blks_read);
@@ -1541,6 +1606,7 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
        Assert(i == (api_version == PGSS_V1_0 ? PG_STAT_STATEMENTS_COLS_V1_0 :
                     api_version == PGSS_V1_1 ? PG_STAT_STATEMENTS_COLS_V1_1 :
                     api_version == PGSS_V1_2 ? PG_STAT_STATEMENTS_COLS_V1_2 :
+                    api_version == PGSS_V1_3 ? PG_STAT_STATEMENTS_COLS_V1_3 :
                     -1 /* fail if you forget to update this assert */ ));
 
        tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -2899,3 +2965,20 @@ comp_location(const void *a, const void *b)
    else
        return 0;
 }
+
+/*
+ * fast sqrt algorithm: reference from Fast inverse square root algorithms.
+ */
+static inline double
+sqrtd(const double x)
+{
+   double      x_half = 0.5 * x;
+   long long int   tmp = 0x5FE6EB50C7B537AAl - ( *(long long int*)&x >> 1);
+   double      x_result = * (double*)&tmp;
+
+   x_result *= (1.5 - (x_half * x_result * x_result));
+   /* If retry this calculation, it becomes higher precision at sqrt */
+   x_result *= (1.5 - (x_half * x_result * x_result));
+
+   return x_result * x;
+}
index 6ecf2b6d1bad32c23384b97f3abe19402e8801cd..53df9789df48afc3ce42a120fb954e990d9c8be4 100644 (file)
@@ -1,5 +1,5 @@
 # pg_stat_statements extension
 comment = 'track execution statistics of all SQL statements executed'
-default_version = '1.2'
+default_version = '1.3'
 module_pathname = '$libdir/pg_stat_statements'
 relocatable = true
index a58ecf7065f2e04a9212dc0cb6e56665fa1d318a..04b3f01ed5cdf49028073e93f03b396bd8b26e6d 100644 (file)
       <entry>Total time spent in the statement, in milliseconds</entry>
      </row>
 
+     <row>
+      <entry><structfield>min_time</structfield></entry>
+      <entry><type>double precision</type></entry>
+      <entry></entry>
+      <entry>Minimum time spent in the statement, in milliseconds</entry>
+     </row>
+
+     <row>
+      <entry><structfield>max_time</structfield></entry>
+      <entry><type>double precision</type></entry>
+      <entry></entry>
+      <entry>Maximum time spent in the statement, in milliseconds</entry>
+     </row>
+
+     <row>
+      <entry><structfield>mean_time</structfield></entry>
+      <entry><type>double precision</type></entry>
+      <entry></entry>
+      <entry>Mean time spent in the statement, in milliseconds</entry>
+     </row>
+
+     <row>
+      <entry><structfield>stddev_time</structfield></entry>
+      <entry><type>double precision</type></entry>
+      <entry></entry>
+      <entry>Population standard deviation of time spent in the statement, in milliseconds</entry>
+     </row>
+
      <row>
       <entry><structfield>rows</structfield></entry>
       <entry><type>bigint</type></entry>