Skip to content

Commit 6e30d52

Browse files
Merge pull request #560 from DevTechProfile/main
PresentMon Improvements: Memory Leak Fixes, ETW Monitoring & Build Setup
2 parents 60ec8e4 + 8e303cc commit 6e30d52

11 files changed

Lines changed: 221 additions & 4 deletions

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33

44
# Build results
55
/build/
6+
PresentMon/build/
7+
8+
# Claude Code
9+
.claude/
10+
11+
# Accidental nul file from Windows commands
12+
PresentMon/nul
613

714
# vcpkg dependencies
815
/vcpkg_installed/

PresentData/GpuTrace.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ GpuTrace::GpuTrace(PMTraceConsumer* pmConsumer)
7575
{
7676
}
7777

78+
GpuTrace::~GpuTrace()
79+
{
80+
for (auto& pair : mContexts) {
81+
auto context = pair.second;
82+
if (context.mIsHwQueue) {
83+
delete context.mNode;
84+
}
85+
}
86+
}
87+
7888
void GpuTrace::RegisterDevice(uint64_t hDevice, uint64_t pDxgAdapter)
7989
{
8090
// Sometimes there are duplicate start events

PresentData/GpuTrace.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class GpuTrace {
7575

7676
public:
7777
explicit GpuTrace(PMTraceConsumer* pmConsumer);
78+
~GpuTrace();
7879

7980
void RegisterDevice(uint64_t hDevice, uint64_t pDxgAdapter);
8081
void UnregisterDevice(uint64_t hDevice);

PresentData/PresentMonTraceConsumer.cpp

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2365,8 +2365,8 @@ void PMTraceConsumer::CompletePresent(std::shared_ptr<PresentEvent> const& p)
23652365
static_cast<int32_t>(appFrameId - it->second.FrameId) >= 10) {
23662366
// Remove the app frame data if the app frame id is too old
23672367
it = mAppTimingDataByAppFrameId.erase(it); // Erase and move to the next element
2368-
} else if (appFrameId == 0 && it->second.ProcessId == processId &&
2369-
it->second.AssignedToPresent == false && it->second.AppPresentStartTime != 0 &&
2368+
} else if (appFrameId == 0 && it->second.ProcessId == processId &&
2369+
it->second.AssignedToPresent == false && it->second.AppPresentStartTime != 0 &&
23702370
presentStartTime >= it->second.AppPresentStartTime &&
23712371
presentStartTime - it->second.AppPresentStartTime >= mDeferralTimeLimit) {
23722372
// Remove app frame data if the app present start time is too old
@@ -2376,6 +2376,47 @@ void PMTraceConsumer::CompletePresent(std::shared_ptr<PresentEvent> const& p)
23762376
++it; // Move to the next element
23772377
}
23782378
}
2379+
2380+
// Prune out old PC Latency timing data to prevent memory leaks.
2381+
// This is critical because PCL data accumulates for every frame and the
2382+
// PCLStatsShutdown event (the only other cleanup mechanism) is app-controlled.
2383+
if (mTrackPcLatency) {
2384+
auto pclFrameId = present->PclFrameId;
2385+
for (auto it = mPclTimingDataByPclFrameId.begin(); it != mPclTimingDataByPclFrameId.end();) {
2386+
if (it->first.second == processId) {
2387+
// For entries from this process:
2388+
// 1. Remove if assigned and frame ID is too old (10+ frames behind)
2389+
// 2. Remove if timestamp is too old (stale data - assigned or not)
2390+
bool shouldRemove = false;
2391+
2392+
// Get the best available timestamp for this entry
2393+
uint64_t timingValue = mUsingOutOfBoundPresentStart
2394+
? it->second.PclOutOfBandPresentStartTime
2395+
: it->second.PclPresentStartTime;
2396+
if (timingValue == 0) {
2397+
timingValue = it->second.PclSimStartTime;
2398+
}
2399+
2400+
if (pclFrameId != 0 && it->second.AssignedToPresent &&
2401+
static_cast<int32_t>(pclFrameId - it->second.FrameId) >= 10) {
2402+
// Remove assigned PCL data if frame id is too old
2403+
shouldRemove = true;
2404+
} else if (timingValue != 0 && presentStartTime >= timingValue &&
2405+
presentStartTime - timingValue >= mDeferralTimeLimit) {
2406+
// Remove PCL data (assigned or not) if timestamp is too old
2407+
shouldRemove = true;
2408+
}
2409+
2410+
if (shouldRemove) {
2411+
it = mPclTimingDataByPclFrameId.erase(it);
2412+
} else {
2413+
++it;
2414+
}
2415+
} else {
2416+
++it;
2417+
}
2418+
}
2419+
}
23792420
}
23802421

