Hello,
I'd like to propose an enhancement to how PostgreSQL reports resource
usage statistics on Windows, building on a 2017 discussion initiated by
Justin Pryzby [1].
I've been spending some time reviewing windows specific code in the
postgresql codebase. I've notice several "opportunities" for
enhancements and cleanup-- especially if we are only supporting Windows
10+. One of the areas I noticed was getrusage().
The current Windows getrusage() shim in src/port/getrusage.c only
reports CPU times. Everything else is zero. Windows has better
performance APIs than Unix getrusage() in several areas - we're just not
using them.
Back in 2017, Justin Pryzby proposed exposing ru_maxrss [1], and Robert
Haas suggested that if we're going to customize by platform, we should
show only meaningful values per platform [2]. Nobody followed through
with a patch.
Problem
Windows administrators get this:
! 0/0 [0/0] filesystem blocks in/out
! 0/39 [0/1281] page faults/reclaims, 0 [0] swaps
The zeros are uninformative. Windows has the data - I/O byte counts,
detailed memory stats, handle counts - but we're trying to jam it into
struct rusage where it doesn't fit.
I propose we let each platform report what it actually measures, using
native APIs:
Windows: GetProcessMemoryInfo, GetProcessIoCounters, QueryProcessCycleTime
Linux/BSD: Keep using getrusage, maybe expose more fields later
This means platform-specific output formats, but that's better than
reporting zeros. This follows the precedent Robert Haas suggested in
2017 [2]: "if we're going to go to the trouble of customizing this code
by platform, we ought to get rid of values that are meaningless on that
platform."
We already have extensive platform-specific code in
src/backend/port/win32/. This follows the same pattern.
I've included a rough proof-of-concept. It:
(1) Adds PgWin32ResourceUsage extension to PGRUsage
(2) Implements Windows-native collection via additions to pg_rusage_init()
(3) Shows actual I/O bytes, memory details, handle/thread counts
(4) Leaves Unix paths completely untouched
On Windows you'd see:
! Memory: 45232 KB working set (48192 KB peak), 38416 KB private
! I/O: 2457600 bytes read (147 ops), 819200 bytes written (43 ops)
! Resources: 247 handles, 8 threads
Before I invest time in a full patch:
(1) Is there support for this general approach of platform-specific
resource reporting?
(2) Should this extend to non-Windows Unix platforms as well (e.g.,
exposing more Linux-specific fields)?
(3) Any concerns about diverging output formats across platforms?
(4) Would you prefer all platforms to show only common fields, or
platform-native output?
I believe this aligns with PostgreSQL's existing philosophy - we already
have extensive platform-specific code in src/backend/port/win32/, and
this would be a natural extension of that approach.
I'm happy to implement this if there's community interest. Feedback welcome!
[1]
https://www.postgresql.org/message-id/[email protected]
[2] https://www.mail-archive.com/[email protected]/msg316929.html
BG
diff --git a/src/include/utils/pg_rusage.h b/src/include/utils/pg_rusage.h
index 1234567..abcdefg 100644
--- a/src/include/utils/pg_rusage.h
+++ b/src/include/utils/pg_rusage.h
@@ -15,10 +15,31 @@
#include <sys/resource.h>
+#ifdef WIN32
+typedef struct PgWin32ResourceUsage
+{
+ uint64 io_read_bytes;
+ uint64 io_write_bytes;
+ uint64 io_other_bytes;
+ uint32 io_read_ops;
+ uint32 io_write_ops;
+ size_t working_set_size;
+ size_t peak_working_set_size;
+ size_t private_bytes;
+ uint32 page_fault_count;
+ uint32 handle_count;
+ uint32 thread_count;
+} PgWin32ResourceUsage;
+#endif
typedef struct PGRUsage
{
struct rusage ru;
struct timeval tv;
+#ifdef WIN32
+ PgWin32ResourceUsage win32;
+#endif
} PGRUsage;
diff --git a/src/backend/utils/misc/pg_rusage.c
b/src/backend/utils/misc/pg_rusage.c
index 2345678..bcdefgh 100644
--- a/src/backend/utils/misc/pg_rusage.c
+++ b/src/backend/utils/misc/pg_rusage.c
@@ -18,6 +18,12 @@
#include "utils/pg_rusage.h"
+#ifdef WIN32
+#include <windows.h>
+#include <psapi.h>
+#include <tlhelp32.h>
+#endif
+
/*
* Initialize usage snapshot.
@@ -26,8 +32,87 @@ void
pg_rusage_init(PGRUsage *ru0)
{
getrusage(RUSAGE_SELF, &ru0->ru);
gettimeofday(&ru0->tv, NULL);
+
+#ifdef WIN32
+ {
+ HANDLE hProcess = GetCurrentProcess();
+ PROCESS_MEMORY_COUNTERS_EX memCounters;
+ IO_COUNTERS ioCounters;
+
+ memset(&ru0->win32, 0, sizeof(PgWin32ResourceUsage));
+
+ /* Get memory statistics */
+ memset(&memCounters, 0, sizeof(memCounters));
+ memCounters.cb = sizeof(memCounters);
+ if (GetProcessMemoryInfo(hProcess,
+
(PROCESS_MEMORY_COUNTERS *)&memCounters,
+
sizeof(memCounters)))
+ {
+ ru0->win32.working_set_size =
memCounters.WorkingSetSize;
+ ru0->win32.peak_working_set_size =
memCounters.PeakWorkingSetSize;
+ ru0->win32.private_bytes = memCounters.PrivateUsage;
+ ru0->win32.page_fault_count =
memCounters.PageFaultCount;
+ }
+
+ /* Get I/O statistics */
+ memset(&ioCounters, 0, sizeof(ioCounters));
+ if (GetProcessIoCounters(hProcess, &ioCounters))
+ {
+ ru0->win32.io_read_bytes = ioCounters.ReadTransferCount;
+ ru0->win32.io_write_bytes =
ioCounters.WriteTransferCount;
+ ru0->win32.io_other_bytes =
ioCounters.OtherTransferCount;
+ ru0->win32.io_read_ops =
(uint32)ioCounters.ReadOperationCount;
+ ru0->win32.io_write_ops =
(uint32)ioCounters.WriteOperationCount;
+ }
+
+ /* Get handle count */
+ GetProcessHandleCount(hProcess, &ru0->win32.handle_count);
+
+ /* Get thread count using toolhelp32 snapshot */
+ {
+ HANDLE hSnapshot;
+ THREADENTRY32 te32;
+ DWORD dwOwnerPID = GetCurrentProcessId();
+
+ ru0->win32.thread_count = 0;
+
+ hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,
0);
+ if (hSnapshot != INVALID_HANDLE_VALUE)
+ {
+ te32.dwSize = sizeof(THREADENTRY32);
+
+ if (Thread32First(hSnapshot, &te32))
+ {
+ do
+ {
+ if (te32.th32OwnerProcessID ==
dwOwnerPID)
+
ru0->win32.thread_count++;
+ } while (Thread32Next(hSnapshot,
&te32));
+ }
+ CloseHandle(hSnapshot);
+ }
+ }
+ }
+#endif
}
/*
@@ -83,6 +168,26 @@ pg_rusage_show(const PGRUsage *ru0)
(long) (r.ru_oublock -
ru0->ru.ru_oublock));
appendStringInfo(&str, _("!\t%ld/%ld [%ld/%ld] voluntary/involuntary
context switches\n"),
r.ru_nvcsw - ru0->ru.ru_nvcsw,
r.ru_nivcsw - ru0->ru.ru_nivcsw,
r.ru_nvcsw, r.ru_nivcsw);
+
+#ifdef WIN32
+ /* Show Windows-specific extended statistics */
+ appendStringInfo(&str, _("!\tWindows: %zu KB working set (%zu KB peak),
%zu KB private\n"),
+ r_win32.working_set_size / 1024,
+ r_win32.peak_working_set_size / 1024,
+ r_win32.private_bytes / 1024);
+
+ appendStringInfo(&str, _("!\tI/O: %llu bytes read (%u ops), %llu write
(%u ops), %llu other\n"),
+ (unsigned long
long)(r_win32.io_read_bytes - ru0->win32.io_read_bytes),
+ r_win32.io_read_ops -
ru0->win32.io_read_ops,
+ (unsigned long
long)(r_win32.io_write_bytes - ru0->win32.io_write_bytes),
+ r_win32.io_write_ops -
ru0->win32.io_write_ops,
+ (unsigned long
long)(r_win32.io_other_bytes - ru0->win32.io_other_bytes));
+
+ appendStringInfo(&str, _("!\tResources: %u handles, %u threads, %u page
faults\n"),
+ r_win32.handle_count,
+ r_win32.thread_count,
+ r_win32.page_fault_count -
ru0->win32.page_fault_count);
+#endif
return str.data;
}
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index 3456789..cdefghi 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -8,3 +8,7 @@ include $(top_builddir)/src/Makefile.global
OBJS = \
guc.o \
pg_rusage.o
+
+ifeq ($(PORTNAME), win32)
+LIBS += -lpsapi
+endif