Capturing Windows Memory Events with ETW: VirtualAlloc, Hard Faults, and Microsoft-Windows-Kernel-Memory
This post documents the practical landscape of capturing memory events with ETW on Windows: which signals come from the classic kernel-provider keywords, which of those keywords silently require the NT Kernel Logger session, what the Microsoft-Windows-Kernel-Memory manifest provider covers instead, and which combination the ET Ducky agent's memory-growth profile settled on for diagnosing leaks with per-process attribution. It exists because most of these constraints are undocumented or documented only in TraceEvent source comments, and we hit each of them while building the profile.
Three routes to memory events
Windows exposes memory telemetry through three distinct mechanisms that are easy to conflate:
- Classic kernel keywords on a kernel trace session —
VirtualAlloc,Memory(page faults),MemoryHardFaults,ReferenceSet, and friends, enabled as flag bits when the session starts. - The
Microsoft-Windows-Kernel-Memorymanifest provider, enabled by GUID like any user-mode provider, which emits periodic memory-info snapshots and working-set data rather than per-operation events. - Heap snapshots (the xperf
HeapSnapshotmechanism), which have no kernel keyword at all and require a separate capture workflow.
Choosing between them is not a matter of preference. Several of the classic keywords only function on one specific session, and the manifest provider answers a different question than the keywords do.
The custom-session constraint
Since Windows 8, kernel providers can be enabled on a custom-named trace session rather than the single system-wide NT Kernel Logger. A custom session name is the right default for an agent: it cannot collide with another tool claiming the NT Kernel Logger, and multiple diagnostic sessions can coexist. The ET Ducky engine creates its kernel session as ETDucky_Kernel for exactly that reliability reason.
The catch: not every kernel keyword works on a custom session. In our engine the following are detected and skipped with a logged warning rather than enabled:
Profile(sampled CPU profiling) — requires the NT Kernel Logger session.ReferenceSet(working-set reference tracking, the deep memory-analysis keyword) — requires the NT Kernel Logger.Handle— depends on theOSkeyword, which itself requires the NT Kernel Logger.
Nothing fails loudly if you set these flags on a custom session; you simply do not get the events. If an agent claims to capture them on a custom-named session, verify it. A further set of memory-adjacent capabilities have no kernel keyword in the first place and can only be reached by enabling a manifest provider by GUID: memory compression state, Microsoft-Windows-Kernel-Power, Microsoft-Windows-Kernel-Session, job objects, and object-manager tracking beyond what Handle covers.
What VirtualAlloc gives you
The VirtualAlloc keyword is the core leak signal. It emits an event per virtual-memory operation — reserve, commit, and free — with the allocating process attributable from the event. That is page-granular data: you see committed memory climb and whether it is ever released, which is the shape of most native leaks. What you do not see is heap-allocation detail inside those pages; a process that commits one large region and leaks small heap blocks inside it shows a single commit. For that, heap snapshots are the tool, and they sit outside the kernel-keyword system entirely.
The tempting companion keyword is Memory — full page-fault tracing. In practice it is unusable for multi-minute captures: page faults on a busy machine arrive at overwhelming volume and add little leak signal over the VirtualAlloc stream, because a leaking process faults constantly whether or not you record each fault. The ET Ducky memory-growth profile deliberately leaves it off. MemoryHardFaults is the better pressure signal: hard faults (paging from disk) are orders of magnitude rarer than soft faults, so the keyword is cheap, and a rising hard-fault rate is a direct symptom of memory pressure doing real I/O damage.
Attribution requires more than the memory keywords
A VirtualAlloc event tells you a process ID. Turning that into a defensible per-process story over a multi-minute window requires three more keywords running alongside:
Process— process start/stop rundown, so PIDs resolve to names even for processes that exit mid-capture, and so PID reuse inside the window does not misattribute allocations.ImageLoad— module load events, which let a spike be attributed to a specific DLL loading rather than the host process's own code.ProcessCounters— per-process memory counters emitted at session rundown, giving a final working-set/commit snapshot to reconcile the delta stream against.
The complete keyword set for the ET Ducky memory-growth profile is therefore: Process + ImageLoad + ProcessCounters + VirtualAlloc + MemoryHardFaults, with page-fault tracing off and heap snapshots documented as out of scope.
Duration is part of the profile
A leak is a trend, not an instant. A sub-minute capture cannot distinguish a leak from ordinary allocation churn, because most processes allocate and free in bursts that dwarf a slow leak's slope. The profile runs three minutes by default, and because a three-minute kernel capture is heavier than a snapshot, it is gated behind an explicit operator opt-in rather than fired automatically by the diagnostic chat flow. The agent also pauses its baseline monitoring for the capture window and resumes afterward, so the capture does not measure the agent's own monitoring overhead.
Where Microsoft-Windows-Kernel-Memory fits
The manifest provider (Microsoft-Windows-Kernel-Memory) is the right tool when the question is "what is the memory state of the system over time" rather than "who allocated what." It emits periodic memory-information snapshots — system-wide and per-process working-set data — on an interval, instead of a per-operation event stream. That makes it cheap enough to leave running continuously, which is what resource-monitoring products do with it. It does not attribute individual allocations, so it cannot answer the leak question by itself; conversely, the VirtualAlloc keyword cannot cheaply answer the trend question at fleet scale. The two are complements, not alternatives. Note that as a manifest provider it is enabled by GUID on an ordinary (non-kernel) session — none of the custom-session keyword caveats above apply to it.
Summary table
| Signal | Mechanism | Custom session? | Use for |
|---|---|---|---|
| VirtualAlloc (reserve/commit/free) | Kernel keyword | Yes | Native leak attribution |
| Page faults (all) | Kernel keyword Memory | Yes, but volume-prohibitive | Short targeted captures only |
| Hard faults | Kernel keyword MemoryHardFaults | Yes | Memory-pressure signal, low volume |
| Working-set reference tracking | Kernel keyword ReferenceSet | No — NT Kernel Logger only | Deep working-set analysis |
| Periodic memory-info snapshots | Microsoft-Windows-Kernel-Memory by GUID | N/A (manifest provider) | Continuous trend monitoring |
| Heap allocations | Heap snapshot workflow | N/A (no kernel keyword) | Small-object leaks inside committed regions |
| Memory compression | GUID provider only | N/A | Compression-store behavior |
The general rule this table encodes: when a memory capability seems to be missing from your custom kernel session, check whether it is an NT-Kernel-Logger-only keyword, a GUID-only manifest provider, or a separate workflow before concluding the events do not exist. All three failure modes are silent.
How this is used in practice
In ET Ducky this profile backs the memory-leak investigation that an operator launches from an agent's properties panel: the agent runs the three-minute capture, the correlated event stream goes through root-cause analysis, and the result is rendered as an explanation plus a per-process allocate/free table. The same profile definitions are used by the AI investigation planner when a memory-shaped question arrives in a diagnostic session. The capture engine, profile definitions, and session-keyword handling described here are in the agent codebase; the desktop app's local monitoring uses the same engine.