From 16e620af6e1e7669c19efbc240958b25fb2b35db Mon Sep 17 00:00:00 2001
From: AyoubKAZ <kazarayoub2004@gmail.com>
Date: Wed, 29 Apr 2026 15:02:59 +0200
Subject: [PATCH 2/2] Add VFD cache footprint metrics to pg_stat_vfdcache

Extend pg_stat_vfdcache with two additional columns:

cache_entries total allocated VFD entries across active backends
cache_bytes total memory used by those entries, in bytes

Unlike hits and misses (cumulative counters), these are live gauges.
Each backend publishes its current VFD cache footprint into backend shared
stats during backend stats flush, and SQL accessors sum those backend shared
stats entries to produce cluster-wide totals.
---
 doc/src/sgml/monitoring.sgml                | 20 ++++++
 src/backend/catalog/system_views.sql        |  2 +
 src/backend/storage/file/fd.c               | 22 +++++++
 src/backend/utils/activity/pgstat_backend.c | 48 ++++++++++++++
 src/backend/utils/adt/pgstatfuncs.c         | 71 +++++++++++++++++++++
 src/include/catalog/pg_proc.dat             | 12 ++++
 src/include/pgstat.h                        | 11 ++++
 src/include/storage/fd.h                    |  2 +
 src/include/utils/pgstat_internal.h         |  5 +-
 src/test/regress/expected/rules.out         |  2 +
 10 files changed, 194 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index fd484de0d47..2c9881982a9 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -3671,6 +3671,26 @@ description | Waiting for a newly initialized WAL file to reach durable storage
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>cache_entries</structfield> <type>integer</type>
+      </para>
+      <para>
+       Sum of currently allocated VFD cache entries across all active
+       backends
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>cache_bytes</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Sum of memory used by allocated VFD cache entries across all active
+       backends, in bytes
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>max_open_fds</structfield> <type>integer</type>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e79de913538..3e123736d6d 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1561,6 +1561,8 @@ CREATE VIEW pg_stat_vfdcache AS
     SELECT
         pg_stat_get_vfd_hits() AS hits,
         pg_stat_get_vfd_misses() AS misses,
+        pg_stat_get_vfd_cache_entries() AS cache_entries,
+        pg_stat_get_vfd_cache_bytes() AS cache_bytes,
         pg_stat_get_vfd_max_open_fds() AS max_open_fds,
         CASE
             WHEN pg_stat_get_vfd_hits() + pg_stat_get_vfd_misses() = 0
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 6393407864c..29fe2c13c07 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -1512,6 +1512,28 @@ FileAccess(File file)
 	return 0;
 }
 
+/*
+ * Return the number of currently allocated VFD entries for this backend,
+ * excluding slot 0 which is used as freelist/LRU header.
+ */
+uint64
+GetVfdCacheEntries(void)
+{
+	if (SizeVfdCache == 0)
+		return 0;
+
+	return (uint64) (SizeVfdCache - 1);
+}
+
+/*
+ * Return the memory footprint of currently allocated VFD entries.
+ */
+uint64
+GetVfdCacheBytes(void)
+{
+	return GetVfdCacheEntries() * sizeof(Vfd);
+}
+
 /*
  * Called whenever a temporary file is deleted to report its size.
  */
diff --git a/src/backend/utils/activity/pgstat_backend.c b/src/backend/utils/activity/pgstat_backend.c
index 73461c9bca5..2345cbbcc49 100644
--- a/src/backend/utils/activity/pgstat_backend.c
+++ b/src/backend/utils/activity/pgstat_backend.c
@@ -27,6 +27,7 @@
 #include "access/xlog.h"
 #include "executor/instrument.h"
 #include "storage/bufmgr.h"
+#include "storage/fd.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/memutils.h"
@@ -48,6 +49,11 @@ static bool backend_has_iostats = false;
  */
 static WalUsage prevBackendWalUsage;
 
