v9b-0003-Lower-pg_stat_io_histogram-shared-memory-use-by-.patch
text/x-patch
Filename: v9b-0003-Lower-pg_stat_io_histogram-shared-memory-use-by-.patch
Type: text/x-patch
Part: 6
Message:
Re: pg_stat_io_histogram
Patch
Format: format-patch
Series: patch 0003
Subject: Lower pg_stat_io_histogram shared memory use by using array with indirect offsets.
| File | + | − |
|---|---|---|
| src/backend/Makefile | 1 | 1 |
| src/backend/utils/activity/Makefile | 1 | 0 |
| src/backend/utils/activity/meson.build | 1 | 0 |
| src/backend/utils/activity/pgstat_io.c | 52 | 253 |
| src/backend/utils/activity/pgstat_io_genslots.c | 275 | 0 |
| src/backend/utils/adt/pgstatfuncs.c | 11 | 4 |
| src/backend/utils/Makefile | 13 | 2 |
| src/common/meson.build | 1 | 1 |
| src/include/Makefile | 2 | 2 |
| src/include/pgstat.h | 18 | 1 |
| src/include/utils/meson.build | 29 | 0 |
From 85a48297be1511e29d79b6800c9b4d46b102b708 Mon Sep 17 00:00:00 2001
From: Jakub Wartak <jakub.wartak@enterprisedb.com>
Date: Wed, 18 Mar 2026 20:57:54 +0100
Subject: [PATCH v9b 3/3] Lower pg_stat_io_histogram shared memory use by using
array with indirect offsets.
We use pgstat_track_io_*() family of functions to derive the length of static
array that is allocated in shared memory region during startup. As the number
of valid combinations of backend types vs I/O object/context/operations is
coming from semi-runtime pgstat_io_get_sum_tracked() function, it cannot be
preprocessed, so we need to come up with #define PGSTAT_IO_HIST_BUCKET_SLOTS
somehow. In order to do that - and avoid that C limitations - we transfer
some routines to pgstat_io_genslots.c, which has added additional standalone
special mode (int main()), that can be used to generate pgstat_io_genslots
binary and used to generate dynamic header file with the proper size.
---
src/backend/Makefile | 2 +-
src/backend/utils/Makefile | 15 +-
src/backend/utils/activity/Makefile | 1 +
src/backend/utils/activity/meson.build | 1 +
src/backend/utils/activity/pgstat_io.c | 305 +++---------------
.../utils/activity/pgstat_io_genslots.c | 275 ++++++++++++++++
src/backend/utils/adt/pgstatfuncs.c | 15 +-
src/common/meson.build | 2 +-
src/include/Makefile | 4 +-
src/include/pgstat.h | 19 +-
src/include/utils/meson.build | 29 ++
11 files changed, 404 insertions(+), 264 deletions(-)
create mode 100644 src/backend/utils/activity/pgstat_io_genslots.c
diff --git a/src/backend/Makefile b/src/backend/Makefile
index ba53cd9d998..c80df4bbde0 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -165,7 +165,7 @@ submake-nodes-headers:
$(MAKE) -C nodes generated-header-symlinks
# run this unconditionally to avoid needing to know its dependencies here:
-submake-utils-headers:
+submake-utils-headers: submake-nodes-headers
$(MAKE) -C utils generated-header-symlinks
.PHONY: submake-catalog-headers submake-nodes-headers submake-utils-headers
diff --git a/src/backend/utils/Makefile b/src/backend/utils/Makefile
index 81b4a956bda..54d04b248bb 100644
--- a/src/backend/utils/Makefile
+++ b/src/backend/utils/Makefile
@@ -43,7 +43,7 @@ generated-header-symlinks: $(top_builddir)/src/include/utils/header-stamp submak
submake-adt-headers:
$(MAKE) -C adt jsonpath_gram.h
-$(SUBDIRS:%=%-recursive): fmgr-stamp errcodes.h guc_tables.inc.c pgstat_wait_event.c wait_event_funcs_data.c wait_event_types.h
+$(SUBDIRS:%=%-recursive): fmgr-stamp errcodes.h guc_tables.inc.c pgstat_wait_event.c wait_event_funcs_data.c wait_event_types.h pgstat_io_genslots.h
# fmgr-stamp records the last time we ran Gen_fmgrtab.pl. We don't rely on
# the timestamps of the individual output files, because the Perl script
@@ -64,6 +64,16 @@ wait_event_funcs_data.c: wait_event_types.h
wait_event_types.h: $(top_srcdir)/src/backend/utils/activity/wait_event_names.txt $(top_srcdir)/src/backend/utils/activity/generate-wait_event_types.pl
$(PERL) $(top_srcdir)/src/backend/utils/activity/generate-wait_event_types.pl --code $<
+pgstat_io_genslots.c: pgstat_io_genslots.h errcodes.h
+
+pgstat_io_genslots.h: pgstat_io_genslots
+ $(top_srcdir)/src/backend/utils/pgstat_io_genslots > $(top_srcdir)/src/include/utils/pgstat_io_genslots.h
+
+pgstat_io_genslots: $(top_srcdir)/src/backend/utils/activity/pgstat_io_genslots.c
+ # for some reason src/backend/utils/errcodes.h is not accessible without
+ # this
+ $(CC) $(CFLAGS) $(CPPFLAGS) -I../../../src/backend -DPGSTAT_IO_GENSLOTS_STANDALONE $< -o pgstat_io_genslots
+
ifeq ($(enable_dtrace), yes)
probes.h: postprocess_dtrace.sed probes.h.tmp
sed -f $^ >$@
@@ -79,7 +89,7 @@ endif
# These generated headers must be symlinked into src/include/.
# We use header-stamp to record that we've done this because the symlinks
# themselves may appear older than fmgr-stamp.
-$(top_builddir)/src/include/utils/header-stamp: fmgr-stamp errcodes.h probes.h guc_tables.inc.c pgstat_wait_event.c wait_event_funcs_data.c wait_event_types.h
+$(top_builddir)/src/include/utils/header-stamp: fmgr-stamp errcodes.h pgstat_io_genslots.h probes.h guc_tables.inc.c pgstat_wait_event.c wait_event_funcs_data.c wait_event_types.h
cd '$(dir $@)' && for file in fmgroids.h fmgrprotos.h errcodes.h probes.h guc_tables.inc.c pgstat_wait_event.c wait_event_funcs_data.c wait_event_types.h; do \
rm -f $$file && $(LN_S) "../../../$(subdir)/$$file" . ; \
done
@@ -100,3 +110,4 @@ clean:
rm -f probes.h probes.h.tmp
rm -f fmgroids.h fmgrprotos.h fmgrtab.c fmgr-stamp errcodes.h guc_tables.inc.c
rm -f wait_event_types.h pgstat_wait_event.c wait_event_funcs_data.c
+ rm -f pgstat_io_genslots pgstat_io_genslots.h
diff --git a/src/backend/utils/activity/Makefile b/src/backend/utils/activity/Makefile
index c37bfb350bb..b6fda50f651 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -16,6 +16,7 @@ include $(top_builddir)/src/Makefile.global
override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
OBJS = \
+ pgstat_io_genslots.o \
backend_progress.o \
backend_status.o \
pgstat.o \
diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build
index 53bd5a246ca..87522ef846e 100644
--- a/src/backend/utils/activity/meson.build
+++ b/src/backend/utils/activity/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
'pgstat_database.c',
'pgstat_function.c',
'pgstat_io.c',
+ 'pgstat_io_genslots.c',
'pgstat_relation.c',
'pgstat_replslot.c',
'pgstat_shmem.c',
diff --git a/src/backend/utils/activity/pgstat_io.c b/src/backend/utils/activity/pgstat_io.c
index f571d558154..23e0e250bff 100644
--- a/src/backend/utils/activity/pgstat_io.c
+++ b/src/backend/utils/activity/pgstat_io.c
@@ -67,26 +67,6 @@ pgstat_bktype_io_stats_valid(PgStat_BktypeIO *backend_io,
return true;
}
-int
-pgstat_bktype_count_potentially_used(BackendType bktype)
-{
- int cnt = 0;
- for (int io_object = 0; io_object < IOOBJECT_NUM_TYPES; io_object++)
- {
- for (int io_context = 0; io_context < IOCONTEXT_NUM_TYPES; io_context++)
- {
- for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++)
- {
- /* we do track it */
- if (pgstat_tracks_io_op(bktype, io_object, io_context, io_op))
- cnt++;
- }
- }
- }
-
- return cnt;
-}
-
void
pgstat_count_io_op(IOObject io_object, IOContext io_context, IOOp io_op,
uint32 cnt, uint64 bytes)
@@ -250,6 +230,7 @@ pgstat_io_flush_cb(bool nowait)
{
LWLock *bktype_lock;
PgStat_BktypeIO *bktype_shstats;
+ PgStat_IO *bk_io;
if (!have_iostats)
return false;
@@ -257,6 +238,7 @@ pgstat_io_flush_cb(bool nowait)
bktype_lock = &pgStatLocal.shmem->io.locks[MyBackendType];
bktype_shstats =
&pgStatLocal.shmem->io.stats.stats[MyBackendType];
+ bk_io = &pgStatLocal.shmem->io.stats;
if (!nowait)
LWLockAcquire(bktype_lock, LW_EXCLUSIVE);
@@ -287,14 +269,22 @@ pgstat_io_flush_cb(bool nowait)
* by using indirect offsets from the pending_hist_time_buckets dynamic array
* (accessed with offsets to save memory) into shared memory.
*/
- if(PendingIOStats.pending_hist_time_buckets != NULL)
+ if(PendingIOStats.pending_hist_time_buckets != NULL) {
+ int16 bktype_shstats_off = bktype_shstats->hist_time_buckets_offsets[io_object][io_context][io_op];
+ /*
+ * -1 means here that such mapping doesn't have slot
+ * (based on pgstat_track_io_*())
+ */
+ if(bktype_shstats_off == -1)
+ continue;
+
for(int b = 0; b < PGSTAT_IO_HIST_BUCKETS; b++) {
int pending_off = PendingIOStats.pending_hist_time_buckets_offsets[io_object][io_context][io_op];
- if(pending_off != -1) {
- bktype_shstats->hist_time_buckets[io_object][io_context][io_op][b] +=
+ if(pending_off != -1)
+ bk_io->hist_time_buckets_slots[bktype_shstats_off][b] +=
PendingIOStats.pending_hist_time_buckets[pending_off][b];
- }
}
+ }
}
}
}
@@ -409,12 +399,12 @@ pgstat_io_init_backend_cb(void)
}
else
PendingIOStats.pending_hist_time_buckets = NULL;
-
}
void
pgstat_io_init_shmem_cb(void *stats)
{
+ uint16 histogram_slots = 0;
PgStatShared_IO *stat_shmem = (PgStatShared_IO *) stats;
for (int i = 0; i < BACKEND_NUM_TYPES; i++)
@@ -422,6 +412,29 @@ pgstat_io_init_shmem_cb(void *stats)
/* this might end up being lazily allocated in pgstat_io_snapshot_cb() */
pgStatLocal.snapshot.io = NULL;
+
+ /*
+ * Establish indirect mapping from PgStat_BktypeIO.hist_time_buckets_offsets[][][] to
+ * PgStatIO.hist_time_buckets_slots[x]
+ */
+ for (int i = 0; i < BACKEND_NUM_TYPES; i++) {
+ for (int io_object = 0; io_object < IOOBJECT_NUM_TYPES; io_object++)
+ {
+ for (int io_context = 0; io_context < IOCONTEXT_NUM_TYPES; io_context++)
+ {
+ for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++)
+ {
+ if (pgstat_tracks_io_op(i, io_object, io_context, io_op)) {
+ stat_shmem->stats.stats[i].hist_time_buckets_offsets[io_object][io_context][io_op] =
+ histogram_slots++;
+ } else
+ stat_shmem->stats.stats[i].hist_time_buckets_offsets[io_object][io_context][io_op] =
+ -1;
+ }
+ }
+ }
+ }
+
}
void
@@ -436,12 +449,17 @@ pgstat_io_reset_all_cb(TimestampTz ts)
/*
* Use the lock in the first BackendType's PgStat_BktypeIO to protect
- * the reset timestamp as well.
+ * the reset timestamp and the memory used for I/O latency data.
*/
- if (i == 0)
+ if (i == 0) {
pgStatLocal.shmem->io.stats.stat_reset_timestamp = ts;
- memset(bktype_shstats, 0, sizeof(*bktype_shstats));
+ memset(pgStatLocal.shmem->io.stats.hist_time_buckets_slots, 0,
+ sizeof(pgStatLocal.shmem->io.stats.hist_time_buckets_slots));
+ }
+
+ /* Avoid resetting our indirect mapping offsets */
+ memset(bktype_shstats, 0, offsetof(PgStat_BktypeIO, hist_time_buckets_offsets));
LWLockRelease(bktype_lock);
}
}
@@ -465,237 +483,18 @@ pgstat_io_snapshot_cb(void)
* Use the lock in the first BackendType's PgStat_BktypeIO to protect
* the reset timestamp as well.
*/
- if (i == 0)
+ if (i == 0) {
pgStatLocal.snapshot.io->stat_reset_timestamp =
pgStatLocal.shmem->io.stats.stat_reset_timestamp;
+ memcpy(pgStatLocal.snapshot.io->hist_time_buckets_slots,
+ pgStatLocal.shmem->io.stats.hist_time_buckets_slots, sizeof(pgStatLocal.snapshot.io->hist_time_buckets_slots));
+ }
+
/* using struct assignment due to better type safety */
*bktype_snap = *bktype_shstats;
- LWLockRelease(bktype_lock);
- }
-}
-/*
-* IO statistics are not collected for all BackendTypes.
-*
-* The following BackendTypes do not participate in the cumulative stats
-* subsystem or do not perform IO on which we currently track:
-* - Dead-end backend because it is not connected to shared memory and
-* doesn't do any IO
-* - Syslogger because it is not connected to shared memory
-* - Archiver because most relevant archiving IO is delegated to a
-* specialized command or module
-*
-* Function returns true if BackendType participates in the cumulative stats
-* subsystem for IO and false if it does not.
-*
-* When adding a new BackendType, also consider adding relevant restrictions to
-* pgstat_tracks_io_object() and pgstat_tracks_io_op().
-*/
-bool
-pgstat_tracks_io_bktype(BackendType bktype)
-{
- /*
- * List every type so that new backend types trigger a warning about
- * needing to adjust this switch.
- */
- switch (bktype)
- {
- case B_INVALID:
- case B_DEAD_END_BACKEND:
- case B_ARCHIVER:
- case B_LOGGER:
- return false;
-
- case B_AUTOVAC_LAUNCHER:
- case B_AUTOVAC_WORKER:
- case B_BACKEND:
- case B_BG_WORKER:
- case B_BG_WRITER:
- case B_CHECKPOINTER:
- case B_IO_WORKER:
- case B_SLOTSYNC_WORKER:
- case B_STANDALONE_BACKEND:
- case B_STARTUP:
- case B_WAL_RECEIVER:
- case B_WAL_SENDER:
- case B_WAL_SUMMARIZER:
- case B_WAL_WRITER:
- return true;
+ LWLockRelease(bktype_lock);
}
-
- return false;
}
-/*
- * Some BackendTypes do not perform IO on certain IOObjects or in certain
- * IOContexts. Some IOObjects are never operated on in some IOContexts. Check
- * that the given BackendType is expected to do IO in the given IOContext and
- * on the given IOObject and that the given IOObject is expected to be operated
- * on in the given IOContext.
- */
-bool
-pgstat_tracks_io_object(BackendType bktype, IOObject io_object,
- IOContext io_context)
-{
- bool no_temp_rel;
-
- /*
- * Some BackendTypes should never track IO statistics.
- */
- if (!pgstat_tracks_io_bktype(bktype))
- return false;
-
- /*
- * Currently, IO on IOOBJECT_WAL objects can only occur in the
- * IOCONTEXT_NORMAL and IOCONTEXT_INIT IOContexts.
- */
- if (io_object == IOOBJECT_WAL &&
- (io_context != IOCONTEXT_NORMAL &&
- io_context != IOCONTEXT_INIT))
- return false;
-
- /*
- * Currently, IO on temporary relations can only occur in the
- * IOCONTEXT_NORMAL IOContext.
- */
- if (io_context != IOCONTEXT_NORMAL &&
- io_object == IOOBJECT_TEMP_RELATION)
- return false;
-
- /*
- * In core Postgres, only regular backends and WAL Sender processes
- * executing queries will use local buffers and operate on temporary
- * relations. Parallel workers will not use local buffers (see
- * InitLocalBuffers()); however, extensions leveraging background workers
- * have no such limitation, so track IO on IOOBJECT_TEMP_RELATION for
- * BackendType B_BG_WORKER.
- */
- no_temp_rel = bktype == B_AUTOVAC_LAUNCHER || bktype == B_BG_WRITER ||
- bktype == B_CHECKPOINTER || bktype == B_AUTOVAC_WORKER ||
- bktype == B_STANDALONE_BACKEND || bktype == B_STARTUP ||
- bktype == B_WAL_SUMMARIZER || bktype == B_WAL_WRITER ||
- bktype == B_WAL_RECEIVER;
-
- if (no_temp_rel && io_context == IOCONTEXT_NORMAL &&
- io_object == IOOBJECT_TEMP_RELATION)
- return false;
-
- /*
- * Some BackendTypes only perform IO under IOOBJECT_WAL, hence exclude all
- * rows for all the other objects for these.
- */
- if ((bktype == B_WAL_SUMMARIZER || bktype == B_WAL_RECEIVER ||
- bktype == B_WAL_WRITER) && io_object != IOOBJECT_WAL)
- return false;
-
- /*
- * Some BackendTypes do not currently perform any IO in certain
- * IOContexts, and, while it may not be inherently incorrect for them to
- * do so, excluding those rows from the view makes the view easier to use.
- */
- if ((bktype == B_CHECKPOINTER || bktype == B_BG_WRITER) &&
- (io_context == IOCONTEXT_BULKREAD ||
- io_context == IOCONTEXT_BULKWRITE ||
- io_context == IOCONTEXT_VACUUM))
- return false;
-
- if (bktype == B_AUTOVAC_LAUNCHER && io_context == IOCONTEXT_VACUUM)
- return false;
-
- if ((bktype == B_AUTOVAC_WORKER || bktype == B_AUTOVAC_LAUNCHER) &&
- io_context == IOCONTEXT_BULKWRITE)
- return false;
-
- return true;
-}
-
-/*
- * Some BackendTypes will never do certain IOOps and some IOOps should not
- * occur in certain IOContexts or on certain IOObjects. Check that the given
- * IOOp is valid for the given BackendType in the given IOContext and on the
- * given IOObject. Note that there are currently no cases of an IOOp being
- * invalid for a particular BackendType only within a certain IOContext and/or
- * only on a certain IOObject.
- */
-bool
-pgstat_tracks_io_op(BackendType bktype, IOObject io_object,
- IOContext io_context, IOOp io_op)
-{
- bool strategy_io_context;
-
- /* if (io_context, io_object) will never collect stats, we're done */
- if (!pgstat_tracks_io_object(bktype, io_object, io_context))
- return false;
-
- /*
- * Some BackendTypes will not do certain IOOps.
- */
- if (bktype == B_BG_WRITER &&
- (io_op == IOOP_READ || io_op == IOOP_EVICT || io_op == IOOP_HIT))
- return false;
-
- if (bktype == B_CHECKPOINTER &&
- ((io_object != IOOBJECT_WAL && io_op == IOOP_READ) ||
- (io_op == IOOP_EVICT || io_op == IOOP_HIT)))
- return false;
-
- if ((bktype == B_AUTOVAC_LAUNCHER || bktype == B_BG_WRITER ||
- bktype == B_CHECKPOINTER) && io_op == IOOP_EXTEND)
- return false;
-
- /*
- * Some BackendTypes do not perform reads with IOOBJECT_WAL.
- */
- if (io_object == IOOBJECT_WAL && io_op == IOOP_READ &&
- (bktype == B_WAL_RECEIVER || bktype == B_BG_WRITER ||
- bktype == B_AUTOVAC_LAUNCHER || bktype == B_AUTOVAC_WORKER ||
- bktype == B_WAL_WRITER))
- return false;
-
- /*
- * Temporary tables are not logged and thus do not require fsync'ing.
- * Writeback is not requested for temporary tables.
- */
- if (io_object == IOOBJECT_TEMP_RELATION &&
- (io_op == IOOP_FSYNC || io_op == IOOP_WRITEBACK))
- return false;
-
- /*
- * Some IOOps are not valid in certain IOContexts and some IOOps are only
- * valid in certain contexts.
- */
- if (io_context == IOCONTEXT_BULKREAD && io_op == IOOP_EXTEND)
- return false;
-
- strategy_io_context = io_context == IOCONTEXT_BULKREAD ||
- io_context == IOCONTEXT_BULKWRITE || io_context == IOCONTEXT_VACUUM;
-
- /*
- * IOOP_REUSE is only relevant when a BufferAccessStrategy is in use.
- */
- if (!strategy_io_context && io_op == IOOP_REUSE)
- return false;
-
- /*
- * IOOBJECT_WAL IOObject will not do certain IOOps depending on IOContext.
- */
- if (io_object == IOOBJECT_WAL && io_context == IOCONTEXT_INIT &&
- !(io_op == IOOP_WRITE || io_op == IOOP_FSYNC))
- return false;
-
- if (io_object == IOOBJECT_WAL && io_context == IOCONTEXT_NORMAL &&
- !(io_op == IOOP_WRITE || io_op == IOOP_READ || io_op == IOOP_FSYNC))
- return false;
-
- /*
- * IOOP_FSYNC IOOps done by a backend using a BufferAccessStrategy are
- * counted in the IOCONTEXT_NORMAL IOContext. See comment in
- * register_dirty_segment() for more details.
- */
- if (strategy_io_context && io_op == IOOP_FSYNC)
- return false;
-
-
- return true;
-}
diff --git a/src/backend/utils/activity/pgstat_io_genslots.c b/src/backend/utils/activity/pgstat_io_genslots.c
new file mode 100644
index 00000000000..34c857ce697
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_io_genslots.c
@@ -0,0 +1,275 @@
+#include "postgres.h"
+#include "pgstat.h"
+
+/*
+ * This can be build as standalone to output how may PGSTAT_IO_HIST_BUCKET_SLOTS
+ * we need for allocation within shared memory.
+ */
+#ifdef PGSTAT_IO_GENSLOTS_STANDALONE
+#include <stdio.h>
+/* restore orginal printf, so that it works without pg_printf() */
+#undef printf
+
+int main(void) {
+ int calculated_size = pgstat_io_get_sum_tracked();
+ printf("%s\n" , "#pragma once");
+ printf("#define PGSTAT_IO_HIST_BUCKET_SLOTS %d+1\n", calculated_size);
+ return 0;
+}
+#endif
+
+int
+pgstat_bktype_count_potentially_used(BackendType bktype)
+{
+ int cnt = 0;
+ for (int io_object = 0; io_object < IOOBJECT_NUM_TYPES; io_object++)
+ {
+ for (int io_context = 0; io_context < IOCONTEXT_NUM_TYPES; io_context++)
+ {
+ for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++)
+ {
+ /* we do track it */
+ if (pgstat_tracks_io_op(bktype, io_object, io_context, io_op))
+ cnt++;
+ }
+ }
+ }
+
+ return cnt;
+}
+
+int
+pgstat_io_get_sum_tracked(void)
+{
+ int sum = 0;
+
+ for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+ sum += pgstat_bktype_count_potentially_used(i);
+
+ return sum;
+}
+
+/*
+* IO statistics are not collected for all BackendTypes.
+*
+* The following BackendTypes do not participate in the cumulative stats
+* subsystem or do not perform IO on which we currently track:
+* - Dead-end backend because it is not connected to shared memory and
+* doesn't do any IO
+* - Syslogger because it is not connected to shared memory
+* - Archiver because most relevant archiving IO is delegated to a
+* specialized command or module
+*
+* Function returns true if BackendType participates in the cumulative stats
+* subsystem for IO and false if it does not.
+*
+* When adding a new BackendType, also consider adding relevant restrictions to
+* pgstat_tracks_io_object() and pgstat_tracks_io_op().
+*/
+bool
+pgstat_tracks_io_bktype(BackendType bktype)
+{
+ /*
+ * List every type so that new backend types trigger a warning about
+ * needing to adjust this switch.
+ */
+ switch (bktype)
+ {
+ case B_INVALID:
+ case B_DEAD_END_BACKEND:
+ case B_ARCHIVER:
+ case B_LOGGER:
+ return false;
+
+ case B_AUTOVAC_LAUNCHER:
+ case B_AUTOVAC_WORKER:
+ case B_BACKEND:
+ case B_BG_WORKER:
+ case B_BG_WRITER:
+ case B_CHECKPOINTER:
+ case B_IO_WORKER:
+ case B_SLOTSYNC_WORKER:
+ case B_STANDALONE_BACKEND:
+ case B_STARTUP:
+ case B_WAL_RECEIVER:
+ case B_WAL_SENDER:
+ case B_WAL_SUMMARIZER:
+ case B_WAL_WRITER:
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Some BackendTypes do not perform IO on certain IOObjects or in certain
+ * IOContexts. Some IOObjects are never operated on in some IOContexts. Check
+ * that the given BackendType is expected to do IO in the given IOContext and
+ * on the given IOObject and that the given IOObject is expected to be operated
+ * on in the given IOContext.
+ */
+bool
+pgstat_tracks_io_object(BackendType bktype, IOObject io_object,
+ IOContext io_context)
+{
+ bool no_temp_rel;
+
+ /*
+ * Some BackendTypes should never track IO statistics.
+ */
+ if (!pgstat_tracks_io_bktype(bktype))
+ return false;
+
+ /*
+ * Currently, IO on IOOBJECT_WAL objects can only occur in the
+ * IOCONTEXT_NORMAL and IOCONTEXT_INIT IOContexts.
+ */
+ if (io_object == IOOBJECT_WAL &&
+ (io_context != IOCONTEXT_NORMAL &&
+ io_context != IOCONTEXT_INIT))
+ return false;
+
+ /*
+ * Currently, IO on temporary relations can only occur in the
+ * IOCONTEXT_NORMAL IOContext.
+ */
+ if (io_context != IOCONTEXT_NORMAL &&
+ io_object == IOOBJECT_TEMP_RELATION)
+ return false;
+
+ /*
+ * In core Postgres, only regular backends and WAL Sender processes
+ * executing queries will use local buffers and operate on temporary
+ * relations. Parallel workers will not use local buffers (see
+ * InitLocalBuffers()); however, extensions leveraging background workers
+ * have no such limitation, so track IO on IOOBJECT_TEMP_RELATION for
+ * BackendType B_BG_WORKER.
+ */
+ no_temp_rel = bktype == B_AUTOVAC_LAUNCHER || bktype == B_BG_WRITER ||
+ bktype == B_CHECKPOINTER || bktype == B_AUTOVAC_WORKER ||
+ bktype == B_STANDALONE_BACKEND || bktype == B_STARTUP ||
+ bktype == B_WAL_SUMMARIZER || bktype == B_WAL_WRITER ||
+ bktype == B_WAL_RECEIVER;
+
+ if (no_temp_rel && io_context == IOCONTEXT_NORMAL &&
+ io_object == IOOBJECT_TEMP_RELATION)
+ return false;
+
+ /*
+ * Some BackendTypes only perform IO under IOOBJECT_WAL, hence exclude all
+ * rows for all the other objects for these.
+ */
+ if ((bktype == B_WAL_SUMMARIZER || bktype == B_WAL_RECEIVER ||
+ bktype == B_WAL_WRITER) && io_object != IOOBJECT_WAL)
+ return false;
+
+ /*
+ * Some BackendTypes do not currently perform any IO in certain
+ * IOContexts, and, while it may not be inherently incorrect for them to
+ * do so, excluding those rows from the view makes the view easier to use.
+ */
+ if ((bktype == B_CHECKPOINTER || bktype == B_BG_WRITER) &&
+ (io_context == IOCONTEXT_BULKREAD ||
+ io_context == IOCONTEXT_BULKWRITE ||
+ io_context == IOCONTEXT_VACUUM))
+ return false;
+
+ if (bktype == B_AUTOVAC_LAUNCHER && io_context == IOCONTEXT_VACUUM)
+ return false;
+
+ if ((bktype == B_AUTOVAC_WORKER || bktype == B_AUTOVAC_LAUNCHER) &&
+ io_context == IOCONTEXT_BULKWRITE)
+ return false;
+
+ return true;
+}
+
+/*
+ * Some BackendTypes will never do certain IOOps and some IOOps should not
+ * occur in certain IOContexts or on certain IOObjects. Check that the given
+ * IOOp is valid for the given BackendType in the given IOContext and on the
+ * given IOObject. Note that there are currently no cases of an IOOp being
+ * invalid for a particular BackendType only within a certain IOContext and/or
+ * only on a certain IOObject.
+ */
+bool
+pgstat_tracks_io_op(BackendType bktype, IOObject io_object,
+ IOContext io_context, IOOp io_op)
+{
+ bool strategy_io_context;
+
+ /* if (io_context, io_object) will never collect stats, we're done */
+ if (!pgstat_tracks_io_object(bktype, io_object, io_context))
+ return false;
+
+ /*
+ * Some BackendTypes will not do certain IOOps.
+ */
+ if (bktype == B_BG_WRITER &&
+ (io_op == IOOP_READ || io_op == IOOP_EVICT || io_op == IOOP_HIT))
+ return false;
+
+ if (bktype == B_CHECKPOINTER &&
+ ((io_object != IOOBJECT_WAL && io_op == IOOP_READ) ||
+ (io_op == IOOP_EVICT || io_op == IOOP_HIT)))
+ return false;
+
+ if ((bktype == B_AUTOVAC_LAUNCHER || bktype == B_BG_WRITER ||
+ bktype == B_CHECKPOINTER) && io_op == IOOP_EXTEND)
+ return false;
+
+ /*
+ * Some BackendTypes do not perform reads with IOOBJECT_WAL.
+ */
+ if (io_object == IOOBJECT_WAL && io_op == IOOP_READ &&
+ (bktype == B_WAL_RECEIVER || bktype == B_BG_WRITER ||
+ bktype == B_AUTOVAC_LAUNCHER || bktype == B_AUTOVAC_WORKER ||
+ bktype == B_WAL_WRITER))
+ return false;
+
+ /*
+ * Temporary tables are not logged and thus do not require fsync'ing.
+ * Writeback is not requested for temporary tables.
+ */
+ if (io_object == IOOBJECT_TEMP_RELATION &&
+ (io_op == IOOP_FSYNC || io_op == IOOP_WRITEBACK))
+ return false;
+
+ /*
+ * Some IOOps are not valid in certain IOContexts and some IOOps are only
+ * valid in certain contexts.
+ */
+ if (io_context == IOCONTEXT_BULKREAD && io_op == IOOP_EXTEND)
+ return false;
+
+ strategy_io_context = io_context == IOCONTEXT_BULKREAD ||
+ io_context == IOCONTEXT_BULKWRITE || io_context == IOCONTEXT_VACUUM;
+
+ /*
+ * IOOP_REUSE is only relevant when a BufferAccessStrategy is in use.
+ */
+ if (!strategy_io_context && io_op == IOOP_REUSE)
+ return false;
+
+ /*
+ * IOOBJECT_WAL IOObject will not do certain IOOps depending on IOContext.
+ */
+ if (io_object == IOOBJECT_WAL && io_context == IOCONTEXT_INIT &&
+ !(io_op == IOOP_WRITE || io_op == IOOP_FSYNC))
+ return false;
+
+ if (io_object == IOOBJECT_WAL && io_context == IOCONTEXT_NORMAL &&
+ !(io_op == IOOP_WRITE || io_op == IOOP_READ || io_op == IOOP_FSYNC))
+ return false;
+
+ /*
+ * IOOP_FSYNC IOOps done by a backend using a BufferAccessStrategy are
+ * counted in the IOCONTEXT_NORMAL IOContext. See comment in
+ * register_dirty_segment() for more details.
+ */
+ if (strategy_io_context && io_op == IOOP_FSYNC)
+ return false;
+
+
+ return true;
+}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index b457d771474..017c2b8676b 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1671,6 +1671,7 @@ typedef enum hist_io_stat_col
*/
static void
pg_stat_io_histogram_build_tuples(ReturnSetInfo *rsinfo,
+ PgStat_IO *backends_io_stats,
PgStat_BktypeIO *bktype_stats,
BackendType bktype,
TimestampTz stat_reset_timestamp)
@@ -1705,6 +1706,8 @@ pg_stat_io_histogram_build_tuples(ReturnSetInfo *rsinfo,
bool nulls[HIST_IO_NUM_COLUMNS] = {0};
RangeBound lower, upper;
RangeType *range;
+ int16 bktype_hist_time_bucket_off;
+ uint64 bktype_bucket;
values[HIST_IO_COL_BACKEND_TYPE] = bktype_desc;
values[HIST_IO_COL_OBJECT] = CStringGetTextDatum(obj_name);
@@ -1732,9 +1735,13 @@ pg_stat_io_histogram_build_tuples(ReturnSetInfo *rsinfo,
range = make_range(typcache, &lower, &upper, false, NULL);
values[HIST_IO_COL_BUCKET_US] = RangeTypePGetDatum(range);
- /* bucket count */
- values[HIST_IO_COL_COUNT] = Int64GetDatum(
- bktype_stats->hist_time_buckets[io_obj][io_context][io_op][bucket]);
+ /* get bucket count, access indirectly */
+ bktype_hist_time_bucket_off = bktype_stats->hist_time_buckets_offsets[io_obj][io_context][io_op];
+ if(bktype_hist_time_bucket_off == -1)
+ continue;
+ Assert(bktype_hist_time_bucket_off < PGSTAT_IO_HIST_BUCKET_SLOTS);
+ bktype_bucket = backends_io_stats->hist_time_buckets_slots[bktype_hist_time_bucket_off][bucket];
+ values[HIST_IO_COL_COUNT] = Int64GetDatum(bktype_bucket);
if (stat_reset_timestamp != 0)
values[HIST_IO_COL_RESET_TIME] = TimestampTzGetDatum(stat_reset_timestamp);
@@ -1780,7 +1787,7 @@ pg_stat_get_io_histogram(PG_FUNCTION_ARGS)
continue;
/* save tuples with data from this PgStat_BktypeIO */
- pg_stat_io_histogram_build_tuples(rsinfo, bktype_stats, bktype,
+ pg_stat_io_histogram_build_tuples(rsinfo, backends_io_stats, bktype_stats, bktype,
backends_io_stats->stat_reset_timestamp);
}
diff --git a/src/common/meson.build b/src/common/meson.build
index 4f9b8b8263d..02eba7d3b27 100644
--- a/src/common/meson.build
+++ b/src/common/meson.build
@@ -136,7 +136,7 @@ common_sources_frontend_static = [
pgcommon = {}
pgcommon_variants = {
'_srv': internal_lib_args + {
- 'sources': common_sources + [lwlocknames_h] + [wait_event_types_h],
+ 'sources': common_sources + [lwlocknames_h] + [wait_event_types_h] + [pgstat_io_genslots_header],
'dependencies': [backend_common_code],
},
'': default_lib_args + {
diff --git a/src/include/Makefile b/src/include/Makefile
index ac673f4cf17..49d94048a74 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -84,7 +84,7 @@ install: all installdirs
$(INSTALL_DATA) $(srcdir)/$$dir/*.h '$(DESTDIR)$(includedir_server)'/$$dir || exit; \
done
ifeq ($(vpath_build),yes)
- for file in storage/lwlocknames.h utils/probes.h utils/wait_event_types.h; do \
+ for file in storage/lwlocknames.h utils/probes.h utils/wait_event_types.h utils/pgstat_io_genslots.h; do \
$(INSTALL_DATA) $$file '$(DESTDIR)$(includedir_server)'/$$file || exit; \
done
endif
@@ -106,7 +106,7 @@ uninstall:
clean:
rm -f utils/fmgroids.h utils/fmgrprotos.h utils/guc_tables.inc.c utils/errcodes.h utils/header-stamp
rm -f utils/pgstat_wait_event.c utils/wait_event_funcs_data.c
- rm -f storage/lwlocknames.h utils/probes.h utils/wait_event_types.h
+ rm -f storage/lwlocknames.h utils/probes.h utils/wait_event_types.h utils/pgstat_io_genslots.h
rm -f nodes/nodetags.h nodes/header-stamp
$(MAKE) -C catalog clean
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 15e5743ca98..a6e8f7a5b85 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -19,6 +19,17 @@
#include "utils/backend_status.h" /* for backward compatibility */ /* IWYU pragma: export */
#include "utils/pgstat_kind.h"
+#ifdef PGSTAT_IO_GENSLOTS_STANDALONE
+/*
+ * This is workaround to avoid error that we do not have
+ * PGSTAT_IO_HIST_BUCKET_SLOTS later on when included by pgstat_io_genslots.c
+ * in standalone mode (launched by meson/autoconf) as it includes pgstat.h
+ */
+#define PGSTAT_IO_HIST_BUCKET_SLOTS 0
+#else
+/* include dynamically generated PGSTAT_IO_HIST_BUCKET_SLOTS */
+#include "utils/pgstat_io_genslots.h"
+#endif
/* avoid including access/transam.h */
typedef struct FullTransactionId FullTransactionId;
@@ -341,7 +352,9 @@ typedef struct PgStat_BktypeIO
uint64 bytes[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
PgStat_Counter counts[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
PgStat_Counter times[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
- uint64 hist_time_buckets[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES][PGSTAT_IO_HIST_BUCKETS];
+
+ /* indirect offset to PgStat_IO (parent structure).hist_time_buckets_offsets */
+ int16 hist_time_buckets_offsets[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
} PgStat_BktypeIO;
typedef struct PgStat_PendingIO
@@ -372,6 +385,9 @@ typedef struct PgStat_IO
{
TimestampTz stat_reset_timestamp;
PgStat_BktypeIO stats[BACKEND_NUM_TYPES];
+
+ /* PGSTAT_IO_HIST_BUCKET_SLOTS comes from dynamically generated pgstat_io_genslots.h */
+ uint64 hist_time_buckets_slots[PGSTAT_IO_HIST_BUCKET_SLOTS][PGSTAT_IO_HIST_BUCKETS];
} PgStat_IO;
typedef struct PgStat_StatDBEntry
@@ -637,6 +653,7 @@ extern PgStat_CheckpointerStats *pgstat_fetch_stat_checkpointer(void);
extern bool pgstat_bktype_io_stats_valid(PgStat_BktypeIO *backend_io,
BackendType bktype);
extern int pgstat_bktype_count_potentially_used(BackendType bktype);
+extern int pgstat_io_get_sum_tracked(void);
extern void pgstat_count_io_op(IOObject io_object, IOContext io_context,
IOOp io_op, uint32 cnt, uint64 bytes);
extern instr_time pgstat_prepare_io_time(bool track_io_guc);
diff --git a/src/include/utils/meson.build b/src/include/utils/meson.build
index fd3a2352df5..d22e4891b6b 100644
--- a/src/include/utils/meson.build
+++ b/src/include/utils/meson.build
@@ -77,6 +77,35 @@ fmgrtab_target = custom_target('fmgrtab',
generated_backend_headers += fmgrtab_target[0]
generated_backend_headers += fmgrtab_target[1]
+# Build standalone executable that is capable of calculating number of
+# really needed pgstat_io histogram slots (this is done to minimize used
+# shared memory).
+pgstat_io_genslots = executable('pgstat_io_genslots',
+ # As this code uses several headers on it's own, we need to be sure
+ # that generation of other headers (other custom_targets) have completed
+ # generation otherwise we may get errors that they don't exist.
+ '../../backend/utils/activity/pgstat_io_genslots.c', generated_nodes[0], errcodes,
+ c_args: '-DPGSTAT_IO_GENSLOTS_STANDALONE',
+ include_directories: [postgres_inc],
+ kwargs: default_bin_args + {
+ 'install': false,
+ },
+)
+bin_targets += pgstat_io_genslots
+
+# Now use above executable to print #define PGSTAT_IO_HIST_BUCKET_SLOTS
+# and save it to pgstat_io_genslots.h that is going to be included when
+# server is going to be built.
+pgstat_io_genslots_header = custom_target('pgstat_io_genslots_header',
+ output: 'pgstat_io_genslots.h',
+ command: [pgstat_io_genslots],
+ capture: true,
+ build_by_default: true,
+ install: true,
+ install_dir: dir_include_server / 'utils',
+)
+generated_backend_headers += pgstat_io_genslots_header
+
# autoconf generates the file there, ensure we get a conflict
generated_sources_ac += {
'src/backend/utils': fmgrtab_output + ['errcodes.h', 'wait_event_types.h', 'probes.h', 'fmgr-stamp'],
--
2.43.0