23812422
void PMTraceConsumer::UpdateReadyCount(bool useLock)
@@ -2708,6 +2749,16 @@ void PMTraceConsumer::HandleProcessEvent(EVENT_RECORD* pEventRecord)
27082749
event.ProcessId = desc[0].GetData<uint32_t>();
27092750
event.IsStartEvent = false;
27102751

2752+
// Clean up PC Latency tracking data for this process to prevent memory leaks.
2753+
// This is necessary because PCLStatsShutdown events are application-controlled
2754+
// and may not be sent if the app crashes or terminates abnormally.
2755+
if (mTrackPcLatency) {
2756+
std::erase_if(mPclTimingDataByPclFrameId, [&event](const auto& p) {
2757+
return p.first.second == event.ProcessId;
2758+
});
2759+
mLatestPingTimestampByProcessId.erase(event.ProcessId);
2760+
}
2761+
27112762
break;
27122763
}
27132764
default:
@@ -2734,6 +2785,14 @@ void PMTraceConsumer::HandleProcessEvent(EVENT_RECORD* pEventRecord)
27342785
mMetadata.GetEventData(pEventRecord, desc, _countof(desc));
27352786
event.ProcessId = desc[0].GetData<uint32_t>();
27362787
event.IsStartEvent = false;
2788+
2789+
// Clean up PC Latency tracking data for this process to prevent memory leaks.
2790+
if (mTrackPcLatency) {
2791+
std::erase_if(mPclTimingDataByPclFrameId, [&event](const auto& p) {
2792+
return p.first.second == event.ProcessId;
2793+
});
2794+
mLatestPingTimestampByProcessId.erase(event.ProcessId);
2795+
}
27372796
} else {
27382797
return;
27392798
}