+/*
+ * Last per-backend VFD cache gauges published to backend stats.
+ */
+static PgStat_BackendVfdCacheStats prevBackendVfdCacheStats;
+
 /*
  * Utility routines to report I/O stats for backends, kept here to avoid
  * exposing PendingBackendStats to the outside world.
@@ -219,6 +225,19 @@ pgstat_backend_wal_have_pending(void)
 	return (pgWalUsage.wal_records != prevBackendWalUsage.wal_records);
 }
 
+/*
+ * To determine whether VFD cache gauges changed.
+ */
+static inline bool
+pgstat_backend_vfdcache_have_pending(void)
+{
+	PgStat_Counter current_entries = (PgStat_Counter) GetVfdCacheEntries();
+	PgStat_Counter current_cache_bytes = (PgStat_Counter) GetVfdCacheBytes();
+
+	return current_entries != prevBackendVfdCacheStats.vfd_entries ||
+		current_cache_bytes != prevBackendVfdCacheStats.vfd_cache_bytes;
+}
+
 /*
  * Flush out locally pending backend WAL statistics.  Locking is managed
  * by the caller.
@@ -262,6 +281,26 @@ pgstat_flush_backend_entry_wal(PgStat_EntryRef *entry_ref)
 	prevBackendWalUsage = pgWalUsage;
 }
 
+/*
+ * Flush out locally pending backend VFD cache gauges.  Locking is managed
+ * by the caller.
+ * No need to check whether there is pending data here because caller will have done that before acquiring the lock.
+ */
+static void
+pgstat_flush_backend_entry_vfdcache(PgStat_EntryRef *entry_ref)
+{
+	PgStatShared_Backend *shbackendent;
+	PgStat_BackendVfdCacheStats *bktype_shstats;
+
+	shbackendent = (PgStatShared_Backend *) entry_ref->shared_stats;
+	bktype_shstats = &shbackendent->stats.vfdcache_stats;
+
+	bktype_shstats->vfd_entries = (PgStat_Counter) GetVfdCacheEntries();
+	bktype_shstats->vfd_cache_bytes = (PgStat_Counter) GetVfdCacheBytes();
+
+	prevBackendVfdCacheStats = *bktype_shstats;
+}
+
 /*
  * Flush out locally pending backend statistics
  *
@@ -286,6 +325,11 @@ pgstat_flush_backend(bool nowait, uint32 flags)
 		pgstat_backend_wal_have_pending())
 		has_pending_data = true;
 
+	/* Some VFD cache data pending? */
+	if ((flags & PGSTAT_BACKEND_FLUSH_VFDCACHE) &&
+		pgstat_backend_vfdcache_have_pending())
+		has_pending_data = true;
+
 	if (!has_pending_data)
 		return false;
 
@@ -301,6 +345,9 @@ pgstat_flush_backend(bool nowait, uint32 flags)
 	if (flags & PGSTAT_BACKEND_FLUSH_WAL)
 		pgstat_flush_backend_entry_wal(entry_ref);
 
+	if (flags & PGSTAT_BACKEND_FLUSH_VFDCACHE)
+		pgstat_flush_backend_entry_vfdcache(entry_ref);
+
 	pgstat_unlock_entry(entry_ref);
 
 	return false;
@@ -339,6 +386,7 @@ pgstat_create_backend(ProcNumber procnum)
 
 	MemSet(&PendingBackendStats, 0, sizeof(PgStat_BackendPending));
 	backend_has_iostats = false;
+	MemSet(&prevBackendVfdCacheStats, 0, sizeof(prevBackendVfdCacheStats));
 
 	/*
 	 * Initialize prevBackendWalUsage with pgWalUsage so that
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index f35513d7081..ed19b9d137e 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -14,6 +14,7 @@
  */
 #include "postgres.h"
 
+#include "access/xact.h"
 #include "access/htup_details.h"
 #include "access/xlog.h"
 #include "access/xlogprefetcher.h"
@@ -1349,6 +1350,76 @@ pg_stat_get_vfd_misses(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(pgstat_fetch_stat_vfdcache()->vfd_misses);
 }
 
+/*
+ * Sum per-backend VFD gauges across currently active backends.
+ */
+static void
+pgstat_get_vfd_backend_sums(PgStat_Counter *entries_sum,
+							PgStat_Counter *bytes_sum)
+{
+	static TimestampTz cached_stmt_start_ts = 0;
+	static PgStat_Counter cached_entries_sum = 0;
+	static PgStat_Counter cached_bytes_sum = 0;
+	TimestampTz stmt_start_ts = GetCurrentStatementStartTimestamp();
+	int			num_backends = pgstat_fetch_stat_numbackends();
+
+	if (cached_stmt_start_ts == stmt_start_ts)
+	{
+		*entries_sum = cached_entries_sum;
+		*bytes_sum = cached_bytes_sum;
+		return;
+	}
+
+	*entries_sum = 0;
+	*bytes_sum = 0;
+
+	for (int curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+		if (!backend_stats)
+			continue;
+
+		*entries_sum += backend_stats->vfdcache_stats.vfd_entries;
+		*bytes_sum += backend_stats->vfdcache_stats.vfd_cache_bytes;
+	}
+
+	cached_entries_sum = *entries_sum;
+	cached_bytes_sum = *bytes_sum;
+	cached_stmt_start_ts = stmt_start_ts;
+}
+
+Datum
+pg_stat_get_vfd_cache_entries(PG_FUNCTION_ARGS)
+{
+	PgStat_Counter entries_sum;
+	PgStat_Counter bytes_sum;
+
+	pgstat_get_vfd_backend_sums(&entries_sum, &bytes_sum);
+
+	PG_RETURN_INT32((int32) entries_sum);
+}
+
+Datum
+pg_stat_get_vfd_cache_bytes(PG_FUNCTION_ARGS)
+{
+	PgStat_Counter entries_sum;
+	PgStat_Counter bytes_sum;
+
+	pgstat_get_vfd_backend_sums(&entries_sum, &bytes_sum);
+
+	PG_RETURN_INT64(bytes_sum);
+}
+
 Datum
 pg_stat_get_vfd_max_open_fds(PG_FUNCTION_ARGS)
 {
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 43132551849..b1d0c55e450 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12780,6 +12780,18 @@
   provolatile => 'v', proparallel => 'r',
   prorettype => 'int8', proargtypes => '',
   prosrc => 'pg_stat_get_vfd_misses' },
