]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Add stats_reset to pg_stat_user_functions
authorMichael Paquier <michael@paquier.xyz>
Wed, 8 Oct 2025 03:43:40 +0000 (12:43 +0900)
committerMichael Paquier <michael@paquier.xyz>
Wed, 8 Oct 2025 03:43:40 +0000 (12:43 +0900)
It is possible to call pg_stat_reset_single_function_counters() for a
single function, but the reset time was missing the system view showing
its statistics.  Like all the fields of pg_stat_user_functions, the GUC
track_functions needs to be enabled to show the statistics about
function executions.

Bump catalog version.
Bump PGSTAT_FILE_FORMAT_ID, as a result of the new field added to
PgStat_StatFuncEntry.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Discussion: https://postgr.es/m/aONjnsaJSx-nEdfU@paquier.xyz

13 files changed:
doc/src/sgml/monitoring.sgml
src/backend/catalog/system_views.sql
src/backend/utils/activity/pgstat.c
src/backend/utils/activity/pgstat_function.c
src/backend/utils/adt/pgstatfuncs.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.dat
src/include/pgstat.h
src/include/utils/pgstat_internal.h
src/test/isolation/expected/stats.out
src/test/isolation/expected/stats_1.out
src/test/isolation/specs/stats.spec
src/test/regress/expected/rules.out

index 6e3aac3d815b9c725bc3d5348ebf72e65d6c06de..789ac16b444c4a4261bf1d1dc7d6e46b5bfdb39b 100644 (file)
@@ -4736,6 +4736,15 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        other functions called by it, in milliseconds
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+        <structfield>stats_reset</structfield> <type>timestamp with time zone</type>
+       </para>
+       <para>
+        Time at which these statistics were last reset
+       </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
index 884b6a2381757a921ae4e02853b066933955ad4e..c94f1f05f523d013407050161cc6cf6932937bd6 100644 (file)
@@ -1131,7 +1131,8 @@ CREATE VIEW pg_stat_user_functions AS
             P.proname AS funcname,
             pg_stat_get_function_calls(P.oid) AS calls,
             pg_stat_get_function_total_time(P.oid) AS total_time,
-            pg_stat_get_function_self_time(P.oid) AS self_time
+            pg_stat_get_function_self_time(P.oid) AS self_time,
+            pg_stat_get_function_stat_reset_time(P.oid) AS stats_reset
     FROM pg_proc P LEFT JOIN pg_namespace N ON (N.oid = P.pronamespace)
     WHERE P.prolang != 12  -- fast check to eliminate built-in functions
           AND pg_stat_get_function_calls(P.oid) IS NOT NULL;
index 48f57e408e13b2d1c49aa03d9f51c872ea3a75f1..7ef06150df7fba3e9150f407f1d7f494df9441a6 100644 (file)
@@ -328,6 +328,7 @@ static const PgStat_KindInfo pgstat_kind_builtin_infos[PGSTAT_KIND_BUILTIN_SIZE]
                .pending_size = sizeof(PgStat_FunctionCounts),
 
                .flush_pending_cb = pgstat_function_flush_cb,
+               .reset_timestamp_cb = pgstat_function_reset_timestamp_cb,
        },
 
        [PGSTAT_KIND_REPLSLOT] = {
index 6214f93d36e0c3a49557b103b105aa94e98c416f..b5db9d15e07100910bca237dd488999f79847c9d 100644 (file)
@@ -214,6 +214,12 @@ pgstat_function_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
        return true;
 }
 
+void
+pgstat_function_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts)
+{
+       ((PgStatShared_Function *) header)->stats.stat_reset_timestamp = ts;
+}
+
 /*
  * find any existing PgStat_FunctionCounts entry for specified function
  *
index 7e89a8048d5f905d1f20586b61e987ccd25c599a..3802a4cb88817d0e02f18fef996027213b70ff54 100644 (file)
@@ -203,6 +203,24 @@ PG_STAT_GET_FUNCENTRY_FLOAT8_MS(total_time)
 /* pg_stat_get_function_self_time */
 PG_STAT_GET_FUNCENTRY_FLOAT8_MS(self_time)
 
