From 481ab56cc33564c3b2dbcdadd49ebc620bd75fca Mon Sep 17 00:00:00 2001 From: Bryan Green Date: Fri, 30 Oct 2026 16:23:14 -0500 Subject: [PATCH v1 1/3] Add an ETW backend for the static probes on Windows The static probes can be traced with DTrace or SystemTap on platforms that have them, but PostgreSQL has no backend for Windows' own tracing facility, Event Tracing for Windows (ETW). Windows builds therefore use the dummy probes.h, and the probes are compiled out. Add an ETW backend for Windows builds, using the manifest-free TraceLogging API, so the same probes can be recorded with the standard Windows tools. Neither the probe set nor the existing call sites change; only the macro expansion does. A new meson option -Detw enables the backend. It needs from the Windows SDK, so it is available in Visual Studio builds; MinGW does not ship that header, so MinGW builds keep the dummy probes (with -Detw=auto silently, with -Detw=enabled as a configure error). It is mutually exclusive with -Ddtrace. probes.d remains the source of truth for the probe arguments. The ETW-specific parts (the keyword and level per event, and a name per argument) live in a new file, probes_etw.txt, which repeats each argument's type. Gen_etw_probes.pl fails the build if the two files disagree, so a type change can't silently mislabel a field. Meson and make builds without ETW run the same check before generating probes.h, so a probe change that needs a matching probes_etw.txt line fails on the platform where it is made, not later on Windows. Events are written to a single provider named "PostgreSQL.Server", registered once per process and torn down via on_proc_exit. The provider GUID is the standard ETW hash of that name, as TraceLoggingProvider.h recommends, so tools that derive a GUID from a provider name find it without the literal. String arguments are tagged as UTF-8 but passed through in the server encoding, and are clipped to 32kB, since ETW drops an event larger than about 64kB entirely and query text can be arbitrarily long. Probe arguments are evaluated only when a session has enabled the event. --- meson.build | 32 ++++ meson_options.txt | 3 + src/backend/postmaster/postmaster.c | 4 + src/backend/utils/Gen_etw_probes.pl | 253 ++++++++++++++++++++++++++++ src/backend/utils/Makefile | 10 +- src/backend/utils/init/miscinit.c | 7 + src/backend/utils/meson.build | 5 + src/backend/utils/pg_trace_etw.c | 102 +++++++++++ src/backend/utils/probes_etw.txt | 103 +++++++++++ src/include/utils/meson.build | 32 ++++ src/include/utils/pg_trace_etw.h | 81 +++++++++ 11 files changed, 629 insertions(+), 3 deletions(-) create mode 100644 src/backend/utils/Gen_etw_probes.pl create mode 100644 src/backend/utils/pg_trace_etw.c create mode 100644 src/backend/utils/probes_etw.txt create mode 100644 src/include/utils/pg_trace_etw.h diff --git a/meson.build b/meson.build index 7e95fd5941d..188af8256e0 100644 --- a/meson.build +++ b/meson.build @@ -1127,6 +1127,37 @@ endif +############################################################### +# Library: ETW (Event Tracing for Windows) +# +# Only a header from the Windows SDK is needed. Check for the newest +# TraceLogging macro the generated probes use, since older SDKs have the +# header without it. +############################################################### + +etwopt = get_option('etw') +etw = not_found_dep +if host_system == 'windows' and not etwopt.disabled() + if cc.has_header_symbol('TraceLoggingProvider.h', + 'TraceLoggingCountedUtf8String', + prefix: '#include ', + args: test_c_args) + cdata.set('USE_ETW', 1) + etw = declare_dependency() + elif etwopt.enabled() + error('ETW support requested but a suitable was not found (it is part of the Windows SDK; MinGW does not provide it)') + endif +elif etwopt.enabled() + error('ETW support is only available on Windows') +endif + +# Only one backend can generate probes.h from probes.d. +if etw.found() and dtrace.found() + error('ETW and DTrace probe support are mutually exclusive') +endif + + + ############################################################### # Library: libxml ############################################################### @@ -4330,6 +4361,7 @@ summary( 'bsd_auth': bsd_auth, 'docs': docs_dep, 'docs_pdf': docs_pdf_dep, + 'etw': etw, 'gss': gssapi, 'icu': icu, 'ldap': ldap, diff --git a/meson_options.txt b/meson_options.txt index 6a793f3e479..386cd0b52c7 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -91,6 +91,9 @@ option('docs_html_style', type: 'combo', choices: ['simple', 'website'], option('dtrace', type: 'feature', value: 'disabled', description: 'DTrace support') +option('etw', type: 'feature', value: 'auto', + description: 'ETW (Event Tracing for Windows) probe support') + option('gssapi', type: 'feature', value: 'auto', description: 'GSSAPI support') diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index f53aa9c404d..cc87326c0f2 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -120,6 +120,7 @@ #include "tcop/tcopprot.h" #include "utils/datetime.h" #include "utils/memutils.h" +#include "utils/pg_trace_etw.h" #include "utils/pidfile.h" #include "utils/timestamp.h" #include "utils/varlena.h" @@ -508,6 +509,9 @@ PostmasterMain(int argc, char *argv[]) IsPostmasterEnvironment = true; + /* Register the ETW trace provider, if built with ETW support. */ + pg_trace_etw_register(); + /* * Start our win32 signal implementation */ diff --git a/src/backend/utils/Gen_etw_probes.pl b/src/backend/utils/Gen_etw_probes.pl new file mode 100644 index 00000000000..471e7984c12 --- /dev/null +++ b/src/backend/utils/Gen_etw_probes.pl @@ -0,0 +1,253 @@ +#!/usr/bin/perl +#---------------------------------------------------------------------- +# +# Generate a TraceLogging probes.h for ETW builds from probes.d and +# probes_etw.txt. +# +# Reads the probe argument types from probes.d and the ETW keyword, level, +# and field names from probes_etw.txt, and emits the same TRACE_POSTGRESQL_* +# macros as the dtrace and dummy backends. Dies if the two files disagree on +# the probe set or on any argument's type, or if probes.d uses a type with no +# mapping below. +# +# With --check, only the checks are done and nothing is written. Builds +# without ETW run it that way, so that probes_etw.txt is kept in step with +# probes.d on every platform. +# +# Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group +# Portions Copyright (c) 1994, Regents of the University of California +# +# src/backend/utils/Gen_etw_probes.pl +# +#---------------------------------------------------------------------- + +use strict; +use warnings FATAL => 'all'; +use Getopt::Long; + +# probes.d type -> [ TraceLogging macro, cast applied to the argument value ]. +# The cast is '' for none, 'STRING' for strings (see below), or a C cast token +# to prepend, used to widen 'long' and the pointer-sized size_t and ssize_t to +# a fixed 64-bit field. +my %type_map = ( + 'LocalTransactionId' => [ 'TraceLoggingUInt32', '' ], + 'BlockNumber' => [ 'TraceLoggingUInt32', '' ], + 'Oid' => [ 'TraceLoggingUInt32', '' ], + 'unsigned int' => [ 'TraceLoggingUInt32', '' ], + 'LWLockMode' => [ 'TraceLoggingInt32', '' ], + 'LOCKMODE' => [ 'TraceLoggingInt32', '' ], + 'ForkNumber' => [ 'TraceLoggingInt32', '' ], + 'int' => [ 'TraceLoggingInt32', '' ], + 'bool' => [ 'TraceLoggingBoolean', '' ], + 'unsigned char' => [ 'TraceLoggingUInt8', '' ], + 'long' => [ 'TraceLoggingInt64', '(LONGLONG)' ], + 'ssize_t' => [ 'TraceLoggingInt64', '(LONGLONG)' ], + 'size_t' => [ 'TraceLoggingUInt64', '(ULONGLONG)' ], + 'const char *' => [ 'TraceLoggingCountedUtf8String', 'STRING' ],); + +my %keyword_token = map { $_ => "PG_KW_$_" } + qw(QUERY TRANSACTION LOCK BUFFER STORAGE WAL CHECKPOINT SORT); + +my %level_token = ( + INFO => 'WINEVENT_LEVEL_INFO', + VERBOSE => 'WINEVENT_LEVEL_VERBOSE',); + +my $check_only = 0; + +GetOptions('check' => \$check_only) + or die "usage: $0 [--check] probes.d probes_etw.txt\n"; + +my ($probes_d, $probes_etw) = @ARGV; +defined $probes_d && defined $probes_etw + or die "usage: $0 [--check] probes.d probes_etw.txt\n"; + +# Read probes.d into a map from probe name to its list of argument types. +my @order; # probes.d order, used for the output +my %args; +open(my $pd, '<', $probes_d) or die "could not open \"$probes_d\": $!\n"; +while (my $line = <$pd>) +{ + next unless $line =~ /^\s*probe\s+([^(]+)\(([^)]*)\)\s*;/; + my ($name, $arglist) = ($1, $2); + $name =~ s/\s+//g; + $arglist =~ s/^\s+|\s+$//g; + my @types; + if (length $arglist) + { + for my $t (split /\s*,\s*/, $arglist) + { + $t =~ s/^\s+|\s+$//g; + $t =~ s/\s+/ /g; + $t =~ s/\s*\*$/ */; + push @types, $t; + } + } + push @order, $name; + $args{$name} = \@types; +} +close $pd; + +# Read probes_etw.txt into a map from probe name to its keyword, level, +# argument types and field names. +my %policy; +open(my $pf, '<', $probes_etw) or die "could not open \"$probes_etw\": $!\n"; +while (my $line = <$pf>) +{ + $line =~ s/#.*$//; + next unless $line =~ /\S/; + $line =~ s/^\s+|\s+$//g; + my ($name, $kw, $lvl, $arglist) = split ' ', $line, 4; + defined $kw && defined $lvl + or die "$probes_etw: probe \"$name\" is missing keyword/level\n"; + exists $keyword_token{$kw} + or die "$probes_etw: probe \"$name\" has unknown keyword \"$kw\"\n"; + exists $level_token{$lvl} + or die "$probes_etw: probe \"$name\" has unknown level \"$lvl\"\n"; + + # Each argument is "type field-name", as in a C prototype. + my (@types, @fields); + if (defined $arglist) + { + for my $decl (split /\s*,\s*/, $arglist) + { + $decl =~ /^(.*?)\s*(\w+)$/ + or die "$probes_etw: probe \"$name\": bad argument \"$decl\"\n"; + my ($t, $f) = ($1, $2); + $t =~ s/\s+/ /g; + $t =~ s/\s*\*$/ */; + push @types, $t; + push @fields, $f; + } + } + $policy{$name} = { + keyword => $kw, + level => $lvl, + types => \@types, + fields => \@fields + }; +} +close $pf; + +# Check that the two files agree. +for my $name (@order) +{ + exists $policy{$name} + or die "probe \"$name\" is in $probes_d but missing from $probes_etw\n"; + my $nargs = scalar @{ $args{$name} }; + my $nfields = scalar @{ $policy{$name}{fields} }; + $nargs == $nfields + or die "probe \"$name\": $probes_d has $nargs argument(s) but " + . "$probes_etw names $nfields field(s)\n"; + for my $i (0 .. $nargs - 1) + { + my $t = $args{$name}[$i]; + my $pt = $policy{$name}{types}[$i]; + $t eq $pt + or die "probe \"$name\": argument " + . ($i + 1) + . " is \"$t\" in $probes_d but \"$pt\" in $probes_etw\n"; + exists $type_map{$t} + or die + "$probes_d: probe \"$name\": no ETW type mapping for \"$t\"\n"; + } +} +for my $name (sort keys %policy) +{ + exists $args{$name} + or die "probe \"$name\" is in $probes_etw but missing from $probes_d\n"; +} + +exit 0 if $check_only; + +# Write the header. +print <<'EOH'; +/* + * This file is generated by Gen_etw_probes.pl from probes.d and + * probes_etw.txt. Do not edit by hand. + * + * It maps the TRACE_POSTGRESQL_* probe macros to ETW (Event Tracing for + * Windows) TraceLogging events. The macro names are identical to those of the + * DTrace and dummy backends, so probe call sites are unchanged. + */ + +#include "utils/pg_trace_etw.h" + +EOH + +for my $name (@order) +{ + my @types = @{ $args{$name} }; + my $pol = $policy{$name}; + my @fields = @{ $pol->{fields} }; + my $kw = $keyword_token{ $pol->{keyword} }; + my $lvl = $level_token{ $pol->{level} }; + + # The event name is the probe name with each double underscore collapsed + # to one, i.e. the DTrace name with underscores in place of hyphens. + (my $event = $name) =~ s/__/_/g; + + # The macro name is TRACE_POSTGRESQL_ plus the upper-cased event name. + (my $macro = $event) =~ tr/a-z/A-Z/; + $macro = "TRACE_POSTGRESQL_$macro"; + + # Macro parameters are positional (arg1, arg2, ...), as in the other + # backends; the field names come from probes_etw.txt. + my @params = map { "arg$_" } 1 .. @types; + my $paramlist = join(', ', @params); + + # String arguments become counted UTF-8 strings, clipped by + # pg_trace_etw_strlen(). The pointer is used twice, so it is evaluated + # once into a local, inside a check that the event is enabled. + my $has_string = scalar grep { $type_map{$_}[1] eq 'STRING' } @types; + my $ind = $has_string ? "\t\t\t" : "\t"; + + my @values; + my @locals; + for my $i (0 .. $#types) + { + my ($tlmacro, $cast) = @{ $type_map{ $types[$i] } }; + my $arg = $params[$i]; + my $field = $fields[$i]; + my $value; + if ($cast eq 'STRING') + { + my $local = "pg_etw_$arg"; + push @locals, "const char *$local = ($arg);"; + $value = "$local ? $local : \"\", pg_trace_etw_strlen($local)"; + } + elsif (length $cast) + { + $value = "$cast($arg)"; + } + else + { + $value = "($arg)"; + } + push @values, "$tlmacro($value, \"$field\")"; + } + + print "/* probe $name(" . join(', ', @types) . "); */\n"; + print "#define $macro($paramlist) \\\n"; + if ($has_string) + { + print "\tdo { \\\n"; + print "\t\tif (${macro}_ENABLED()) \\\n"; + print "\t\t{ \\\n"; + print "\t\t\t$_ \\\n" for @locals; + } + print "${ind}TraceLoggingWrite(pg_etw_provider, \"$event\", \\\n"; + print "${ind}\tTraceLoggingKeyword($kw), \\\n"; + print "${ind}\tTraceLoggingLevel($lvl)"; + print ", \\\n${ind}\t$_" for @values; + print ")"; + if ($has_string) + { + print "; \\\n"; + print "\t\t} \\\n"; + print "\t} while (0)"; + } + print "\n"; + + print "#define ${macro}_ENABLED() \\\n"; + print "\tTraceLoggingProviderEnabled(pg_etw_provider, $lvl, $kw)\n\n"; +} diff --git a/src/backend/utils/Makefile b/src/backend/utils/Makefile index 81b4a956bda..bbd85b8ed92 100644 --- a/src/backend/utils/Makefile +++ b/src/backend/utils/Makefile @@ -64,16 +64,20 @@ 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 $< +# Both branches also check probes_etw.txt against probes.d; see +# Gen_etw_probes.pl. ifeq ($(enable_dtrace), yes) probes.h: postprocess_dtrace.sed probes.h.tmp sed -f $^ >$@ rm probes.h.tmp -probes.h.tmp: probes.d +probes.h.tmp: probes.d Gen_etw_probes.pl probes_etw.txt + $(PERL) $(srcdir)/Gen_etw_probes.pl --check $(srcdir)/probes.d $(srcdir)/probes_etw.txt $(DTRACE) -C -h -s $< -o $@ else -probes.h: Gen_dummy_probes.pl probes.d - $(PERL) -n $^ >$@ +probes.h: Gen_dummy_probes.pl probes.d Gen_etw_probes.pl probes_etw.txt + $(PERL) $(srcdir)/Gen_etw_probes.pl --check $(srcdir)/probes.d $(srcdir)/probes_etw.txt + $(PERL) -n $(srcdir)/Gen_dummy_probes.pl $(srcdir)/probes.d >$@ endif # These generated headers must be symlinked into src/include/. diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index eddce1ce33f..48853fdeaee 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -52,6 +52,7 @@ #include "utils/guc.h" #include "utils/inval.h" #include "utils/memutils.h" +#include "utils/pg_trace_etw.h" #include "utils/pidfile.h" #include "utils/syscache.h" #include "utils/varlena.h" @@ -165,6 +166,9 @@ InitPostmasterChild(void) (errcode_for_socket_access(), errmsg_internal("could not set postmaster death monitoring pipe to FD_CLOEXEC mode: %m"))); #endif + + /* Register the ETW trace provider, if built with ETW support. */ + pg_trace_etw_register(); } /* @@ -188,6 +192,9 @@ InitStandaloneProcess(const char *argv0) InitProcessGlobals(); + /* Register the ETW trace provider, if built with ETW support. */ + pg_trace_etw_register(); + /* Initialize process-local latch support */ InitializeWaitEventSupport(); InitProcessLocalLatch(); diff --git a/src/backend/utils/meson.build b/src/backend/utils/meson.build index 7d0377944fe..1333b4fc535 100644 --- a/src/backend/utils/meson.build +++ b/src/backend/utils/meson.build @@ -17,3 +17,8 @@ subdir('mmgr') subdir('resowner') subdir('sort') subdir('time') + +# ETW provider definition and lifecycle, only built with ETW support +if etw.found() + backend_sources += files('pg_trace_etw.c') +endif diff --git a/src/backend/utils/pg_trace_etw.c b/src/backend/utils/pg_trace_etw.c new file mode 100644 index 00000000000..ea34e3a255c --- /dev/null +++ b/src/backend/utils/pg_trace_etw.c @@ -0,0 +1,102 @@ +/*------------------------------------------------------------------------- + * + * pg_trace_etw.c + * ETW (Event Tracing for Windows) provider definition and lifecycle. + * + * Defines the single "PostgreSQL.Server" TraceLogging provider that the + * generated TRACE_POSTGRESQL_* macros write to, and the per-process register + * and unregister entry points from pg_trace_etw.h. Compiled only with the + * "etw" option (Windows SDK builds; MinGW lacks TraceLoggingProvider.h). + * + * Registration is best-effort. If it fails, the provider handle stays + * disabled and every probe macro is a no-op, so a failure never affects + * server startup. + * + * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/pg_trace_etw.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "mb/pg_wchar.h" +#include "storage/ipc.h" +#include "utils/pg_trace_etw.h" + +/* + * Define the "PostgreSQL.Server" provider. The tuple is the provider GUID + * ea7811de-e757-5488-7ad6-2b057cced37d, which is the name hash recommended + * in TraceLoggingProvider.h (the same value .NET's EventSource.GetGuid + * computes for this name). See pg_trace_etw.h. + */ +TRACELOGGING_DEFINE_PROVIDER( + pg_etw_provider, + "PostgreSQL.Server", + (0xea7811de, 0xe757, 0x5488, 0x7a, 0xd6, 0x2b, 0x05, 0x7c, 0xce, 0xd3, 0x7d)); + +static bool etw_registered = false; + +static void pg_trace_etw_atexit(int code, Datum arg); + +/* + * Register the provider for this process, once. Safe to call more than once. + */ +void +pg_trace_etw_register(void) +{ + if (etw_registered) + return; + + if (TraceLoggingRegister(pg_etw_provider) == ERROR_SUCCESS) + { + etw_registered = true; + on_proc_exit(pg_trace_etw_atexit, 0); + } +} + +/* + * Unregister the provider for this process, if registered. + */ +void +pg_trace_etw_unregister(void) +{ + if (!etw_registered) + return; + + TraceLoggingUnregister(pg_etw_provider); + etw_registered = false; +} + +/* + * Return the number of bytes of a string probe argument to write, which is + * its length clipped to PG_ETW_MAX_STRING without splitting a multibyte + * character. A NULL argument is written as an empty string. + * + * The generated macros only call this when a session has enabled the event. + */ +uint16 +pg_trace_etw_strlen(const char *str) +{ + int len; + + if (str == NULL) + return 0; + + len = strlen(str); + if (len > PG_ETW_MAX_STRING) + len = pg_mbcliplen(str, len, PG_ETW_MAX_STRING); + + return (uint16) len; +} + +/* + * on_proc_exit callback wrapper. + */ +static void +pg_trace_etw_atexit(int code, Datum arg) +{ + pg_trace_etw_unregister(); +} diff --git a/src/backend/utils/probes_etw.txt b/src/backend/utils/probes_etw.txt new file mode 100644 index 00000000000..04be3099119 --- /dev/null +++ b/src/backend/utils/probes_etw.txt @@ -0,0 +1,103 @@ +# +# probes_etw.txt +# ETW keyword, level and field names for the static probes +# +# Copyright (c) 2026, PostgreSQL Global Development Group +# +# probes.d has the argument types of each probe but nothing ETW-specific. +# This file gives, for each probe, the keyword and level its event is +# written under and a name for each argument. The arguments are written +# as in a C prototype, with their types, and Gen_etw_probes.pl fails if +# the probe set or any argument's type differs from probes.d, so a change +# to probes.d needs a matching change here. The field names are taken +# from the argument descriptions in doc/src/sgml/monitoring.sgml. +# +# Each non-comment line has these whitespace-separated fields: +# +# [type field-name, ...] +# +# probe-name as in probes.d, double underscores and all +# keyword QUERY, TRANSACTION, LOCK, BUFFER, STORAGE, WAL, CHECKPOINT +# or SORT (the PG_KW_* bits in pg_trace_etw.h) +# level INFO or VERBOSE +# arguments one "type field-name" per argument, in probes.d order and +# comma-separated; omitted for probes with no arguments + +# Transaction +transaction__start TRANSACTION INFO LocalTransactionId lxid +transaction__commit TRANSACTION INFO LocalTransactionId lxid +transaction__abort TRANSACTION INFO LocalTransactionId lxid + +# Lightweight locks (high volume) +lwlock__acquire LOCK VERBOSE const char *tranche, LWLockMode mode +lwlock__release LOCK VERBOSE const char *tranche +lwlock__wait__start LOCK VERBOSE const char *tranche, LWLockMode mode +lwlock__wait__done LOCK VERBOSE const char *tranche, LWLockMode mode +lwlock__condacquire LOCK VERBOSE const char *tranche, LWLockMode mode +lwlock__condacquire__fail LOCK VERBOSE const char *tranche, LWLockMode mode +lwlock__acquire__or__wait LOCK VERBOSE const char *tranche, LWLockMode mode +lwlock__acquire__or__wait__fail LOCK VERBOSE const char *tranche, LWLockMode mode + +# Heavyweight locks (rare, high interest) +lock__wait__start LOCK INFO unsigned int locktag_field1, unsigned int locktag_field2, unsigned int locktag_field3, unsigned int locktag_field4, unsigned int locktag_type, LOCKMODE mode +lock__wait__done LOCK INFO unsigned int locktag_field1, unsigned int locktag_field2, unsigned int locktag_field3, unsigned int locktag_field4, unsigned int locktag_type, LOCKMODE mode + +# Query processing +query__parse__start QUERY INFO const char *query +query__parse__done QUERY INFO const char *query +query__rewrite__start QUERY INFO const char *query +query__rewrite__done QUERY INFO const char *query +query__plan__start QUERY INFO +query__plan__done QUERY INFO +query__execute__start QUERY INFO +query__execute__done QUERY INFO +query__start QUERY INFO const char *query +query__done QUERY INFO const char *query +statement__status QUERY INFO const char *status + +# Sort +sort__start SORT VERBOSE int sort_type, bool unique, int num_keys, int workmem_kb, bool random_access, int parallel_kind +sort__done SORT VERBOSE bool is_external, long space_used + +# Buffer manager: per-buffer I/O (high volume) +buffer__read__start BUFFER VERBOSE ForkNumber forknum, BlockNumber blocknum, Oid spcoid, Oid dboid, Oid reloid, int creator_procno +buffer__read__done BUFFER VERBOSE ForkNumber forknum, BlockNumber blocknum, Oid spcoid, Oid dboid, Oid reloid, int creator_procno, bool found +buffer__flush__start BUFFER VERBOSE ForkNumber forknum, BlockNumber blocknum, Oid spcoid, Oid dboid, Oid reloid +buffer__flush__done BUFFER VERBOSE ForkNumber forknum, BlockNumber blocknum, Oid spcoid, Oid dboid, Oid reloid +buffer__extend__start BUFFER VERBOSE ForkNumber forknum, Oid spcoid, Oid dboid, Oid reloid, int creator_procno, unsigned int extend_by +buffer__extend__done BUFFER VERBOSE ForkNumber forknum, Oid spcoid, Oid dboid, Oid reloid, int creator_procno, unsigned int extend_by, BlockNumber first_block + +# Buffer manager: checkpoint phases (per-checkpoint, low volume) +buffer__checkpoint__start BUFFER INFO int flags +buffer__checkpoint__sync__start BUFFER INFO +buffer__checkpoint__done BUFFER INFO +buffer__sync__start BUFFER INFO int num_buffers, int num_dirty +buffer__sync__written BUFFER VERBOSE int buffer_id +buffer__sync__done BUFFER INFO int num_buffers, int num_written, int num_expected + +# Deadlock +deadlock__found LOCK INFO + +# Checkpoint +checkpoint__start CHECKPOINT INFO int flags +checkpoint__done CHECKPOINT INFO int num_buffers, int num_total, int wal_added, int wal_removed, int wal_recycled +clog__checkpoint__start CHECKPOINT INFO bool is_normal +clog__checkpoint__done CHECKPOINT INFO bool is_normal +subtrans__checkpoint__start CHECKPOINT INFO bool is_normal +subtrans__checkpoint__done CHECKPOINT INFO bool is_normal +multixact__checkpoint__start CHECKPOINT INFO bool is_normal +multixact__checkpoint__done CHECKPOINT INFO bool is_normal +twophase__checkpoint__start CHECKPOINT INFO +twophase__checkpoint__done CHECKPOINT INFO + +# Storage manager (md): per-block disk I/O (high volume) +smgr__md__read__start STORAGE VERBOSE ForkNumber forknum, BlockNumber blocknum, Oid spcoid, Oid dboid, Oid reloid, int creator_procno +smgr__md__read__done STORAGE VERBOSE ForkNumber forknum, BlockNumber blocknum, Oid spcoid, Oid dboid, Oid reloid, int creator_procno, ssize_t bytes_read, size_t bytes_requested +smgr__md__write__start STORAGE VERBOSE ForkNumber forknum, BlockNumber blocknum, Oid spcoid, Oid dboid, Oid reloid, int creator_procno +smgr__md__write__done STORAGE VERBOSE ForkNumber forknum, BlockNumber blocknum, Oid spcoid, Oid dboid, Oid reloid, int creator_procno, ssize_t bytes_written, size_t bytes_requested + +# WAL +wal__insert WAL VERBOSE unsigned char rmid, unsigned char info +wal__switch WAL INFO +wal__buffer__write__dirty__start WAL VERBOSE +wal__buffer__write__dirty__done WAL VERBOSE diff --git a/src/include/utils/meson.build b/src/include/utils/meson.build index fd3a2352df5..6ed10d5c67c 100644 --- a/src/include/utils/meson.build +++ b/src/include/utils/meson.build @@ -37,6 +37,22 @@ guc_tables = custom_target('guc_tables', command: [perl, files('../../backend/utils/misc/gen_guc_tables.pl'), '@INPUT@', '@OUTPUT@']) generated_headers += guc_tables +# Builds without ETW still check probes_etw.txt against probes.d before +# generating probes.h, so that a probe change is caught on the platform +# where it is made. +probes_etw_check = [] +if not etw.found() + probes_etw_check = custom_target('probes_etw_check', + input: files( + '../../backend/utils/probes.d', + '../../backend/utils/probes_etw.txt'), + output: 'probes_etw_check.stamp', + capture: true, + command: [perl, files('../../backend/utils/Gen_etw_probes.pl'), + '--check', '@INPUT0@', '@INPUT1@'], + ) +endif + if dtrace.found() probes_tmp = custom_target('probes.h.tmp', input: files('../../backend/utils/probes.d'), @@ -48,6 +64,21 @@ if dtrace.found() output: 'probes.h', capture: true, command: [sed, '-f', files('../../backend/utils/postprocess_dtrace.sed'), '@INPUT@'], + depends: probes_etw_check, + install: true, + install_dir: dir_include_server / 'utils', + ) +elif etw.found() + # Generate TraceLogging macros from probes.d (argument types) and + # probes_etw.txt (keyword, level and field names). + probes = custom_target('probes.h', + input: files( + '../../backend/utils/probes.d', + '../../backend/utils/probes_etw.txt'), + output: 'probes.h', + capture: true, + command: [perl, files('../../backend/utils/Gen_etw_probes.pl'), + '@INPUT0@', '@INPUT1@'], install: true, install_dir: dir_include_server / 'utils', ) @@ -57,6 +88,7 @@ else output: 'probes.h', capture: true, command: [perl, '-n', files('../../backend/utils/Gen_dummy_probes.pl'), '@INPUT@'], + depends: probes_etw_check, install: true, install_dir: dir_include_server / 'utils', ) diff --git a/src/include/utils/pg_trace_etw.h b/src/include/utils/pg_trace_etw.h new file mode 100644 index 00000000000..9431818bb14 --- /dev/null +++ b/src/include/utils/pg_trace_etw.h @@ -0,0 +1,81 @@ +/*------------------------------------------------------------------------- + * + * pg_trace_etw.h + * ETW (Event Tracing for Windows) backend for the static probes. + * + * With the "etw" build option (Windows, with the Windows SDK's + * TraceLoggingProvider.h, which MinGW lacks), the TRACE_POSTGRESQL_* macros + * are generated by Gen_etw_probes.pl and expand to TraceLogging events on a + * single "PostgreSQL.Server" provider. This header declares the provider, + * the keyword bits that group the events, and the per-process register and + * unregister entry points. Without the option those entry points are no-op + * macros, so callers need no #ifdef. + * + * These macros evaluate their arguments only when a trace session has enabled + * the event, so probe arguments must not have side effects. + * + * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/utils/pg_trace_etw.h + * + *------------------------------------------------------------------------- + */ +#ifndef PG_TRACE_ETW_H +#define PG_TRACE_ETW_H + +#ifdef USE_ETW + +/* + * is already included via win32_port.h. Add the TraceLogging API + * and the WINEVENT_LEVEL_* constants the generated macros use. + */ +#include +#include + +/* + * The "PostgreSQL.Server" provider. The name and GUID are a stable identity + * that saved capture profiles depend on, so neither changes across releases. + * The GUID (ea7811de-e757-5488-7ad6-2b057cced37d) is the standard ETW hash of + * the name, so tools that derive a provider GUID from its name find it; the + * literal is in pg_trace_etw.c. + * + * This is TRACELOGGING_DECLARE_PROVIDER written out, so that the handle can + * be marked PGDLLIMPORT. + */ +extern PGDLLIMPORT const TraceLoggingHProvider pg_etw_provider; + +/* + * ETW drops an event larger than about 64kB outright, so string fields (query + * text, LWLock tranche names) are clipped to this many bytes. + */ +#define PG_ETW_MAX_STRING 32768 + +/* + * Keyword bits. Each probe is tagged with one of these so a consumer can + * enable one subsystem at a time. Bits 48-63 are reserved by Microsoft. + */ +#define PG_KW_QUERY 0x0000000000000001ULL +#define PG_KW_TRANSACTION 0x0000000000000002ULL +#define PG_KW_LOCK 0x0000000000000004ULL +#define PG_KW_BUFFER 0x0000000000000008ULL +#define PG_KW_STORAGE 0x0000000000000010ULL +#define PG_KW_WAL 0x0000000000000020ULL +#define PG_KW_CHECKPOINT 0x0000000000000040ULL +#define PG_KW_SORT 0x0000000000000080ULL + +/* Register or unregister the provider for the current process. */ +extern void pg_trace_etw_register(void); +extern void pg_trace_etw_unregister(void); + +/* Length of a string probe argument as written to the event. */ +extern uint16 pg_trace_etw_strlen(const char *str); + +#else /* !USE_ETW */ + +#define pg_trace_etw_register() ((void) 0) +#define pg_trace_etw_unregister() ((void) 0) + +#endif /* USE_ETW */ + +#endif /* PG_TRACE_ETW_H */ -- 2.51.0