PresentData/PresentMonTraceSession.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,9 @@ ULONG PMTraceSession::Start(
477477
sessionProps.Wnode.Flags = WNODE_FLAG_TRACED_GUID;
478478
sessionProps.LogFileMode = EVENT_TRACE_REAL_TIME_MODE; // We have a realtime consumer, not writing to a log file
479479
sessionProps.LoggerNameOffset = offsetof(TraceProperties, mSessionName); // Location of session name; will be written by StartTrace()
480+
sessionProps.BufferSize = 64; // Buffer size in KB
481+
sessionProps.MinimumBuffers = 256; // Minimum number of buffers allocated for the session's buffer pool
482+
sessionProps.MaximumBuffers = 1024; // Maximum number of buffers (0 = no limit)
480483

481484
auto status = StartTraceW(&mSessionHandle, sessionName, &sessionProps);
482485
if (status != ERROR_SUCCESS) {
@@ -656,6 +659,41 @@ void PMTraceSession::TimestampToLocalSystemTime(uint64_t timestamp, SYSTEMTIME*
656659
*ns = (timestamp % 10000000) * 100;
657660
}
658661

662+
bool PMTraceSession::QueryEtwStatus(EtwStatus* status) const
663+
{
664+
if (mSessionHandle == 0) {
665+
return false;
666+
}
667+
668+
TraceProperties sessionProps = {};
669+
sessionProps.Wnode.BufferSize = (ULONG) sizeof(TraceProperties);
670+
sessionProps.LoggerNameOffset = offsetof(TraceProperties, mSessionName);
671+
672+
auto queryStatus = ControlTraceW(mSessionHandle, nullptr, &sessionProps, EVENT_TRACE_CONTROL_QUERY);
673+
if (queryStatus != ERROR_SUCCESS) {
674+
return false;
675+
}
676+
677+
// Update cached status
678+
mCachedEtwStatus.mEtwBuffersInUse = sessionProps.NumberOfBuffers - sessionProps.FreeBuffers;
679+
mCachedEtwStatus.mEtwTotalBuffers = sessionProps.NumberOfBuffers;
680+
mCachedEtwStatus.mEtwEventsLost = sessionProps.EventsLost;
681+
mCachedEtwStatus.mEtwBuffersLost = sessionProps.LogBuffersLost + sessionProps.RealTimeBuffersLost;
682+
683+
if (sessionProps.NumberOfBuffers > 0) {
684+
mCachedEtwStatus.mEtwBufferFillPct = 100.0 * mCachedEtwStatus.mEtwBuffersInUse / sessionProps.NumberOfBuffers;
685+
} else {
686+
mCachedEtwStatus.mEtwBufferFillPct = 0.0;
687+
}
688+
689+
// Copy to output if provided
690+
if (status != nullptr) {
691+
*status = mCachedEtwStatus;
692+
}
693+
694+
return true;
695+
}
696+
659697
ULONG EnableProvidersListing(
660698
TRACEHANDLE sessionHandle,
661699
const GUID* pSessionGuid,

PresentData/PresentMonTraceSession.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@
66

77
struct PMTraceConsumer;
88

9+
struct EtwStatus {
10+
double mEtwBufferFillPct;
11+
ULONG mEtwBuffersInUse;
12+
ULONG mEtwTotalBuffers;
13+
ULONG mEtwEventsLost;
14+
ULONG mEtwBuffersLost;
15+
};
16+
917
struct PMTraceSession {
1018
enum TimestampType {
1119
TIMESTAMP_TYPE_QPC = 1,
@@ -38,6 +46,9 @@ struct PMTraceSession {
3846

3947
bool mIsRealtimeSession = false;
4048

49+
// Cached ETW status for CSV output (updated periodically via QueryEtwStatus)
50+
mutable EtwStatus mCachedEtwStatus = {};
51+
4152
ULONG Start(wchar_t const* etlPath, // If nullptr, start a live/realtime tracing session
4253
wchar_t const* sessionName); // Required session name
4354
void Stop();
@@ -48,6 +59,8 @@ struct PMTraceSession {
4859
double TimestampToMilliSeconds(uint64_t timestamp) const;
4960
void TimestampToLocalSystemTime(uint64_t timestamp, SYSTEMTIME* st, uint64_t* ns) const;
5061
uint64_t MilliSecondsDeltaToTimestamp(double millisecondsDelta) const;
62+
63+
bool QueryEtwStatus(EtwStatus* status) const;
5164
};
5265

5366
ULONG StopNamedTraceSession(wchar_t const* sessionName);

PresentMon/CommandLine.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ bool ParseCommandLine(int argc, wchar_t** argv)
411411
args->mWriteFrameId = false;
412412
args->mWriteDisplayTime = false;
413413
args->mDisableOfflineBackpressure = false;
414+
args->mTrackEtwStatus = false;
414415

415416
bool sessionNameSet = false;
416417
bool csvOutputStdout = false;
@@ -479,6 +480,7 @@ bool ParseCommandLine(int argc, wchar_t** argv)
479480
else if (ParseArg(argv[i], L"write_frame_id")) { args->mWriteFrameId = true; continue; }
480481
else if (ParseArg(argv[i], L"write_display_time")) { args->mWriteDisplayTime = true; continue; }
481482
else if (ParseArg(argv[i], L"disable_offline_backpressure")) { args->mDisableOfflineBackpressure = true; continue; }
483+
else if (ParseArg(argv[i], L"track_etw_status")) { args->mTrackEtwStatus = true; continue; }
482484

483485
// Provided argument wasn't recognized
484486
else if (!(ParseArg(argv[i], L"?") || ParseArg(argv[i], L"h") || ParseArg(argv[i], L"help"))) {

PresentMon/ConsoleApplication.sln

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio Version 17
4-
VisualStudioVersion = 17.6.33723.286
3+
# Visual Studio Version 18
4+
VisualStudioVersion = 18.1.11312.151 d18.0
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PresentData", "..\PresentData\PresentData.vcxproj", "{892028E5-32F6-45FC-8AB2-90FCBCAC4BF6}"
77
EndProject
@@ -10,6 +10,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PresentMon", "PresentMon.vc
1010
{892028E5-32F6-45FC-8AB2-90FCBCAC4BF6} = {892028E5-32F6-45FC-8AB2-90FCBCAC4BF6}
1111
EndProjectSection
1212
EndProject
13+
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonUtilities", "..\IntelPresentMon\CommonUtilities\CommonUtilities.vcxproj", "{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}"
14+
EndProject
1315
Global
1416
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1517
Debug|ARM = Debug|ARM
@@ -54,6 +56,22 @@ Global
5456
{4EB9794B-1F12-48CE-ADC1-917E9810F29E}.Release|x64.Build.0 = Release|x64
5557
{4EB9794B-1F12-48CE-ADC1-917E9810F29E}.Release|x86.ActiveCfg = Release|Win32
5658
{4EB9794B-1F12-48CE-ADC1-917E9810F29E}.Release|x86.Build.0 = Release|Win32
59+
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Debug|ARM.ActiveCfg = Debug|x64
60+
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Debug|ARM.Build.0 = Debug|x64
61+
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Debug|ARM64.ActiveCfg = Debug|x64
62+
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Debug|ARM64.Build.0 = Debug|x64
63+
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Debug|x64.ActiveCfg = Debug|x64
64+
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Debug|x64.Build.0 = Debug|x64
65+
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Debug|x86.ActiveCfg = Debug|Win32
66+
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Debug|x86.Build.0 = Debug|Win32
67+
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Release|ARM.ActiveCfg = Release|x64
68+
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Release|ARM.Build.0 = Release|x64
69+
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Release|ARM64.ActiveCfg = Release|x64
70+
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Release|ARM64.Build.0 = Release|x64
71+
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Release|x64.ActiveCfg = Release|x64
72+
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Release|x64.Build.0 = Release|x64
73+
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Release|x86.ActiveCfg = Release|Win32
74+
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Release|x86.Build.0 = Release|Win32
5775
EndGlobalSection
5876
GlobalSection(SolutionProperties) = preSolution
5977
HideSolutionNode = FALSE

PresentMon/CsvOutput.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,13 @@ void WriteCsvHeader<FrameMetrics1>(FILE* fp)
179179
if (args.mWriteFrameId) {
180180
fwprintf(fp, L",FrameId");
181181
}
182+
if (args.mTrackEtwStatus) {
183+
fwprintf(fp, L",EtwBufferFillPct"
184+
L",EtwBuffersInUse"
185+
L",EtwTotalBuffers"
186+
L",EtwEventsLost"
187+
L",EtwBuffersLost");
188+
}
182189
fwprintf(fp, L"\n");
183190

184191
if (args.mCSVOutput == CSVOutput::Stdout) {
@@ -259,6 +266,14 @@ void WriteCsvRow<FrameMetrics1>(
259266
if (args.mWriteFrameId) {
260267
fwprintf(fp, L",%u", p.FrameId);
261268
}
269+
if (args.mTrackEtwStatus) {
270+
fwprintf(fp, L",%.1lf,%lu,%lu,%lu,%lu",
271+
pmSession.mCachedEtwStatus.mEtwBufferFillPct,
272+
pmSession.mCachedEtwStatus.mEtwBuffersInUse,
273+
pmSession.mCachedEtwStatus.mEtwTotalBuffers,
274+
pmSession.mCachedEtwStatus.mEtwEventsLost,
275+
pmSession.mCachedEtwStatus.mEtwBuffersLost);
276+
}
262277
fwprintf(fp, L"\n");
263278

264279
if (args.mCSVOutput == CSVOutput::Stdout) {
@@ -393,6 +408,13 @@ void WriteCsvHeader<FrameMetrics>(FILE* fp)
393408
fwprintf(fp, L",PCLFrameId");
394409
}
395410
}
411+
if (args.mTrackEtwStatus) {
412+
fwprintf(fp, L",EtwBufferFillPct"
413+
L",EtwBuffersInUse"
414+
L",EtwTotalBuffers"
415+
L",EtwEventsLost"
416+
L",EtwBuffersLost");
417+
}
396418
fwprintf(fp, L"\n");
397419

398420
if (args.mCSVOutput == CSVOutput::Stdout) {
@@ -606,6 +628,14 @@ void WriteCsvRow<FrameMetrics>(
606628
fwprintf(fp, L",%u", p.PclFrameId);
607629
}
608630
}
631+
if (args.mTrackEtwStatus) {
632+
fwprintf(fp, L",%.1lf,%lu,%lu,%lu,%lu",
633+
pmSession.mCachedEtwStatus.mEtwBufferFillPct,
634+
pmSession.mCachedEtwStatus.mEtwBuffersInUse,
635+
pmSession.mCachedEtwStatus.mEtwTotalBuffers,
636+
pmSession.mCachedEtwStatus.mEtwEventsLost,
637+
pmSession.mCachedEtwStatus.mEtwBuffersLost);
638+
}
609639
fwprintf(fp, L"\n");
610640

611641
if (args.mCSVOutput == CSVOutput::Stdout) {

0 commit comments

Comments
 (0)