+Datum
+pg_stat_get_function_stat_reset_time(PG_FUNCTION_ARGS)
+{
+       Oid                     funcid = PG_GETARG_OID(0);
+       TimestampTz result;
+       PgStat_StatFuncEntry *funcentry;
+
+       if ((funcentry = pgstat_fetch_stat_funcentry(funcid)) == NULL)
+               result = 0;
+       else
+               result = funcentry->stat_reset_timestamp;
+
+       if (result == 0)
+               PG_RETURN_NULL();
+       else
+               PG_RETURN_TIMESTAMPTZ(result);
+}
+
 Datum
 pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 {
index 642abe5217e3662a473561b0f1674431160a92d4..00b3bce1e120835d38ac80f729e3435d380a7d4f 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     202510062
+#define CATALOG_VERSION_NO     202510081
 
 #endif
index 7c20180637f69d8d8d883d42c73dac6a01bb235d..6bb31892d1d862dff55ea79d1d8c2d20dddb1e80 100644 (file)
   proname => 'pg_stat_get_function_self_time', provolatile => 's',
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_function_self_time' },
+{ oid => '8745', descr => 'statistics: last reset for a function',
+  proname => 'pg_stat_get_function_stat_reset_time', provolatile => 's',
+  proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_function_stat_reset_time' },
 
 { oid => '3037',
   descr => 'statistics: number of scans done for table/index in current transaction',
index 8e8adb011766835acdca2e95b0522860c9fed2b4..d24bf864a227fea55fce8b524d7069a9058246a0 100644 (file)
@@ -212,7 +212,7 @@ typedef struct PgStat_TableXactStatus
  * ------------------------------------------------------------
  */
 
-#define PGSTAT_FILE_FORMAT_ID  0x01A5BCB8
+#define PGSTAT_FILE_FORMAT_ID  0x01A5BCB9
 
 typedef struct PgStat_ArchiverStats
 {
@@ -384,6 +384,7 @@ typedef struct PgStat_StatFuncEntry
 
        PgStat_Counter total_time;      /* times in microseconds */
        PgStat_Counter self_time;
+       TimestampTz stat_reset_timestamp;
 } PgStat_StatFuncEntry;
 
 typedef struct PgStat_StatReplSlotEntry
index dc42d8043b5c6ad8244f65a3f7b9d982b97a7b7a..4d2b8aa6081a5b133d1a457972991c6f12e99b5b 100644 (file)
@@ -691,6 +691,7 @@ extern void pgstat_database_reset_timestamp_cb(PgStatShared_Common *header, Time
  */
 
 extern bool pgstat_function_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern void pgstat_function_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts);
 
 
 /*
index 8c7fe60217e98a9d907d95e1f04ad3ffd0be26b2..cfad309ccf34a949bbb2471b3610c1c94256e1ac 100644 (file)
@@ -1025,7 +1025,7 @@ test_stat_func|                          |                |
 (1 row)
 
 
-starting permutation: s1_track_funcs_all s2_track_funcs_all s1_func_call s2_func_call s2_func_call2 s1_ff s2_ff s1_func_stats s2_func_call s2_func_call2 s2_ff s1_func_stats s1_func_stats2 s1_func_stats s1_func_stats_reset s1_func_stats s1_func_stats2 s1_func_stats
+starting permutation: s1_track_funcs_all s2_track_funcs_all s1_func_call s2_func_call s2_func_call2 s1_ff s2_ff s1_func_stats s2_func_call s2_func_call2 s2_ff s1_func_stats s1_func_stats2 s1_func_stats s1_func_stats_reset_check s1_func_stats_reset s1_func_stats s1_func_stats2 s1_func_stats s1_func_stats_reset_check
 pg_stat_force_next_flush
 ------------------------
                         
@@ -1137,6 +1137,15 @@ name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
 test_stat_func|                         3|t               |t              
 (1 row)
 
+step s1_func_stats_reset_check: 
+    SELECT pg_stat_get_function_stat_reset_time('test_stat_func'::regproc)
+        IS NOT NULL AS has_stats_reset;
+
+has_stats_reset
+---------------
+f              
+(1 row)
+
 step s1_func_stats_reset: SELECT pg_stat_reset_single_function_counters('test_stat_func'::regproc);
 pg_stat_reset_single_function_counters
 --------------------------------------
@@ -1185,6 +1194,15 @@ name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
 test_stat_func|                         0|f               |f              
 (1 row)
 
+step s1_func_stats_reset_check: 
+    SELECT pg_stat_get_function_stat_reset_time('test_stat_func'::regproc)
+        IS NOT NULL AS has_stats_reset;
+
+has_stats_reset
+---------------
+t              
+(1 row)
+
 
 starting permutation: s1_func_stats_nonexistent s1_func_stats_reset_nonexistent s1_func_stats_nonexistent
 pg_stat_force_next_flush
index 6b965bb95534a3d3c65cf86641d7a345e42356a7..e1d937784cb1e1c1feef084869973eb2c8d3d518 100644 (file)
@@ -1025,7 +1025,7 @@ test_stat_func|                          |                |
 (1 row)
 
 
-starting permutation: s1_track_funcs_all s2_track_funcs_all s1_func_call s2_func_call s2_func_call2 s1_ff s2_ff s1_func_stats s2_func_call s2_func_call2 s2_ff s1_func_stats s1_func_stats2 s1_func_stats s1_func_stats_reset s1_func_stats s1_func_stats2 s1_func_stats
+starting permutation: s1_track_funcs_all s2_track_funcs_all s1_func_call s2_func_call s2_func_call2 s1_ff s2_ff s1_func_stats s2_func_call s2_func_call2 s2_ff s1_func_stats s1_func_stats2 s1_func_stats s1_func_stats_reset_check s1_func_stats_reset s1_func_stats s1_func_stats2 s1_func_stats s1_func_stats_reset_check
 pg_stat_force_next_flush
 ------------------------
                         
@@ -1137,6 +1137,15 @@ name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
 test_stat_func|                         3|t               |t              
 (1 row)
 
+step s1_func_stats_reset_check: 
+    SELECT pg_stat_get_function_stat_reset_time('test_stat_func'::regproc)
+        IS NOT NULL AS has_stats_reset;
+
+has_stats_reset
+---------------
+f              
+(1 row)
+
 step s1_func_stats_reset: SELECT pg_stat_reset_single_function_counters('test_stat_func'::regproc);
 pg_stat_reset_single_function_counters
 --------------------------------------
@@ -1185,6 +1194,15 @@ name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
 test_stat_func|                         0|f               |f              
 (1 row)
 
+step s1_func_stats_reset_check: 
+    SELECT pg_stat_get_function_stat_reset_time('test_stat_func'::regproc)
+        IS NOT NULL AS has_stats_reset;
+
+has_stats_reset
+---------------
+t              
+(1 row)
+
 
 starting permutation: s1_func_stats_nonexistent s1_func_stats_reset_nonexistent s1_func_stats_nonexistent
 pg_stat_force_next_flush
index e6d297499156a076b61352d49f944c821bd57b93..da16710da0fb252d65d2d6a9f7c1c530f9f2a017 100644 (file)
@@ -58,6 +58,10 @@ step s1_track_funcs_none { SET track_functions = 'none'; }
 step s1_func_call { SELECT test_stat_func(); }
 step s1_func_drop { DROP FUNCTION test_stat_func(); }
 step s1_func_stats_reset { SELECT pg_stat_reset_single_function_counters('test_stat_func'::regproc); }
+step s1_func_stats_reset_check {
+    SELECT pg_stat_get_function_stat_reset_time('test_stat_func'::regproc)
+        IS NOT NULL AS has_stats_reset;
+}
 step s1_func_stats_reset_nonexistent { SELECT pg_stat_reset_single_function_counters(12000); }
 step s1_reset { SELECT pg_stat_reset(); }
 step s1_func_stats {
@@ -235,9 +239,9 @@ permutation
   s1_ff s2_ff
   s1_func_stats
   s2_func_call s2_func_call2 s2_ff
-  s1_func_stats s1_func_stats2 s1_func_stats
+  s1_func_stats s1_func_stats2 s1_func_stats s1_func_stats_reset_check
   s1_func_stats_reset
-  s1_func_stats s1_func_stats2 s1_func_stats
+  s1_func_stats s1_func_stats2 s1_func_stats s1_func_stats_reset_check
 
 # test pg_stat_reset_single_function_counters of non-existing function
 permutation
index 7f1cb3bb4afcbce1fc465dd84889a6823cbdce5b..8859a5a885f46576ae8b8b63ca0384dd92cb5f73 100644 (file)
@@ -2244,7 +2244,8 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
     p.proname AS funcname,
     pg_stat_get_function_calls(p.oid) AS calls,
     pg_stat_get_function_total_time(p.oid) AS total_time,
-    pg_stat_get_function_self_time(p.oid) AS self_time
+    pg_stat_get_function_self_time(p.oid) AS self_time,
+    pg_stat_get_function_stat_reset_time(p.oid) AS stats_reset
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));