public inbox for [email protected]help / color / mirror / Atom feed
[PATCH v7 1/2] Add backtrace support for Windows using DbgHelp API 5+ messages / 2 participants [nested] [flat]
* [PATCH v7 1/2] Add backtrace support for Windows using DbgHelp API @ 2025-11-02 05:04 Bryan Green <[email protected]> 0 siblings, 0 replies; 5+ messages in thread From: Bryan Green @ 2025-11-02 05:04 UTC (permalink / raw) Previously, backtrace generation on Windows would return an "unsupported" message. This patch implements Windows backtrace support using CaptureStackBackTrace() for capturing the call stack and the DbgHelp API (SymFromAddrW, SymGetLineFromAddrW64) for symbol resolution. The implementation provides symbol names, offsets, and addresses. When PDB files are available, it also includes source file names and line numbers. Symbol names and file paths are converted from UTF-16 to the database encoding using wchar2char(), which properly handles both UTF-8 and non-UTF-8 databases on Windows. When symbol information is unavailable or encoding conversion fails, it falls back to displaying raw addresses. The implementation uses the explicit Unicode versions of the DbgHelp functions (SYMBOL_INFOW, SymFromAddrW, IMAGEHLP_LINEW64, SymGetLineFromAddrW64) rather than the generic versions. This is necessary because the generic SYMBOL_INFO becomes SYMBOL_INFOA on PostgreSQL's Windows builds (which don't define UNICODE), providing strings in the Windows ANSI codepage rather than a predictable encoding that can be reliably converted to the database encoding. Symbol handler initialization (SymInitialize) is performed once per process and cached. If initialization fails, a warning is logged and no backtrace is generated. The symbol handler is cleaned up via on_proc_exit() to release DbgHelp resources. Author: Bryan Green Reviewed-by: Euler Taveira, Jakub Wartak --- src/backend/meson.build | 5 ++ src/backend/utils/error/elog.c | 143 ++++++++++++++++++++++++++++++++- 2 files changed, 147 insertions(+), 1 deletion(-) diff --git a/src/backend/meson.build b/src/backend/meson.build index 712a857cdb4..2e7f2be2c78 100644 --- a/src/backend/meson.build +++ b/src/backend/meson.build @@ -1,6 +1,11 @@ # Copyright (c) 2022-2026, PostgreSQL Global Development Group backend_build_deps = [backend_code] + +if host_system == 'windows' and cc.get_id() == 'msvc' + backend_build_deps += cc.find_library('dbghelp') +endif + backend_sources = [] backend_link_with = [pgport_srv, common_srv] diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 129906e2daa..60f95a58f7a 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -66,11 +66,14 @@ #include <execinfo.h> #endif +#ifdef _MSC_VER +#include <dbghelp.h> +#endif + #include "access/xact.h" #include "common/ip.h" #include "libpq/libpq.h" #include "libpq/pqformat.h" -#include "mb/pg_wchar.h" #include "miscadmin.h" #include "nodes/miscnodes.h" #include "pgstat.h" @@ -140,6 +143,11 @@ static void write_syslog(int level, const char *line); static void write_eventlog(int level, const char *line, int len); #endif +#ifdef _MSC_VER +static bool backtrace_symbols_initialized = false; +static HANDLE backtrace_process = NULL; +#endif + /* We provide a small stack of ErrorData records for re-entrant cases */ #define ERRORDATA_STACK_SIZE 5 @@ -1117,11 +1125,30 @@ errbacktrace(void) return 0; } +#ifdef _MSC_VER +/* + * Cleanup function for DbgHelp resources. + * Called via on_proc_exit() to release resources allocated by SymInitialize(). + */ +static void +backtrace_cleanup(int code, Datum arg) +{ + SymCleanup(backtrace_process); +} +#endif + /* * Compute backtrace data and add it to the supplied ErrorData. num_skip * specifies how many inner frames to skip. Use this to avoid showing the * internal backtrace support functions in the backtrace. This requires that * this and related functions are not inlined. + * + * Platform-specific implementations: + * - Unix/Linux: Uses backtrace() and backtrace_symbols() + * - Windows: Uses CaptureStackBackTrace() with DbgHelp for symbol resolution + * (requires PDB files; falls back to exported functions/raw addresses if + * unavailable) + * - Other: Returns unsupported message */ static void set_backtrace(ErrorData *edata, int num_skip) @@ -1148,6 +1175,120 @@ set_backtrace(ErrorData *edata, int num_skip) appendStringInfoString(&errtrace, "insufficient memory for backtrace generation"); } +#elif defined(_MSC_VER) + { + void *buf[100]; + int nframes; + char buffer[sizeof(SYMBOL_INFOW) + MAX_SYM_NAME * sizeof(wchar_t)]; + PSYMBOL_INFOW psymbol; + + if (!backtrace_symbols_initialized) + { + backtrace_process = GetCurrentProcess(); + + SymSetOptions(SYMOPT_UNDNAME | + SYMOPT_DEFERRED_LOADS | + SYMOPT_LOAD_LINES | + SYMOPT_FAIL_CRITICAL_ERRORS); + + if (SymInitialize(backtrace_process, NULL, TRUE)) + { + backtrace_symbols_initialized = true; + on_proc_exit(backtrace_cleanup, 0); + } + else + { + elog(WARNING, "could not initialize the symbol handler: error code %lu", + GetLastError()); + edata->backtrace = errtrace.data; + return; + } + } + + nframes = CaptureStackBackTrace(num_skip, lengthof(buf), buf, NULL); + + if (nframes == 0) + { + appendStringInfoString(&errtrace, "\nNo stack frames captured"); + edata->backtrace = errtrace.data; + return; + } + + psymbol = (PSYMBOL_INFOW) buffer; + psymbol->MaxNameLen = MAX_SYM_NAME; + psymbol->SizeOfStruct = sizeof(SYMBOL_INFOW); + + for (int i = 0; i < nframes; i++) + { + DWORD64 address = (DWORD64) buf[i]; + DWORD64 displacement = 0; + BOOL sym_result; + + sym_result = SymFromAddrW(backtrace_process, + address, + &displacement, + psymbol); + if (sym_result) + { + char symbol_name[MAX_SYM_NAME]; + size_t result; + + /* + * Convert symbol name from UTF-16 to database encoding using + * wchar2char(), which handles both UTF-8 and non-UTF-8 + * databases correctly on Windows. + */ + result = wchar2char(symbol_name, (const wchar_t *) psymbol->Name, + sizeof(symbol_name), NULL); + + if (result == (size_t) -1) + { + /* Conversion failed, use address only */ + appendStringInfo(&errtrace, + "\n[0x%llx]", + (unsigned long long) address); + } + else + { + IMAGEHLP_LINEW64 line; + DWORD line_displacement = 0; + char filename[MAX_PATH]; + + line.SizeOfStruct = sizeof(IMAGEHLP_LINEW64); + + /* Start with the common part: symbol+offset [address] */ + appendStringInfo(&errtrace, + "\n%s+0x%llx [0x%llx]", + symbol_name, + (unsigned long long) displacement, + (unsigned long long) address); + + /* Try to append line info if available */ + if (SymGetLineFromAddrW64(backtrace_process, + address, + &line_displacement, + &line)) + { + result = wchar2char(filename, (const wchar_t *) line.FileName, + sizeof(filename), NULL); + + if (result != (size_t) -1) + { + appendStringInfo(&errtrace, + " [%s:%lu]", + filename, + (unsigned long) line.LineNumber); + } + } + } + } + else + { + elog(WARNING, "symbol lookup failed: error code %lu", + GetLastError()); + } + } + } #else appendStringInfoString(&errtrace, "backtrace generation is not supported by this installation"); -- 2.47.3 --66lzhokyymna6c2w Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="v7-0002-fixups.patch" ^ permalink raw reply [nested|flat] 5+ messages in thread
* [PATCH v8] Add backtrace support for Windows using DbgHelp API @ 2025-11-02 05:04 Bryan Green <[email protected]> 0 siblings, 0 replies; 5+ messages in thread From: Bryan Green @ 2025-11-02 05:04 UTC (permalink / raw) Previously, backtrace generation on Windows would return an "unsupported" message. With this commit, we rely on CaptureStackBackTrace() to capture the call stack and the DbgHelp API (SymFromAddrW, SymGetLineFromAddrW64) for symbol resolution. Symbol handler initialization (SymInitialize) is performed once per process and cached. If initialization fails, the report for it is returned as the backtrace output. The symbol handler is cleaned up via on_proc_exit() to release DbgHelp resources. The implementation provides symbol names, offsets, and addresses. When PDB files are available, it also includes source file names and line numbers. Symbol names and file paths are converted from UTF-16 to the database encoding using wchar2char(), which properly handles both UTF-8 and non-UTF-8 databases on Windows. When symbol information is unavailable or encoding conversion fails, it falls back to displaying raw addresses. The implementation uses the explicit Unicode versions of the DbgHelp functions (SYMBOL_INFOW, SymFromAddrW, IMAGEHLP_LINEW64, SymGetLineFromAddrW64) rather than the generic versions. This allows us to rely on predictable encoding conversion from Unicode, rather than using the haphazard ANSI codepage that we'd get otherwise. DbgHelp is apparently available on all Windows platforms we support, so there are no version number checks. Author: Bryan Green <[email protected]> Reviewed-by: Euler Taveira <[email protected]> Reviewed-by: Jakub Wartak <[email protected]> Reviewed-by: Greg Burd <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/backend/meson.build | 6 ++ src/backend/utils/error/elog.c | 153 ++++++++++++++++++++++++++++++++- 2 files changed, 156 insertions(+), 3 deletions(-) diff --git a/src/backend/meson.build b/src/backend/meson.build index 712a857cdb4..9041cde59fd 100644 --- a/src/backend/meson.build +++ b/src/backend/meson.build @@ -41,6 +41,12 @@ backend_link_args = [] backend_link_depends = [] +# On Windows also make the backend depend on dbghelp, for backtrace support +if host_system == 'windows' and cc.get_id() == 'msvc' + backend_build_deps += cc.find_library('dbghelp') +endif + + # On windows when compiling with msvc we need to make postgres export all its # symbols so that extension libraries can use them. For that we need to scan # the constituting objects and generate a file specifying all the functions as diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index cb1c9d85ffe..d406f18f9d7 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -66,6 +66,10 @@ #include <execinfo.h> #endif +#ifdef _MSC_VER +#include <dbghelp.h> +#endif + #include "access/xact.h" #include "common/ip.h" #include "libpq/libpq.h" @@ -140,6 +144,11 @@ static void write_syslog(int level, const char *line); static void write_eventlog(int level, const char *line, int len); #endif +#ifdef _MSC_VER +static bool backtrace_symbols_initialized = false; +static HANDLE backtrace_process = NULL; +#endif + /* We provide a small stack of ErrorData records for re-entrant cases */ #define ERRORDATA_STACK_SIZE 5 @@ -180,6 +189,7 @@ static void set_stack_entry_location(ErrorData *edata, const char *funcname); static bool matches_backtrace_functions(const char *funcname); static pg_noinline void set_backtrace(ErrorData *edata, int num_skip); +static void backtrace_cleanup(int code, Datum arg); static void set_errdata_field(MemoryContextData *cxt, char **ptr, const char *str); static void FreeErrorDataContents(ErrorData *edata); static int log_min_messages_cmp(const ListCell *a, const ListCell *b); @@ -1122,6 +1132,13 @@ errbacktrace(void) * specifies how many inner frames to skip. Use this to avoid showing the * internal backtrace support functions in the backtrace. This requires that * this and related functions are not inlined. + * + * The implementation is, unsurprisingly, platform-specific: + * - GNU libc and copycats: Uses backtrace() and backtrace_symbols() + * - Windows: Uses CaptureStackBackTrace() with DbgHelp for symbol resolution + * (requires PDB files; falls back to exported functions/raw addresses if + * unavailable) + * - Others (musl libc): unsupported */ static void set_backtrace(ErrorData *edata, int num_skip) @@ -1132,12 +1149,12 @@ set_backtrace(ErrorData *edata, int num_skip) #ifdef HAVE_BACKTRACE_SYMBOLS { - void *buf[100]; + void *frames[100]; int nframes; char **strfrms; - nframes = backtrace(buf, lengthof(buf)); - strfrms = backtrace_symbols(buf, nframes); + nframes = backtrace(frames, lengthof(frames)); + strfrms = backtrace_symbols(frames, nframes); if (strfrms != NULL) { for (int i = num_skip; i < nframes; i++) @@ -1148,6 +1165,123 @@ set_backtrace(ErrorData *edata, int num_skip) appendStringInfoString(&errtrace, "insufficient memory for backtrace generation"); } +#elif defined(_MSC_VER) + { + void *frames[100]; + int nframes; + char buffer[sizeof(SYMBOL_INFOW) + MAX_SYM_NAME * sizeof(wchar_t)]; + PSYMBOL_INFOW psymbol; + + if (!backtrace_symbols_initialized) + { + backtrace_process = GetCurrentProcess(); + + SymSetOptions(SYMOPT_UNDNAME | + SYMOPT_DEFERRED_LOADS | + SYMOPT_LOAD_LINES | + SYMOPT_FAIL_CRITICAL_ERRORS); + + if (SymInitialize(backtrace_process, NULL, TRUE)) + { + backtrace_symbols_initialized = true; + on_proc_exit(backtrace_cleanup, 0); + } + else + { + appendStringInfo(&errtrace, + "could not initialize symbol handler: error code %lu", + GetLastError()); + edata->backtrace = errtrace.data; + return; + } + } + + nframes = CaptureStackBackTrace(num_skip, lengthof(frames), frames, NULL); + + if (nframes == 0) + { + appendStringInfoString(&errtrace, "zero stack frames captured"); + edata->backtrace = errtrace.data; + return; + } + + psymbol = (PSYMBOL_INFOW) buffer; + psymbol->MaxNameLen = MAX_SYM_NAME; + psymbol->SizeOfStruct = sizeof(SYMBOL_INFOW); + + for (int i = 0; i < nframes; i++) + { + DWORD64 address = (DWORD64) frames[i]; + DWORD64 displacement = 0; + BOOL sym_result; + + sym_result = SymFromAddrW(backtrace_process, + address, + &displacement, + psymbol); + if (sym_result) + { + char symbol_name[MAX_SYM_NAME]; + size_t result; + + /* + * Convert symbol name from UTF-16 to database encoding using + * wchar2char(), which handles both UTF-8 and non-UTF-8 + * databases correctly on Windows. + */ + result = wchar2char(symbol_name, (const wchar_t *) psymbol->Name, + sizeof(symbol_name), NULL); + + if (result == (size_t) -1) + { + /* Conversion failed, use address only */ + appendStringInfo(&errtrace, + "\n[0x%llx]", + (unsigned long long) address); + } + else + { + IMAGEHLP_LINEW64 line; + DWORD line_displacement = 0; + char filename[MAX_PATH]; + + line.SizeOfStruct = sizeof(IMAGEHLP_LINEW64); + + /* Start with the common part: symbol+offset [address] */ + appendStringInfo(&errtrace, + "\n%s+0x%llx [0x%llx]", + symbol_name, + (unsigned long long) displacement, + (unsigned long long) address); + + /* Try to append line info if available */ + if (SymGetLineFromAddrW64(backtrace_process, + address, + &line_displacement, + &line)) + { + result = wchar2char(filename, (const wchar_t *) line.FileName, + sizeof(filename), NULL); + + if (result != (size_t) -1) + { + appendStringInfo(&errtrace, + " [%s:%lu]", + filename, + (unsigned long) line.LineNumber); + } + } + } + } + else + { + appendStringInfo(&errtrace, + "\n[0x%llx] (symbol lookup failed: error code %lu)", + (unsigned long long) address, + GetLastError()); + } + } + } #else appendStringInfoString(&errtrace, "backtrace generation is not supported by this installation"); @@ -1156,6 +1290,19 @@ set_backtrace(ErrorData *edata, int num_skip) edata->backtrace = errtrace.data; } +/* + * Cleanup function for DbgHelp resources. + * Called via on_proc_exit() to release resources allocated by SymInitialize(). + */ +pg_attribute_unused() +static void +backtrace_cleanup(int code, Datum arg) +{ +#ifdef _MSC_VER + SymCleanup(backtrace_process); +#endif +} + /* * errmsg_internal --- add a primary error message text to the current error * -- 2.47.3 --h24v6pggzlszygs4-- ^ permalink raw reply [nested|flat] 5+ messages in thread
* [PATCH v7 1/2] Add backtrace support for Windows using DbgHelp API @ 2025-11-02 05:04 Bryan Green <[email protected]> 0 siblings, 0 replies; 5+ messages in thread From: Bryan Green @ 2025-11-02 05:04 UTC (permalink / raw) Previously, backtrace generation on Windows would return an "unsupported" message. This patch implements Windows backtrace support using CaptureStackBackTrace() for capturing the call stack and the DbgHelp API (SymFromAddrW, SymGetLineFromAddrW64) for symbol resolution. The implementation provides symbol names, offsets, and addresses. When PDB files are available, it also includes source file names and line numbers. Symbol names and file paths are converted from UTF-16 to the database encoding using wchar2char(), which properly handles both UTF-8 and non-UTF-8 databases on Windows. When symbol information is unavailable or encoding conversion fails, it falls back to displaying raw addresses. The implementation uses the explicit Unicode versions of the DbgHelp functions (SYMBOL_INFOW, SymFromAddrW, IMAGEHLP_LINEW64, SymGetLineFromAddrW64) rather than the generic versions. This is necessary because the generic SYMBOL_INFO becomes SYMBOL_INFOA on PostgreSQL's Windows builds (which don't define UNICODE), providing strings in the Windows ANSI codepage rather than a predictable encoding that can be reliably converted to the database encoding. Symbol handler initialization (SymInitialize) is performed once per process and cached. If initialization fails, a warning is logged and no backtrace is generated. The symbol handler is cleaned up via on_proc_exit() to release DbgHelp resources. Author: Bryan Green Reviewed-by: Euler Taveira, Jakub Wartak --- src/backend/meson.build | 5 ++ src/backend/utils/error/elog.c | 143 ++++++++++++++++++++++++++++++++- 2 files changed, 147 insertions(+), 1 deletion(-) diff --git a/src/backend/meson.build b/src/backend/meson.build index 712a857cdb4..2e7f2be2c78 100644 --- a/src/backend/meson.build +++ b/src/backend/meson.build @@ -1,6 +1,11 @@ # Copyright (c) 2022-2026, PostgreSQL Global Development Group backend_build_deps = [backend_code] + +if host_system == 'windows' and cc.get_id() == 'msvc' + backend_build_deps += cc.find_library('dbghelp') +endif + backend_sources = [] backend_link_with = [pgport_srv, common_srv] diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 129906e2daa..60f95a58f7a 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -66,11 +66,14 @@ #include <execinfo.h> #endif +#ifdef _MSC_VER +#include <dbghelp.h> +#endif + #include "access/xact.h" #include "common/ip.h" #include "libpq/libpq.h" #include "libpq/pqformat.h" -#include "mb/pg_wchar.h" #include "miscadmin.h" #include "nodes/miscnodes.h" #include "pgstat.h" @@ -140,6 +143,11 @@ static void write_syslog(int level, const char *line); static void write_eventlog(int level, const char *line, int len); #endif +#ifdef _MSC_VER +static bool backtrace_symbols_initialized = false; +static HANDLE backtrace_process = NULL; +#endif + /* We provide a small stack of ErrorData records for re-entrant cases */ #define ERRORDATA_STACK_SIZE 5 @@ -1117,11 +1125,30 @@ errbacktrace(void) return 0; } +#ifdef _MSC_VER +/* + * Cleanup function for DbgHelp resources. + * Called via on_proc_exit() to release resources allocated by SymInitialize(). + */ +static void +backtrace_cleanup(int code, Datum arg) +{ + SymCleanup(backtrace_process); +} +#endif + /* * Compute backtrace data and add it to the supplied ErrorData. num_skip * specifies how many inner frames to skip. Use this to avoid showing the * internal backtrace support functions in the backtrace. This requires that * this and related functions are not inlined. + * + * Platform-specific implementations: + * - Unix/Linux: Uses backtrace() and backtrace_symbols() + * - Windows: Uses CaptureStackBackTrace() with DbgHelp for symbol resolution + * (requires PDB files; falls back to exported functions/raw addresses if + * unavailable) + * - Other: Returns unsupported message */ static void set_backtrace(ErrorData *edata, int num_skip) @@ -1148,6 +1175,120 @@ set_backtrace(ErrorData *edata, int num_skip) appendStringInfoString(&errtrace, "insufficient memory for backtrace generation"); } +#elif defined(_MSC_VER) + { + void *buf[100]; + int nframes; + char buffer[sizeof(SYMBOL_INFOW) + MAX_SYM_NAME * sizeof(wchar_t)]; + PSYMBOL_INFOW psymbol; + + if (!backtrace_symbols_initialized) + { + backtrace_process = GetCurrentProcess(); + + SymSetOptions(SYMOPT_UNDNAME | + SYMOPT_DEFERRED_LOADS | + SYMOPT_LOAD_LINES | + SYMOPT_FAIL_CRITICAL_ERRORS); + + if (SymInitialize(backtrace_process, NULL, TRUE)) + { + backtrace_symbols_initialized = true; + on_proc_exit(backtrace_cleanup, 0); + } + else + { + elog(WARNING, "could not initialize the symbol handler: error code %lu", + GetLastError()); + edata->backtrace = errtrace.data; + return; + } + } + + nframes = CaptureStackBackTrace(num_skip, lengthof(buf), buf, NULL); + + if (nframes == 0) + { + appendStringInfoString(&errtrace, "\nNo stack frames captured"); + edata->backtrace = errtrace.data; + return; + } + + psymbol = (PSYMBOL_INFOW) buffer; + psymbol->MaxNameLen = MAX_SYM_NAME; + psymbol->SizeOfStruct = sizeof(SYMBOL_INFOW); + + for (int i = 0; i < nframes; i++) + { + DWORD64 address = (DWORD64) buf[i]; + DWORD64 displacement = 0; + BOOL sym_result; + + sym_result = SymFromAddrW(backtrace_process, + address, + &displacement, + psymbol); + if (sym_result) + { + char symbol_name[MAX_SYM_NAME]; + size_t result; + + /* + * Convert symbol name from UTF-16 to database encoding using + * wchar2char(), which handles both UTF-8 and non-UTF-8 + * databases correctly on Windows. + */ + result = wchar2char(symbol_name, (const wchar_t *) psymbol->Name, + sizeof(symbol_name), NULL); + + if (result == (size_t) -1) + { + /* Conversion failed, use address only */ + appendStringInfo(&errtrace, + "\n[0x%llx]", + (unsigned long long) address); + } + else + { + IMAGEHLP_LINEW64 line; + DWORD line_displacement = 0; + char filename[MAX_PATH]; + + line.SizeOfStruct = sizeof(IMAGEHLP_LINEW64); + + /* Start with the common part: symbol+offset [address] */ + appendStringInfo(&errtrace, + "\n%s+0x%llx [0x%llx]", + symbol_name, + (unsigned long long) displacement, + (unsigned long long) address); + + /* Try to append line info if available */ + if (SymGetLineFromAddrW64(backtrace_process, + address, + &line_displacement, + &line)) + { + result = wchar2char(filename, (const wchar_t *) line.FileName, + sizeof(filename), NULL); + + if (result != (size_t) -1) + { + appendStringInfo(&errtrace, + " [%s:%lu]", + filename, + (unsigned long) line.LineNumber); + } + } + } + } + else + { + elog(WARNING, "symbol lookup failed: error code %lu", + GetLastError()); + } + } + } #else appendStringInfoString(&errtrace, "backtrace generation is not supported by this installation"); -- 2.47.3 --66lzhokyymna6c2w Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="v7-0002-fixups.patch" ^ permalink raw reply [nested|flat] 5+ messages in thread
* [PATCH v8] Add backtrace support for Windows using DbgHelp API @ 2025-11-02 05:04 Bryan Green <[email protected]> 0 siblings, 0 replies; 5+ messages in thread From: Bryan Green @ 2025-11-02 05:04 UTC (permalink / raw) Previously, backtrace generation on Windows would return an "unsupported" message. With this commit, we rely on CaptureStackBackTrace() to capture the call stack and the DbgHelp API (SymFromAddrW, SymGetLineFromAddrW64) for symbol resolution. Symbol handler initialization (SymInitialize) is performed once per process and cached. If initialization fails, the report for it is returned as the backtrace output. The symbol handler is cleaned up via on_proc_exit() to release DbgHelp resources. The implementation provides symbol names, offsets, and addresses. When PDB files are available, it also includes source file names and line numbers. Symbol names and file paths are converted from UTF-16 to the database encoding using wchar2char(), which properly handles both UTF-8 and non-UTF-8 databases on Windows. When symbol information is unavailable or encoding conversion fails, it falls back to displaying raw addresses. The implementation uses the explicit Unicode versions of the DbgHelp functions (SYMBOL_INFOW, SymFromAddrW, IMAGEHLP_LINEW64, SymGetLineFromAddrW64) rather than the generic versions. This allows us to rely on predictable encoding conversion from Unicode, rather than using the haphazard ANSI codepage that we'd get otherwise. DbgHelp is apparently available on all Windows platforms we support, so there are no version number checks. Author: Bryan Green <[email protected]> Reviewed-by: Euler Taveira <[email protected]> Reviewed-by: Jakub Wartak <[email protected]> Reviewed-by: Greg Burd <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/backend/meson.build | 6 ++ src/backend/utils/error/elog.c | 153 ++++++++++++++++++++++++++++++++- 2 files changed, 156 insertions(+), 3 deletions(-) diff --git a/src/backend/meson.build b/src/backend/meson.build index 712a857cdb4..9041cde59fd 100644 --- a/src/backend/meson.build +++ b/src/backend/meson.build @@ -41,6 +41,12 @@ backend_link_args = [] backend_link_depends = [] +# On Windows also make the backend depend on dbghelp, for backtrace support +if host_system == 'windows' and cc.get_id() == 'msvc' + backend_build_deps += cc.find_library('dbghelp') +endif + + # On windows when compiling with msvc we need to make postgres export all its # symbols so that extension libraries can use them. For that we need to scan # the constituting objects and generate a file specifying all the functions as diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index cb1c9d85ffe..d406f18f9d7 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -66,6 +66,10 @@ #include <execinfo.h> #endif +#ifdef _MSC_VER +#include <dbghelp.h> +#endif + #include "access/xact.h" #include "common/ip.h" #include "libpq/libpq.h" @@ -140,6 +144,11 @@ static void write_syslog(int level, const char *line); static void write_eventlog(int level, const char *line, int len); #endif +#ifdef _MSC_VER +static bool backtrace_symbols_initialized = false; +static HANDLE backtrace_process = NULL; +#endif + /* We provide a small stack of ErrorData records for re-entrant cases */ #define ERRORDATA_STACK_SIZE 5 @@ -180,6 +189,7 @@ static void set_stack_entry_location(ErrorData *edata, const char *funcname); static bool matches_backtrace_functions(const char *funcname); static pg_noinline void set_backtrace(ErrorData *edata, int num_skip); +static void backtrace_cleanup(int code, Datum arg); static void set_errdata_field(MemoryContextData *cxt, char **ptr, const char *str); static void FreeErrorDataContents(ErrorData *edata); static int log_min_messages_cmp(const ListCell *a, const ListCell *b); @@ -1122,6 +1132,13 @@ errbacktrace(void) * specifies how many inner frames to skip. Use this to avoid showing the * internal backtrace support functions in the backtrace. This requires that * this and related functions are not inlined. + * + * The implementation is, unsurprisingly, platform-specific: + * - GNU libc and copycats: Uses backtrace() and backtrace_symbols() + * - Windows: Uses CaptureStackBackTrace() with DbgHelp for symbol resolution + * (requires PDB files; falls back to exported functions/raw addresses if + * unavailable) + * - Others (musl libc): unsupported */ static void set_backtrace(ErrorData *edata, int num_skip) @@ -1132,12 +1149,12 @@ set_backtrace(ErrorData *edata, int num_skip) #ifdef HAVE_BACKTRACE_SYMBOLS { - void *buf[100]; + void *frames[100]; int nframes; char **strfrms; - nframes = backtrace(buf, lengthof(buf)); - strfrms = backtrace_symbols(buf, nframes); + nframes = backtrace(frames, lengthof(frames)); + strfrms = backtrace_symbols(frames, nframes); if (strfrms != NULL) { for (int i = num_skip; i < nframes; i++) @@ -1148,6 +1165,123 @@ set_backtrace(ErrorData *edata, int num_skip) appendStringInfoString(&errtrace, "insufficient memory for backtrace generation"); } +#elif defined(_MSC_VER) + { + void *frames[100]; + int nframes; + char buffer[sizeof(SYMBOL_INFOW) + MAX_SYM_NAME * sizeof(wchar_t)]; + PSYMBOL_INFOW psymbol; + + if (!backtrace_symbols_initialized) + { + backtrace_process = GetCurrentProcess(); + + SymSetOptions(SYMOPT_UNDNAME | + SYMOPT_DEFERRED_LOADS | + SYMOPT_LOAD_LINES | + SYMOPT_FAIL_CRITICAL_ERRORS); + + if (SymInitialize(backtrace_process, NULL, TRUE)) + { + backtrace_symbols_initialized = true; + on_proc_exit(backtrace_cleanup, 0); + } + else + { + appendStringInfo(&errtrace, + "could not initialize symbol handler: error code %lu", + GetLastError()); + edata->backtrace = errtrace.data; + return; + } + } + + nframes = CaptureStackBackTrace(num_skip, lengthof(frames), frames, NULL); + + if (nframes == 0) + { + appendStringInfoString(&errtrace, "zero stack frames captured"); + edata->backtrace = errtrace.data; + return; + } + + psymbol = (PSYMBOL_INFOW) buffer; + psymbol->MaxNameLen = MAX_SYM_NAME; + psymbol->SizeOfStruct = sizeof(SYMBOL_INFOW); + + for (int i = 0; i < nframes; i++) + { + DWORD64 address = (DWORD64) frames[i]; + DWORD64 displacement = 0; + BOOL sym_result; + + sym_result = SymFromAddrW(backtrace_process, + address, + &displacement, + psymbol); + if (sym_result) + { + char symbol_name[MAX_SYM_NAME]; + size_t result; + + /* + * Convert symbol name from UTF-16 to database encoding using + * wchar2char(), which handles both UTF-8 and non-UTF-8 + * databases correctly on Windows. + */ + result = wchar2char(symbol_name, (const wchar_t *) psymbol->Name, + sizeof(symbol_name), NULL); + + if (result == (size_t) -1) + { + /* Conversion failed, use address only */ + appendStringInfo(&errtrace, + "\n[0x%llx]", + (unsigned long long) address); + } + else + { + IMAGEHLP_LINEW64 line; + DWORD line_displacement = 0; + char filename[MAX_PATH]; + + line.SizeOfStruct = sizeof(IMAGEHLP_LINEW64); + + /* Start with the common part: symbol+offset [address] */ + appendStringInfo(&errtrace, + "\n%s+0x%llx [0x%llx]", + symbol_name, + (unsigned long long) displacement, + (unsigned long long) address); + + /* Try to append line info if available */ + if (SymGetLineFromAddrW64(backtrace_process, + address, + &line_displacement, + &line)) + { + result = wchar2char(filename, (const wchar_t *) line.FileName, + sizeof(filename), NULL); + + if (result != (size_t) -1) + { + appendStringInfo(&errtrace, + " [%s:%lu]", + filename, + (unsigned long) line.LineNumber); + } + } + } + } + else + { + appendStringInfo(&errtrace, + "\n[0x%llx] (symbol lookup failed: error code %lu)", + (unsigned long long) address, + GetLastError()); + } + } + } #else appendStringInfoString(&errtrace, "backtrace generation is not supported by this installation"); @@ -1156,6 +1290,19 @@ set_backtrace(ErrorData *edata, int num_skip) edata->backtrace = errtrace.data; } +/* + * Cleanup function for DbgHelp resources. + * Called via on_proc_exit() to release resources allocated by SymInitialize(). + */ +pg_attribute_unused() +static void +backtrace_cleanup(int code, Datum arg) +{ +#ifdef _MSC_VER + SymCleanup(backtrace_process); +#endif +} + /* * errmsg_internal --- add a primary error message text to the current error * -- 2.47.3 --h24v6pggzlszygs4-- ^ permalink raw reply [nested|flat] 5+ messages in thread
* [PATCH v1] Add per-backend AIO statistics @ 2026-06-11 09:45 Bertrand Drouvot <[email protected]> 0 siblings, 0 replies; 5+ messages in thread From: Bertrand Drouvot @ 2026-06-11 09:45 UTC (permalink / raw) This commit adds per-backend AIO statistics, providing per-backend AIO behavior details. This data can be retrieved with a new system function called pg_stat_get_backend_aio(), that returns the following counters based on the PID provided in input: - started: total number of AIO operations initiated - executed_sync: IOs that were executed synchronously (fallback path) - executed_async: IOs that were submitted asynchronously to the IO method - completed_self: IO completions processed by the issuing backend itself - completed_other: IO completions processed on behalf of another backend - handle_waits: times the backend had to wait for a free AIO handle - submitted: number of submitted calls to the IO method These counters are useful for understanding and tuning AIO behavior: - executed_async / started. A ratio near zero means the backend is falling back to synchronous execution (TOAST chunk fetches, temp buffers, ...). - a non-zero handle_waits means the backend exhausted all its AIO handles. That could mean that io_max_concurrency is too low. - completed_self vs completed_other reveals cross-backend completion patterns. That helps see how IO completion work is distributed and could help interpret per backend IO statistics values. - executed_async / submitted gives the average batch size per submit call. This commit is straight-forward, relying on the infrastructure provided by 9aea73fc61d4 (backend-level pgstats). XXX: Bump catalog version. No need to touch PGSTAT_FILE_FORMAT_ID as backend statistics are never written to disk. Author: Bertrand Drouvot <[email protected]> Reviewed-by: Discussion: --- doc/src/sgml/monitoring.sgml | 70 +++++++++++++ src/backend/storage/aio/aio.c | 13 +++ src/backend/utils/activity/pgstat_backend.c | 109 ++++++++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 60 +++++++++++ src/include/catalog/pg_proc.dat | 7 ++ src/include/pgstat.h | 25 +++++ src/include/utils/pgstat_internal.h | 3 +- src/test/modules/test_aio/t/001_aio.pl | 32 ++++++ src/tools/pgindent/typedefs.list | 1 + 9 files changed, 319 insertions(+), 1 deletion(-) 24.4% doc/src/sgml/ 3.2% src/backend/storage/aio/ 25.3% src/backend/utils/activity/ 20.5% src/backend/utils/adt/ 4.9% src/include/catalog/ 10.0% src/include/ 11.1% src/test/modules/test_aio/t/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 12b9ee20d4a..1c566cf0e0e 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -5641,6 +5641,76 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry id="pg-stat-get-backend-aio" role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_aio</primary> + </indexterm> + <function>pg_stat_get_backend_aio</function> ( <type>integer</type> ) + <returnvalue>record</returnvalue> + </para> + <para> + Returns <acronym>AIO</acronym> (Asynchronous I/O) statistics about the + backend with the specified process ID. The returned values are: + <itemizedlist> + <listitem> + <para> + <literal>started</literal>: Total IOs initiated. + </para> + </listitem> + <listitem> + <para> + <literal>executed_sync</literal>: IOs executed synchronously. + </para> + </listitem> + <listitem> + <para> + <literal>executed_async</literal>: IOs submitted asynchronously. + </para> + </listitem> + <listitem> + <para> + <literal>completed_self</literal>: IO completions processed by this + backend. + </para> + </listitem> + <listitem> + <para> + <literal>completed_other</literal>: IO completions processed on + behalf of other backends. + </para> + </listitem> + <listitem> + <para> + <literal>handle_waits</literal>: Times waited for a free AIO handle. + </para> + </listitem> + <listitem> + <para> + <literal>submitted</literal>: Number of submit calls to the IO + method. Compare with <literal>executed_async</literal> to determine + average batch size. + </para> + </listitem> + <listitem> + <para> + <literal>stats_reset</literal>: Timestamp of last stats reset. + </para> + </listitem> + </itemizedlist> + </para> + <para> + The <literal>completed_other</literal> column is only meaningful + when <varname>io_method</varname> is set to <literal>io_uring</literal>; + with <literal>worker</literal> mode, IO completions are processed by + IO worker processes which do not track these statistics. + </para> + <para> + The function does not return AIO statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/storage/aio/aio.c b/src/backend/storage/aio/aio.c index 8f7e26607b9..507f1727d41 100644 --- a/src/backend/storage/aio/aio.c +++ b/src/backend/storage/aio/aio.c @@ -40,6 +40,7 @@ #include "lib/ilist.h" #include "miscadmin.h" +#include "pgstat.h" #include "port/atomics.h" #include "storage/aio.h" #include "storage/aio_internal.h" @@ -458,6 +459,8 @@ pgaio_io_stage(PgAioHandle *ioh, PgAioOp op) "staged (synchronous: %d, in_batch: %d)", needs_synchronous, pgaio_my_backend->in_batchmode); + pgstat_count_backend_aio_start(needs_synchronous); + if (!needs_synchronous) { pgaio_my_backend->staged_ios[pgaio_my_backend->num_staged_ios++] = ioh; @@ -544,6 +547,12 @@ pgaio_io_process_completion(PgAioHandle *ioh, int result) /* condition variable broadcast ensures state is visible before wakeup */ ConditionVariableBroadcast(&ioh->cv); + /* Track AIO completion stats */ + if (ioh->owner_procno == MyProcNumber) + pgstat_count_backend_aio_complete_self(); + else + pgstat_count_backend_aio_complete_other(); + /* contains call to pgaio_io_call_complete_local() */ if (ioh->owner_procno == MyProcNumber) pgaio_io_reclaim(ioh); @@ -762,6 +771,8 @@ pgaio_io_wait_for_free(void) { int reclaimed = 0; + pgstat_count_backend_aio_handle_wait(); + pgaio_debug(DEBUG2, "waiting for free IO with %d pending, %u in-flight, %u idle IOs", pgaio_my_backend->num_staged_ios, dclist_count(&pgaio_my_backend->in_flight_ios), @@ -1150,6 +1161,8 @@ pgaio_submit_staged(void) Assert(total_submitted == did_submit); + pgstat_count_backend_aio_submitted(); + pgaio_my_backend->num_staged_ios = 0; pgaio_debug(DEBUG4, diff --git a/src/backend/utils/activity/pgstat_backend.c b/src/backend/utils/activity/pgstat_backend.c index b736b2ccc6f..4ad755f7f18 100644 --- a/src/backend/utils/activity/pgstat_backend.c +++ b/src/backend/utils/activity/pgstat_backend.c @@ -40,6 +40,7 @@ static PgStat_BackendPending PendingBackendStats; static bool backend_has_iostats = false; static bool backend_has_lockstats = false; +static bool backend_has_aiostats = false; /* * WAL usage counters saved from pgWalUsage at the previous call to @@ -120,6 +121,74 @@ pgstat_count_backend_lock_fastpath_exceeded(uint8 locktag_type) pgstat_report_fixed = true; } +/* + * Utility routines to report AIO stats for backends, kept here to avoid + * exposing PendingBackendStats to the outside world. + */ +void +pgstat_count_backend_aio_start(bool synchronous) +{ + if (!pgstat_tracks_backend_bktype(MyBackendType)) + return; + + PendingBackendStats.aio_counters.started++; + if (synchronous) + PendingBackendStats.aio_counters.executed_sync++; + else + PendingBackendStats.aio_counters.executed_async++; + + backend_has_aiostats = true; + pgstat_report_fixed = true; +} + +void +pgstat_count_backend_aio_complete_self(void) +{ + if (!pgstat_tracks_backend_bktype(MyBackendType)) + return; + + PendingBackendStats.aio_counters.completed_self++; + + backend_has_aiostats = true; + pgstat_report_fixed = true; +} + +void +pgstat_count_backend_aio_complete_other(void) +{ + if (!pgstat_tracks_backend_bktype(MyBackendType)) + return; + + PendingBackendStats.aio_counters.completed_other++; + + backend_has_aiostats = true; + pgstat_report_fixed = true; +} + +void +pgstat_count_backend_aio_handle_wait(void) +{ + if (!pgstat_tracks_backend_bktype(MyBackendType)) + return; + + PendingBackendStats.aio_counters.handle_waits++; + + backend_has_aiostats = true; + pgstat_report_fixed = true; +} + +void +pgstat_count_backend_aio_submitted(void) +{ + if (!pgstat_tracks_backend_bktype(MyBackendType)) + return; + + PendingBackendStats.aio_counters.submitted++; + + backend_has_aiostats = true; + pgstat_report_fixed = true; +} + /* * Returns statistics of a backend by proc number. */ @@ -326,6 +395,38 @@ pgstat_flush_backend_entry_lock(PgStat_EntryRef *entry_ref) backend_has_lockstats = false; } +/* + * Flush out locally pending backend AIO statistics. Locking is managed + * by the caller. + */ +static void +pgstat_flush_backend_entry_aio(PgStat_EntryRef *entry_ref) +{ + PgStatShared_Backend *shbackendent; + PgStat_AioCounters *bktype_shstats; + + if (!backend_has_aiostats) + return; + + shbackendent = (PgStatShared_Backend *) entry_ref->shared_stats; + bktype_shstats = &shbackendent->stats.aio_counters; + +#define AIOSTAT_ACC(fld) \ + (bktype_shstats->fld += PendingBackendStats.aio_counters.fld) + AIOSTAT_ACC(started); + AIOSTAT_ACC(executed_sync); + AIOSTAT_ACC(executed_async); + AIOSTAT_ACC(completed_self); + AIOSTAT_ACC(completed_other); + AIOSTAT_ACC(handle_waits); + AIOSTAT_ACC(submitted); +#undef AIOSTAT_ACC + + MemSet(&PendingBackendStats.aio_counters, 0, sizeof(PgStat_AioCounters)); + + backend_has_aiostats = false; +} + /* * Flush out locally pending backend statistics * @@ -354,6 +455,10 @@ pgstat_flush_backend(bool nowait, uint32 flags) if ((flags & PGSTAT_BACKEND_FLUSH_LOCK) && backend_has_lockstats) has_pending_data = true; + /* Some AIO data pending? */ + if ((flags & PGSTAT_BACKEND_FLUSH_AIO) && backend_has_aiostats) + has_pending_data = true; + if (!has_pending_data) return false; @@ -372,6 +477,9 @@ pgstat_flush_backend(bool nowait, uint32 flags) if (flags & PGSTAT_BACKEND_FLUSH_LOCK) pgstat_flush_backend_entry_lock(entry_ref); + if (flags & PGSTAT_BACKEND_FLUSH_AIO) + pgstat_flush_backend_entry_aio(entry_ref); + pgstat_unlock_entry(entry_ref); return false; @@ -411,6 +519,7 @@ pgstat_create_backend(ProcNumber procnum) MemSet(&PendingBackendStats, 0, sizeof(PgStat_BackendPending)); backend_has_iostats = false; backend_has_lockstats = false; + backend_has_aiostats = false; /* * Initialize prevBackendWalUsage with pgWalUsage so that diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 565d0e70768..fb62a56f3fe 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -1722,6 +1722,66 @@ pg_stat_get_backend_wal(PG_FUNCTION_ARGS) return (pg_stat_wal_build_tuple(bktype_stats, backend_stats->stat_reset_timestamp)); } +/* + * Returns AIO statistics for a backend with given PID. + */ +Datum +pg_stat_get_backend_aio(PG_FUNCTION_ARGS) +{ +#define PG_STAT_BACKEND_AIO_COLS 8 + TupleDesc tupdesc; + Datum values[PG_STAT_BACKEND_AIO_COLS] = {0}; + bool nulls[PG_STAT_BACKEND_AIO_COLS] = {0}; + int pid; + PgStat_Backend *backend_stats; + PgStat_AioCounters aio_counters; + + pid = PG_GETARG_INT32(0); + backend_stats = pgstat_fetch_stat_backend_by_pid(pid, NULL); + + if (!backend_stats) + PG_RETURN_NULL(); + + aio_counters = backend_stats->aio_counters; + + /* Initialise attributes information in the tuple descriptor */ + tupdesc = CreateTemplateTupleDesc(PG_STAT_BACKEND_AIO_COLS); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "started", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "executed_sync", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "executed_async", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "completed_self", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "completed_other", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "handle_waits", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "submitted", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "stats_reset", + TIMESTAMPTZOID, -1, 0); + TupleDescFinalize(tupdesc); + BlessTupleDesc(tupdesc); + + /* Fill values */ + values[0] = Int64GetDatum(aio_counters.started); + values[1] = Int64GetDatum(aio_counters.executed_sync); + values[2] = Int64GetDatum(aio_counters.executed_async); + values[3] = Int64GetDatum(aio_counters.completed_self); + values[4] = Int64GetDatum(aio_counters.completed_other); + values[5] = Int64GetDatum(aio_counters.handle_waits); + values[6] = Int64GetDatum(aio_counters.submitted); + + if (backend_stats->stat_reset_timestamp != 0) + values[7] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[7] = true; + + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + /* * Returns statistics of WAL activity */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 3cb84359adf..a089860ada4 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -6108,6 +6108,13 @@ proargmodes => '{i,o,o,o,o,o}', proargnames => '{backend_pid,locktype,waits,wait_time,fastpath_exceeded,stats_reset}', prosrc => 'pg_stat_get_backend_lock' }, +{ oid => '9082', descr => 'statistics: backend AIO activity', + proname => 'pg_stat_get_backend_aio', provolatile => 'v', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int8,int8,int8,int8,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o,o,o,o}', + proargnames => '{backend_pid,started,executed_sync,executed_async,completed_self,completed_other,handle_waits,submitted,stats_reset}', + prosrc => 'pg_stat_get_backend_aio' }, { oid => '6248', descr => 'statistics: information about WAL prefetching', proname => 'pg_stat_get_recovery_prefetch', prorows => '1', proretset => 't', provolatile => 'v', prorettype => 'record', proargtypes => '', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 58a44857f13..64afb2bc082 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -514,6 +514,21 @@ typedef struct PgStat_WalStats TimestampTz stat_reset_timestamp; } PgStat_WalStats; +/* ------- + * PgStat_AioCounters AIO activity counters + * ------- + */ +typedef struct PgStat_AioCounters +{ + PgStat_Counter started; + PgStat_Counter executed_sync; + PgStat_Counter executed_async; + PgStat_Counter completed_self; + PgStat_Counter completed_other; + PgStat_Counter handle_waits; + PgStat_Counter submitted; +} PgStat_AioCounters; + /* ------- * PgStat_Backend Backend statistics * ------- @@ -524,6 +539,7 @@ typedef struct PgStat_Backend PgStat_BktypeIO io_stats; PgStat_WalCounters wal_counters; PgStat_PendingLock lock_stats; + PgStat_AioCounters aio_counters; } PgStat_Backend; /* --------- @@ -542,6 +558,8 @@ typedef struct PgStat_BackendPending * PGSTAT_KIND_LOCK. */ PgStat_PendingLock pending_lock; + /* Store the AIO statistics counters */ + PgStat_AioCounters aio_counters; } PgStat_BackendPending; /* @@ -598,6 +616,13 @@ extern void pgstat_count_backend_io_op(IOObject io_object, extern void pgstat_count_backend_lock_waits(uint8 locktag_type, PgStat_Counter usecs); extern void pgstat_count_backend_lock_fastpath_exceeded(uint8 locktag_type); +/* used by aio.c for AIO stats tracked in backends */ +extern void pgstat_count_backend_aio_start(bool synchronous); +extern void pgstat_count_backend_aio_complete_self(void); +extern void pgstat_count_backend_aio_complete_other(void); +extern void pgstat_count_backend_aio_handle_wait(void); +extern void pgstat_count_backend_aio_submitted(void); + extern PgStat_Backend *pgstat_fetch_stat_backend(ProcNumber procNumber); extern PgStat_Backend *pgstat_fetch_stat_backend_by_pid(int pid, BackendType *bktype); diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h index b3dc3ff7d8b..b2092fb42b9 100644 --- a/src/include/utils/pgstat_internal.h +++ b/src/include/utils/pgstat_internal.h @@ -706,7 +706,8 @@ extern void pgstat_archiver_snapshot_cb(void); #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_LOCK (1 << 2) /* Flush lock statistics */ -#define PGSTAT_BACKEND_FLUSH_ALL (PGSTAT_BACKEND_FLUSH_IO | PGSTAT_BACKEND_FLUSH_WAL | PGSTAT_BACKEND_FLUSH_LOCK) +#define PGSTAT_BACKEND_FLUSH_AIO (1 << 3) /* Flush AIO statistics */ +#define PGSTAT_BACKEND_FLUSH_ALL (PGSTAT_BACKEND_FLUSH_IO | PGSTAT_BACKEND_FLUSH_WAL | PGSTAT_BACKEND_FLUSH_LOCK | PGSTAT_BACKEND_FLUSH_AIO) extern bool pgstat_flush_backend(bool nowait, uint32 flags); extern bool pgstat_backend_flush_cb(bool nowait); diff --git a/src/test/modules/test_aio/t/001_aio.pl b/src/test/modules/test_aio/t/001_aio.pl index 63cadd64c15..8ecf3d3ad91 100644 --- a/src/test/modules/test_aio/t/001_aio.pl +++ b/src/test/modules/test_aio/t/001_aio.pl @@ -1842,6 +1842,37 @@ read_buffers('$table', 0, 4)|, $psql_c->quit(); } +# Test per-backend AIO statistics counters +sub test_aio_stats +{ + my $io_method = shift; + my $node = shift; + + my $psql = $node->background_psql('postgres', on_error_stop => 0); + + # Reset backend stats, evict relation, then read it back to force + # physical IO through the AIO layer. + $psql->query_safe(qq(SELECT pg_stat_reset_backend_stats(pg_backend_pid()))); + $psql->query_safe(qq(SELECT evict_rel('tbl_ok'))); + $psql->query_safe(qq(SELECT count(*) FROM tbl_ok)); + $psql->query_safe(qq(SELECT pg_stat_force_next_flush())); + + # started must be > 0 after physical IO + my $started = $psql->query_safe( + qq(SELECT started FROM pg_stat_get_backend_aio(pg_backend_pid()))); + cmp_ok($started, '>', 0, + "$io_method: AIO stats: started > 0 after physical IO"); + + # invariant: started = executed_sync + executed_async + my $consistent = $psql->query_safe( + qq(SELECT started = executed_sync + executed_async + FROM pg_stat_get_backend_aio(pg_backend_pid()))); + is($consistent, 't', + "$io_method: AIO stats: started = executed_sync + executed_async"); + + $psql->quit(); +} + # Run all tests that for the specified node / io_method sub test_io_method { @@ -1878,6 +1909,7 @@ CHECKPOINT; test_ignore_checksum($io_method, $node); test_checksum_createdb($io_method, $node); test_read_buffers($io_method, $node); + test_aio_stats($io_method, $node); # generic injection tests SKIP: diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 117e7379f10..6ce3077fa5b 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2329,6 +2329,7 @@ PgStatShared_ReplSlot PgStatShared_SLRU PgStatShared_Subscription PgStatShared_Wal +PgStat_AioCounters PgStat_ArchiverStats PgStat_Backend PgStat_BackendPending -- 2.34.1 --Pi0/Nu3LVhi0Ij+H-- ^ permalink raw reply [nested|flat] 5+ messages in thread
end of thread, other threads:[~2026-06-11 09:45 UTC | newest] Thread overview: 5+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2025-11-02 05:04 [PATCH v7 1/2] Add backtrace support for Windows using DbgHelp API Bryan Green <[email protected]> 2025-11-02 05:04 [PATCH v8] Add backtrace support for Windows using DbgHelp API Bryan Green <[email protected]> 2025-11-02 05:04 [PATCH v7 1/2] Add backtrace support for Windows using DbgHelp API Bryan Green <[email protected]> 2025-11-02 05:04 [PATCH v8] Add backtrace support for Windows using DbgHelp API Bryan Green <[email protected]> 2026-06-11 09:45 [PATCH v1] Add per-backend AIO statistics Bertrand Drouvot <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox