Greetings,

The attached patch set adds event tracing to windows for PG.  It doesn't
add any new probes. It just gets windows on par with our other
platforms, at least for the MSVC build.

The existing probes become TraceLogging events that wpr and WPA can
record and show in the same trace as the kernel's disk and scheduler events.

probes.h is generated from probes.d as it is today, plus a small
data file that carries a keyword and level for each event and a name for
each argument.  probes.d and the call sites don't change, and the build
fails on every platform, not just Windows, if the two files disagree.

There's one provider, "PostgreSQL.Server", with its GUID derived
from the name, registered once per process.

It's a meson option, on by default where the Windows SDK's
TraceLogging header is available, so MSVC builds get it and MinGW
keeps the dummy.  On pgbench -S an idle ETW build was within 0.5% of
the dummy build, which is within noise here.

0002 turns it on in the Windows - Visual Studio CI task, and 0003 is
the docs.

To see events, from a prompt whose user is in Performance Log Users:

  logman create trace pg -p "{ea7811de-e757-5488-7ad6-2b057cced37d}" 0x3
0x4 -bs 64 -o pg.etl -ets
  pgbench -S -T 5 postgres
  logman stop pg -ets
  tracerpt pg.etl -o pg.xml -of XML


-- 
Bryan Green
EDB: https://www.enterprisedb.com
From 481ab56cc33564c3b2dbcdadd49ebc620bd75fca Mon Sep 17 00:00:00 2001
From: Bryan Green <[email protected]>
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
<TraceLoggingProvider.h> 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 <windows.h>',
+      args: test_c_args)
+    cdata.set('USE_ETW', 1)
+    etw = declare_dependency()
+  elif etwopt.enabled()
+    error('ETW support requested but a suitable <TraceLoggingProvider.h> 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:
+#
+#   <probe-name>  <keyword>  <level>  [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
+
+/*
+ * <windows.h> is already included via win32_port.h.  Add the TraceLogging API
+ * and the WINEVENT_LEVEL_* constants the generated macros use.
+ */
+#include <winmeta.h>
+#include <TraceLoggingProvider.h>
+
+/*
+ * 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

From e7524737f32d73caa8942c64aafd3d6c30b4cb15 Mon Sep 17 00:00:00 2001
From: Bryan Green <[email protected]>
Date: Fri, 30 Oct 2026 16:23:15 -0500
Subject: [PATCH v1 2/3] ci: Enable the ETW build in the Windows - Visual
 Studio task

The CI configures with -Dauto_features=disabled, so the new etw option,
which defaults to auto, resolves to disabled and the backend is never
built.  Enable it explicitly on the Windows - Visual Studio task, the
same way -Ddtrace=enabled gives the DTrace path coverage.
---
 .github/workflows/pg-ci.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/pg-ci.yml b/.github/workflows/pg-ci.yml
index e8eb49b5f15..1fdbd1dbd1c 100644
--- a/.github/workflows/pg-ci.yml
+++ b/.github/workflows/pg-ci.yml
@@ -866,6 +866,7 @@ jobs:
       MESON_FEATURES: >-
         -Dauto_features=disabled
         -Dcpp_args=/std:c++20
+        -Detw=enabled
         -Dldap=enabled
         -Dplperl=enabled
         -Dplpython=enabled
-- 
2.51.0

From f78f8f4bc05851659418b0dd0a7e2095bbabcba2 Mon Sep 17 00:00:00 2001
From: Bryan Green <[email protected]>
Date: Fri, 30 Oct 2026 16:23:17 -0500
Subject: [PATCH v1 3/3] Document the ETW probe backend

Add a section describing the ETW backend for the static probes on
Windows: the -Detw build option, the "PostgreSQL.Server" provider and
its name-derived GUID, the keyword and level scheme, and how to capture
events with the standard Windows tools.  Also update "Defining New
Probes" for probes_etw.txt, and note that with ETW the probe arguments
are evaluated only when the event is enabled.
---
 doc/src/sgml/installation.sgml |  22 ++++
 doc/src/sgml/monitoring.sgml   | 234 +++++++++++++++++++++++++++++++++
 2 files changed, 256 insertions(+)

diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index e4159522e18..13308af238a 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -3229,6 +3229,28 @@ ninja install
       </listitem>
      </varlistentry>
 
+     <varlistentry id="configure-etw-meson">
+      <term><option>-Detw={ auto | enabled | disabled }</option></term>
+      <listitem>
+       <para>
+        <indexterm>
+         <primary>ETW</primary>
+        </indexterm>
+        On <productname>Windows</productname>, enabling this compiles
+        <productname>PostgreSQL</productname> with support for emitting its
+        probes as Event Tracing for Windows (ETW) events.  It requires
+        <filename>&lt;TraceLoggingProvider.h&gt;</filename> from a recent
+        <productname>Windows SDK</productname>, so it is available when
+        building with <productname>Visual Studio</productname> but not with
+        <productname>MinGW</productname>; with <literal>auto</literal>, builds
+        that lack the header keep the empty probe macros.  This option has no
+        effect on other platforms, and is mutually exclusive with
+        <option>-Ddtrace</option>.  See <xref linkend="dynamic-trace"/> for
+        more information.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="configure-injection-points-meson">
       <term><option>-Dinjection_points={ true | false }</option></term>
       <listitem>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 31ff4a9d99f..86a1218209d 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -8537,6 +8537,14 @@ FROM pg_stat_get_backend_idset() AS backendid;
    the macros in <filename>src/include/utils/probes.h</filename>.
   </para>
 
+  <para>
+   On <productname>Windows</productname>, where these probes cannot be traced
+   with DTrace, the same probe points can instead be emitted as
+   <ulink 
url="https://learn.microsoft.com/en-us/windows/win32/etw/about-event-tracing";>Event
+   Tracing for Windows</ulink> (ETW) events and consumed with the standard
+   Windows performance tools.  See <xref linkend="dynamic-trace-etw"/>.
+  </para>
+
   <sect2 id="compiling-for-trace">
    <title>Compiling for Dynamic Tracing</title>
 
@@ -8547,6 +8555,23 @@ FROM pg_stat_get_backend_idset() AS backendid;
    specify <option>--enable-dtrace</option> to configure.  See <xref
    linkend="configure-options-devel"/> for further information.
   </para>
+
+  <para>
+   On <productname>Windows</productname>, the <productname>Meson</productname>
+   option <option>-Detw</option> (see <xref linkend="configure-etw-meson"/>)
+   makes the probes available as ETW events instead (see
+   <xref linkend="dynamic-trace-etw"/>).  It defaults to
+   <literal>auto</literal>, so it is enabled whenever the build environment
+   supports it.  This requires the
+   <productname>Windows SDK</productname> header
+   <filename>&lt;TraceLoggingProvider.h&gt;</filename>, in a version recent
+   enough to provide the TraceLogging macros the probes use, so it is
+   available when building with <productname>Visual Studio</productname>; no
+   additional libraries are needed.  <productname>MinGW</productname> does not 
provide
+   this header, so ETW support is not available in
+   <productname>MinGW</productname> builds, which keep the empty probe
+   macros.  ETW and DTrace support are mutually exclusive.
+  </para>
   </sect2>
 
   <sect2 id="trace-points">
@@ -9128,6 +9153,17 @@ Total time (ns)                        2312105013
     </para>
    </step>
 
+   <step>
+    <para>
+     Add a matching line for each new probe to
+     <filename>src/backend/utils/probes_etw.txt</filename>, giving its ETW
+     keyword and level and the type and name of each argument (see
+     <xref linkend="dynamic-trace-etw"/>).  The build checks that the two
+     files agree on every platform, not only on
+     <productname>Windows</productname>.
+    </para>
+   </step>
+
    <step>
     <para>
      Include <filename>pg_trace.h</filename> if it is not already present in 
the
@@ -9173,6 +9209,16 @@ probe transaction__start(LocalTransactionId);
     </para>
    </step>
 
+   <step>
+    <para>
+     Add the probe to <filename>src/backend/utils/probes_etw.txt</filename>,
+     with its ETW keyword and level and the type and name of its argument:
+<programlisting>
+transaction__start                  TRANSACTION  INFO     LocalTransactionId 
lxid
+</programlisting>
+    </para>
+   </step>
+
    <step>
     <para>
      At compile time, <literal>transaction__start</literal> is converted to a 
macro
@@ -9239,8 +9285,196 @@ if (TRACE_POSTGRESQL_TRANSACTION_START_ENABLED())
       Each trace macro has a corresponding <literal>ENABLED</literal> macro.
      </para>
     </listitem>
+
+    <listitem>
+     <para>
+      With ETW (see <xref linkend="dynamic-trace-etw"/>), the arguments are
+      evaluated only when a trace session has enabled the event, so the
+      arguments to a trace macro must not have side effects.
+     </para>
+    </listitem>
+   </itemizedlist>
+
+  </para>
+
+  </sect2>
+
+  <sect2 id="dynamic-trace-etw">
+   <title>Event Tracing for Windows (ETW)</title>
+
+   <indexterm zone="dynamic-trace-etw">
+    <primary>ETW</primary>
+   </indexterm>
+   <indexterm zone="dynamic-trace-etw">
+    <primary>Event Tracing for Windows</primary>
+   </indexterm>
+
+  <para>
+   When <productname>PostgreSQL</productname> is built on
+   <productname>Windows</productname> with ETW support (see
+   <xref linkend="compiling-for-trace"/>), the same probe points listed in
+   <xref linkend="dtrace-probe-point-table"/> are emitted as
+   <ulink 
url="https://learn.microsoft.com/en-us/windows/win32/tracelogging/trace-logging-portal";>TraceLogging</ulink>
+   events through a single ETW provider.  They can be captured and analyzed
+   with the standard Windows tools, including
+   <application>Windows Performance Recorder</application>
+   (<command>wpr</command>),
+   <application>Windows Performance Analyzer</application>
+   (<application>WPA</application>),
+   <application>PerfView</application>, <command>logman</command>,
+   <command>tracerpt</command>, and <command>xperf</command>.  No manifest
+   registration or additional consumer binary is required: the events are
+   self-describing, so consumers display each field by name.
+  </para>
+
+  <para>
+   The provider is identified as follows.  The name and
+   <acronym>GUID</acronym> are a stable contract that capture profiles depend
+   on, and never change:
+   <itemizedlist>
+    <listitem>
+     <para>Provider name: <literal>PostgreSQL.Server</literal></para>
+    </listitem>
+    <listitem>
+     <para>Provider GUID:
+      <literal>ea7811de-e757-5488-7ad6-2b057cced37d</literal></para>
+    </listitem>
    </itemizedlist>
+   The <acronym>GUID</acronym> is derived from the provider name using the
+   standard ETW name hash, so tools that accept a provider name in place of a
+   <acronym>GUID</acronym> (for example
+   <literal>*PostgreSQL.Server</literal> in
+   <application>PerfView</application>) compute the same value.
+  </para>
+
+  <para>
+   Every event carries the same name as the corresponding DTrace probe (with
+   underscores in place of hyphens, for example
+   <literal>transaction_start</literal>), and its arguments appear as named
+   fields using the parameter descriptions from
+   <xref linkend="dtrace-probe-point-table"/> (for example
+   <literal>lxid</literal>, <literal>blocknum</literal>, or
+   <literal>tranche</literal>).
+  </para>
+
+  <para>
+   String fields, such as query text and lightweight lock tranche names, are
+   marked as UTF-8 in the event, but their contents are passed through in the
+   server encoding without conversion, so non-ASCII characters are displayed
+   correctly only when the server encoding is <literal>UTF8</literal>.  To
+   keep events within the size limit that ETW imposes, string fields longer
+   than 32kB are truncated.  ETW also discards any event larger than the trace
+   session's buffer size, so sessions that record query text should use
+   buffers of at least 64kB (for example <literal>-bs 64</literal> with
+   <command>logman</command>).
+  </para>
+
+  <note>
+   <para>
+    The query probes include the full text of each statement.  Any user who
+    can start a trace session on the server machine, which includes members
+    of the <literal>Performance Log Users</literal> group, can record these
+    events, so treat the trace files accordingly.
+   </para>
+  </note>
+
+  <para>
+   Each event is tagged with one <firstterm>keyword</firstterm> and a
+   <firstterm>level</firstterm>, so a consumer can enable just the subsystems
+   and verbosity it needs by filtering on the tuple
+   <literal>(provider, level, keyword)</literal>.  The keywords are a 64-bit
+   bitmask; the bits currently in use are shown in
+   <xref linkend="etw-keyword-table"/>.  Bits 48 to 63 are reserved by
+   <productname>Windows</productname>, and the other unused bits are reserved
+   for future keywords.
+  </para>
+
+  <table id="etw-keyword-table">
+   <title>ETW Probe Keywords</title>
+   <tgroup cols="3">
+    <colspec colname="col1" colwidth="2*"/>
+    <colspec colname="col2" colwidth="1*"/>
+    <colspec colname="col3" colwidth="4*"/>
+    <thead>
+     <row>
+      <entry>Keyword</entry>
+      <entry>Bit</entry>
+      <entry>Probes</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>PG_KW_QUERY</literal></entry>
+      <entry><literal>0x01</literal></entry>
+      <entry>Query parse, rewrite, plan, execute, and statement status</entry>
+     </row>
+     <row>
+      <entry><literal>PG_KW_TRANSACTION</literal></entry>
+      <entry><literal>0x02</literal></entry>
+      <entry>Transaction start, commit, and abort</entry>
+     </row>
+     <row>
+      <entry><literal>PG_KW_LOCK</literal></entry>
+      <entry><literal>0x04</literal></entry>
+      <entry>Lightweight locks, heavyweight locks, and deadlock 
detection</entry>
+     </row>
+     <row>
+      <entry><literal>PG_KW_BUFFER</literal></entry>
+      <entry><literal>0x08</literal></entry>
+      <entry>Buffer reads, flushes, extends, and checkpoint buffer 
writing</entry>
+     </row>
+     <row>
+      <entry><literal>PG_KW_STORAGE</literal></entry>
+      <entry><literal>0x10</literal></entry>
+      <entry>Storage manager (md) block reads and writes</entry>
+     </row>
+     <row>
+      <entry><literal>PG_KW_WAL</literal></entry>
+      <entry><literal>0x20</literal></entry>
+      <entry>WAL insert, segment switch, and buffer write</entry>
+     </row>
+     <row>
+      <entry><literal>PG_KW_CHECKPOINT</literal></entry>
+      <entry><literal>0x40</literal></entry>
+      <entry>Checkpoint progress, including the CLOG, SUBTRANS, MultiXact, and
+       two-phase phases</entry>
+     </row>
+     <row>
+      <entry><literal>PG_KW_SORT</literal></entry>
+      <entry><literal>0x80</literal></entry>
+      <entry>Sort start and completion</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
 
+  <para>
+   Levels follow the <productname>Windows</productname> convention, where a
+   higher number is more verbose.  Low-volume, high-interest probes
+   (transaction, query, checkpoint, heavyweight lock wait, deadlock, and WAL
+   segment switch) are emitted at <literal>WINEVENT_LEVEL_INFO</literal> (4),
+   so they are captured by a default session.  High-volume probes (lightweight 
locks, buffer and
+   storage I/O, sort, and WAL record insertion) are emitted at
+   <literal>WINEVENT_LEVEL_VERBOSE</literal> (5), so they are excluded unless a
+   session explicitly asks for verbose events.
+  </para>
+
+  <para>
+   The following example uses <command>logman</command> to capture all
+   <productname>PostgreSQL</productname> events (every keyword, up to the
+   verbose level) into an event trace log, then decodes it with
+   <command>tracerpt</command>:
+<programlisting>
+logman create trace pg_etw -p "{ea7811de-e757-5488-7ad6-2b057cced37d}" 
0xffffffffffffffff 0x5 -bs 64 -o pg_etw.etl -ets
+<lineannotation>... run the workload ...</lineannotation>
+logman stop pg_etw -ets
+tracerpt pg_etw.etl -o pg_etw.xml -of XML
+</programlisting>
+   To capture a single subsystem, supply just its keyword bit in place of
+   <literal>0xffffffffffffffff</literal>; for example, <literal>0x2</literal>
+   captures only transaction events.  Because every server process registers
+   the provider at startup, events from all of them appear in the same
+   session.
   </para>
 
   </sect2>
-- 
2.51.0

Reply via email to