+{ oid => '9562',
+  descr => 'statistics: total number of allocated VFD cache entries',
+  proname => 'pg_stat_get_vfd_cache_entries',
+  provolatile => 'v', proparallel => 'r',
+  prorettype => 'int4', proargtypes => '',
+  prosrc => 'pg_stat_get_vfd_cache_entries' },
+{ oid => '9563',
+  descr => 'statistics: total memory footprint of VFD cache entries in bytes',
+  proname => 'pg_stat_get_vfd_cache_bytes',
+  provolatile => 'v', proparallel => 'r',
+  prorettype => 'int8', proargtypes => '',
+  prosrc => 'pg_stat_get_vfd_cache_bytes' },
 { oid => '9564',
   descr => 'statistics: timestamp of last VFD cache stats reset',
   proname => 'pg_stat_get_vfd_stat_reset_time',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index f2223a538fb..c9a2f00a4b7 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -286,6 +286,16 @@ typedef struct PgStat_VfdCacheStats
 	TimestampTz stat_reset_timestamp;
 } PgStat_VfdCacheStats;
 
+/* -------
+ * PgStat_BackendVfdCacheStats	Per-backend VFD cache gauges
+ * -------
+ */
+typedef struct PgStat_BackendVfdCacheStats
+{
+	PgStat_Counter vfd_entries;
+	PgStat_Counter vfd_cache_bytes;
+} PgStat_BackendVfdCacheStats;
+
 /*
  * Types related to counting IO operations
  */
@@ -536,6 +546,7 @@ typedef struct PgStat_Backend
 	TimestampTz stat_reset_timestamp;
 	PgStat_BktypeIO io_stats;
 	PgStat_WalCounters wal_counters;
+	PgStat_BackendVfdCacheStats vfdcache_stats;
 } PgStat_Backend;
 
 /* ---------
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 8ac466fd346..17da82c915b 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -149,6 +149,8 @@ extern char *FilePathName(File file);
 extern int	FileGetRawDesc(File file);
 extern int	FileGetRawFlags(File file);
 extern mode_t FileGetRawMode(File file);
+extern uint64 GetVfdCacheEntries(void);
+extern uint64 GetVfdCacheBytes(void);
 
 /* Operations used for sharing named temporary files */
 extern File PathNameCreateTemporaryFile(const char *path, bool error_on_failure);
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 5ede0ee255e..5f830c107fc 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -715,7 +715,10 @@ extern void pgstat_archiver_snapshot_cb(void);
 /* flags for pgstat_flush_backend() */
 #define PGSTAT_BACKEND_FLUSH_IO		(1 << 0)	/* Flush I/O statistics */
 #define PGSTAT_BACKEND_FLUSH_WAL   (1 << 1) /* Flush WAL statistics */
-#define PGSTAT_BACKEND_FLUSH_ALL   (PGSTAT_BACKEND_FLUSH_IO | PGSTAT_BACKEND_FLUSH_WAL)
+#define PGSTAT_BACKEND_FLUSH_VFDCACHE (1 << 2)	/* Flush VFD cache gauges */
+#define PGSTAT_BACKEND_FLUSH_ALL   (PGSTAT_BACKEND_FLUSH_IO | \
+								 PGSTAT_BACKEND_FLUSH_WAL | \
+								 PGSTAT_BACKEND_FLUSH_VFDCACHE)
 
 extern bool pgstat_flush_backend(bool nowait, uint32 flags);
 extern bool pgstat_backend_flush_cb(bool nowait);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 034b85a29f2..f424eb612e5 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2417,6 +2417,8 @@ pg_stat_user_tables| SELECT relid,
   WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stat_vfdcache| SELECT pg_stat_get_vfd_hits() AS hits,
     pg_stat_get_vfd_misses() AS misses,
+    pg_stat_get_vfd_cache_entries() AS cache_entries,
+    pg_stat_get_vfd_cache_bytes() AS cache_bytes,
     pg_stat_get_vfd_max_open_fds() AS max_open_fds,
         CASE
             WHEN ((pg_stat_get_vfd_hits() + pg_stat_get_vfd_misses()) = 0) THEN NULL::double precision
-- 
2.34.1

