Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@

# Build results
/build/
PresentMon/build/

# Claude Code
.claude/

# Accidental nul file from Windows commands
PresentMon/nul

# vcpkg dependencies
/vcpkg_installed/
Expand Down
10 changes: 10 additions & 0 deletions PresentData/GpuTrace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ GpuTrace::GpuTrace(PMTraceConsumer* pmConsumer)
{
}

GpuTrace::~GpuTrace()
{
for (auto& pair : mContexts) {
auto context = pair.second;
if (context.mIsHwQueue) {
delete context.mNode;
}
}
}

void GpuTrace::RegisterDevice(uint64_t hDevice, uint64_t pDxgAdapter)
{
// Sometimes there are duplicate start events
Expand Down
1 change: 1 addition & 0 deletions PresentData/GpuTrace.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class GpuTrace {

public:
explicit GpuTrace(PMTraceConsumer* pmConsumer);
~GpuTrace();

void RegisterDevice(uint64_t hDevice, uint64_t pDxgAdapter);
void UnregisterDevice(uint64_t hDevice);
Expand Down
63 changes: 61 additions & 2 deletions PresentData/PresentMonTraceConsumer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2365,8 +2365,8 @@ void PMTraceConsumer::CompletePresent(std::shared_ptr<PresentEvent> const& p)
static_cast<int32_t>(appFrameId - it->second.FrameId) >= 10) {
// Remove the app frame data if the app frame id is too old
it = mAppTimingDataByAppFrameId.erase(it); // Erase and move to the next element
} else if (appFrameId == 0 && it->second.ProcessId == processId &&
it->second.AssignedToPresent == false && it->second.AppPresentStartTime != 0 &&
} else if (appFrameId == 0 && it->second.ProcessId == processId &&
it->second.AssignedToPresent == false && it->second.AppPresentStartTime != 0 &&
presentStartTime >= it->second.AppPresentStartTime &&
presentStartTime - it->second.AppPresentStartTime >= mDeferralTimeLimit) {
// Remove app frame data if the app present start time is too old
Expand All @@ -2376,6 +2376,47 @@ void PMTraceConsumer::CompletePresent(std::shared_ptr<PresentEvent> const& p)
++it; // Move to the next element
}
}

// Prune out old PC Latency timing data to prevent memory leaks.
// This is critical because PCL data accumulates for every frame and the
// PCLStatsShutdown event (the only other cleanup mechanism) is app-controlled.
if (mTrackPcLatency) {
auto pclFrameId = present->PclFrameId;
for (auto it = mPclTimingDataByPclFrameId.begin(); it != mPclTimingDataByPclFrameId.end();) {
if (it->first.second == processId) {
// For entries from this process:
// 1. Remove if assigned and frame ID is too old (10+ frames behind)
// 2. Remove if timestamp is too old (stale data - assigned or not)
bool shouldRemove = false;

// Get the best available timestamp for this entry
uint64_t timingValue = mUsingOutOfBoundPresentStart
? it->second.PclOutOfBandPresentStartTime
: it->second.PclPresentStartTime;
if (timingValue == 0) {
timingValue = it->second.PclSimStartTime;
}

if (pclFrameId != 0 && it->second.AssignedToPresent &&
static_cast<int32_t>(pclFrameId - it->second.FrameId) >= 10) {
// Remove assigned PCL data if frame id is too old
shouldRemove = true;
} else if (timingValue != 0 && presentStartTime >= timingValue &&
presentStartTime - timingValue >= mDeferralTimeLimit) {
// Remove PCL data (assigned or not) if timestamp is too old
shouldRemove = true;
}

if (shouldRemove) {
it = mPclTimingDataByPclFrameId.erase(it);
} else {
++it;
}
} else {
++it;
}
}
}
}

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

// Clean up PC Latency tracking data for this process to prevent memory leaks.
// This is necessary because PCLStatsShutdown events are application-controlled
// and may not be sent if the app crashes or terminates abnormally.
if (mTrackPcLatency) {
std::erase_if(mPclTimingDataByPclFrameId, [&event](const auto& p) {
return p.first.second == event.ProcessId;
});
mLatestPingTimestampByProcessId.erase(event.ProcessId);
}

break;
}
default:
Expand All @@ -2734,6 +2785,14 @@ void PMTraceConsumer::HandleProcessEvent(EVENT_RECORD* pEventRecord)
mMetadata.GetEventData(pEventRecord, desc, _countof(desc));
event.ProcessId = desc[0].GetData<uint32_t>();
event.IsStartEvent = false;

// Clean up PC Latency tracking data for this process to prevent memory leaks.
if (mTrackPcLatency) {
std::erase_if(mPclTimingDataByPclFrameId, [&event](const auto& p) {
return p.first.second == event.ProcessId;
});
mLatestPingTimestampByProcessId.erase(event.ProcessId);
}
} else {
return;
}
Expand Down
38 changes: 38 additions & 0 deletions PresentData/PresentMonTraceSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,9 @@ ULONG PMTraceSession::Start(
sessionProps.Wnode.Flags = WNODE_FLAG_TRACED_GUID;
sessionProps.LogFileMode = EVENT_TRACE_REAL_TIME_MODE; // We have a realtime consumer, not writing to a log file
sessionProps.LoggerNameOffset = offsetof(TraceProperties, mSessionName); // Location of session name; will be written by StartTrace()
sessionProps.BufferSize = 64; // Buffer size in KB
sessionProps.MinimumBuffers = 256; // Minimum number of buffers allocated for the session's buffer pool
sessionProps.MaximumBuffers = 1024; // Maximum number of buffers (0 = no limit)

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

bool PMTraceSession::QueryEtwStatus(EtwStatus* status) const
{
if (mSessionHandle == 0) {
return false;
}

TraceProperties sessionProps = {};
sessionProps.Wnode.BufferSize = (ULONG) sizeof(TraceProperties);
sessionProps.LoggerNameOffset = offsetof(TraceProperties, mSessionName);

auto queryStatus = ControlTraceW(mSessionHandle, nullptr, &sessionProps, EVENT_TRACE_CONTROL_QUERY);
if (queryStatus != ERROR_SUCCESS) {
return false;
}

// Update cached status
mCachedEtwStatus.mEtwBuffersInUse = sessionProps.NumberOfBuffers - sessionProps.FreeBuffers;
mCachedEtwStatus.mEtwTotalBuffers = sessionProps.NumberOfBuffers;
mCachedEtwStatus.mEtwEventsLost = sessionProps.EventsLost;
mCachedEtwStatus.mEtwBuffersLost = sessionProps.LogBuffersLost + sessionProps.RealTimeBuffersLost;

if (sessionProps.NumberOfBuffers > 0) {
mCachedEtwStatus.mEtwBufferFillPct = 100.0 * mCachedEtwStatus.mEtwBuffersInUse / sessionProps.NumberOfBuffers;
} else {
mCachedEtwStatus.mEtwBufferFillPct = 0.0;
}

// Copy to output if provided
if (status != nullptr) {
*status = mCachedEtwStatus;
}

return true;
}

ULONG EnableProvidersListing(
TRACEHANDLE sessionHandle,
const GUID* pSessionGuid,
Expand Down
13 changes: 13 additions & 0 deletions PresentData/PresentMonTraceSession.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@

struct PMTraceConsumer;

struct EtwStatus {
double mEtwBufferFillPct;
ULONG mEtwBuffersInUse;
ULONG mEtwTotalBuffers;
ULONG mEtwEventsLost;
ULONG mEtwBuffersLost;
};

struct PMTraceSession {
enum TimestampType {
TIMESTAMP_TYPE_QPC = 1,
Expand Down Expand Up @@ -38,6 +46,9 @@ struct PMTraceSession {

bool mIsRealtimeSession = false;

// Cached ETW status for CSV output (updated periodically via QueryEtwStatus)
mutable EtwStatus mCachedEtwStatus = {};

ULONG Start(wchar_t const* etlPath, // If nullptr, start a live/realtime tracing session
wchar_t const* sessionName); // Required session name
void Stop();
Expand All @@ -48,6 +59,8 @@ struct PMTraceSession {
double TimestampToMilliSeconds(uint64_t timestamp) const;
void TimestampToLocalSystemTime(uint64_t timestamp, SYSTEMTIME* st, uint64_t* ns) const;
uint64_t MilliSecondsDeltaToTimestamp(double millisecondsDelta) const;

bool QueryEtwStatus(EtwStatus* status) const;
};

ULONG StopNamedTraceSession(wchar_t const* sessionName);
Expand Down
2 changes: 2 additions & 0 deletions PresentMon/CommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ bool ParseCommandLine(int argc, wchar_t** argv)
args->mWriteFrameId = false;
args->mWriteDisplayTime = false;
args->mDisableOfflineBackpressure = false;
args->mTrackEtwStatus = false;

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

// Provided argument wasn't recognized
else if (!(ParseArg(argv[i], L"?") || ParseArg(argv[i], L"h") || ParseArg(argv[i], L"help"))) {
Expand Down
22 changes: 20 additions & 2 deletions PresentMon/ConsoleApplication.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.6.33723.286
# Visual Studio Version 18
VisualStudioVersion = 18.1.11312.151 d18.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PresentData", "..\PresentData\PresentData.vcxproj", "{892028E5-32F6-45FC-8AB2-90FCBCAC4BF6}"
EndProject
Expand All @@ -10,6 +10,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PresentMon", "PresentMon.vc
{892028E5-32F6-45FC-8AB2-90FCBCAC4BF6} = {892028E5-32F6-45FC-8AB2-90FCBCAC4BF6}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonUtilities", "..\IntelPresentMon\CommonUtilities\CommonUtilities.vcxproj", "{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM
Expand Down Expand Up @@ -54,6 +56,22 @@ Global
{4EB9794B-1F12-48CE-ADC1-917E9810F29E}.Release|x64.Build.0 = Release|x64
{4EB9794B-1F12-48CE-ADC1-917E9810F29E}.Release|x86.ActiveCfg = Release|Win32
{4EB9794B-1F12-48CE-ADC1-917E9810F29E}.Release|x86.Build.0 = Release|Win32
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Debug|ARM.ActiveCfg = Debug|x64
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Debug|ARM.Build.0 = Debug|x64
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Debug|ARM64.ActiveCfg = Debug|x64
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Debug|ARM64.Build.0 = Debug|x64
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Debug|x64.ActiveCfg = Debug|x64
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Debug|x64.Build.0 = Debug|x64
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Debug|x86.ActiveCfg = Debug|Win32
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Debug|x86.Build.0 = Debug|Win32
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Release|ARM.ActiveCfg = Release|x64
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Release|ARM.Build.0 = Release|x64
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Release|ARM64.ActiveCfg = Release|x64
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Release|ARM64.Build.0 = Release|x64
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Release|x64.ActiveCfg = Release|x64
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Release|x64.Build.0 = Release|x64
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Release|x86.ActiveCfg = Release|Win32
{08A704D8-CA1C-45E9-8EDE-542A1A43B53E}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
30 changes: 30 additions & 0 deletions PresentMon/CsvOutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ void WriteCsvHeader<FrameMetrics1>(FILE* fp)
if (args.mWriteFrameId) {
fwprintf(fp, L",FrameId");
}
if (args.mTrackEtwStatus) {
fwprintf(fp, L",EtwBufferFillPct"
L",EtwBuffersInUse"
L",EtwTotalBuffers"
L",EtwEventsLost"
L",EtwBuffersLost");
}
fwprintf(fp, L"\n");

if (args.mCSVOutput == CSVOutput::Stdout) {
Expand Down Expand Up @@ -259,6 +266,14 @@ void WriteCsvRow<FrameMetrics1>(
if (args.mWriteFrameId) {
fwprintf(fp, L",%u", p.FrameId);
}
if (args.mTrackEtwStatus) {
fwprintf(fp, L",%.1lf,%lu,%lu,%lu,%lu",
pmSession.mCachedEtwStatus.mEtwBufferFillPct,
pmSession.mCachedEtwStatus.mEtwBuffersInUse,
pmSession.mCachedEtwStatus.mEtwTotalBuffers,
pmSession.mCachedEtwStatus.mEtwEventsLost,
pmSession.mCachedEtwStatus.mEtwBuffersLost);
}
fwprintf(fp, L"\n");

if (args.mCSVOutput == CSVOutput::Stdout) {
Expand Down Expand Up @@ -393,6 +408,13 @@ void WriteCsvHeader<FrameMetrics>(FILE* fp)
fwprintf(fp, L",PCLFrameId");
}
}
if (args.mTrackEtwStatus) {
fwprintf(fp, L",EtwBufferFillPct"
L",EtwBuffersInUse"
L",EtwTotalBuffers"
L",EtwEventsLost"
L",EtwBuffersLost");
}
fwprintf(fp, L"\n");

if (args.mCSVOutput == CSVOutput::Stdout) {
Expand Down Expand Up @@ -606,6 +628,14 @@ void WriteCsvRow<FrameMetrics>(
fwprintf(fp, L",%u", p.PclFrameId);
}
}
if (args.mTrackEtwStatus) {
fwprintf(fp, L",%.1lf,%lu,%lu,%lu,%lu",
pmSession.mCachedEtwStatus.mEtwBufferFillPct,
pmSession.mCachedEtwStatus.mEtwBuffersInUse,
pmSession.mCachedEtwStatus.mEtwTotalBuffers,
pmSession.mCachedEtwStatus.mEtwEventsLost,
pmSession.mCachedEtwStatus.mEtwBuffersLost);
}
fwprintf(fp, L"\n");

if (args.mCSVOutput == CSVOutput::Stdout) {
Expand Down
Loading