Files
dolphin/Source/Core/Common/PerformanceTrace.cpp
T
Yaya48 c230971f07 IOS LLE: checkpoint NAND conformance and diagnostic tooling
Preserve formatted NAND boot and Wii Remote pairing, add DMA/SRAM conformance tests and a Wii comparison probe. Keep P1 profiling tools and native update A/B evidence; reject the unhelpful targeted SRAM shortcut.
2026-09-23 13:30:17 +02:00

295 lines
8.5 KiB
C++

// Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Common/PerformanceTrace.h"
#include <atomic>
#include <cstdlib>
#include <cstring>
#ifdef _WIN32
#include <Windows.h>
#include <TraceLoggingProvider.h>
#pragma comment(lib, "advapi32.lib")
TRACELOGGING_DEFINE_PROVIDER(s_dolphin_perf, "Dolphin.Performance",
(0x54ee9231, 0x28be, 0x4ab0, 0x98, 0x1e, 0xb2, 0x6f, 0x97, 0x9c, 0x56,
0xa4));
#endif
namespace Common::PerformanceTrace
{
namespace
{
constexpr u32 SCHEMA = 1;
constexpr u32 SAMPLE_MASK = 1023;
struct ThreadState
{
bool sampling = false;
u32 rng = 0x9e3779b9;
u64 roots = 0;
u64 selected = 0;
u64 last_snapshot = 0;
ScopeStack stack;
std::array<u64, static_cast<size_t>(Counter::Count)> counters{};
};
thread_local ThreadState s_thread;
#ifdef _WIN32
struct Registration
{
Registration() : status(TraceLoggingRegister(s_dolphin_perf)) {}
~Registration()
{
if (status == ERROR_SUCCESS)
TraceLoggingUnregister(s_dolphin_perf);
}
ULONG status;
};
#endif
} // namespace
bool ScopeStack::Begin(Region region, u64 now)
{
if (m_depth == m_stack.size() || static_cast<size_t>(region) >= m_timings.size())
{
++m_errors;
return false;
}
m_stack[m_depth++] = {region, now, 0};
return true;
}
bool ScopeStack::End(u64 now)
{
if (m_depth == 0)
{
++m_errors;
return false;
}
const Entry entry = m_stack[--m_depth];
if (now < entry.start || now - entry.start < entry.children)
{
++m_errors;
return false;
}
const u64 elapsed = now - entry.start;
auto& timing = m_timings[static_cast<size_t>(entry.region)];
++timing.calls;
timing.inclusive += elapsed;
timing.exclusive += elapsed - entry.children;
if (m_depth != 0)
m_stack[m_depth - 1].children += elapsed;
return true;
}
void ScopeStack::Reset()
{
m_depth = 0;
m_errors = 0;
m_timings = {};
}
bool Requested()
{
#ifdef _WIN32
static const bool requested = [] {
const char* value = std::getenv("DOLPHIN_PERF_TRACE");
return value && std::strcmp(value, "1") == 0;
}();
return requested;
#else
return false;
#endif
}
bool Enabled()
{
#ifdef _WIN32
if (!Requested())
return false;
static Registration registration;
return registration.status == ERROR_SUCCESS && TraceLoggingProviderEnabled(s_dolphin_perf, 0, 0);
#else
return false;
#endif
}
u64 Now()
{
#ifdef _WIN32
LARGE_INTEGER value;
QueryPerformanceCounter(&value);
return static_cast<u64>(value.QuadPart);
#else
return 0;
#endif
}
u64 Frequency()
{
#ifdef _WIN32
static const u64 frequency = [] {
LARGE_INTEGER value;
QueryPerformanceFrequency(&value);
return static_cast<u64>(value.QuadPart);
}();
return frequency;
#else
return 0;
#endif
}
void Add(Counter counter, u64 value)
{
if (Requested() && static_cast<size_t>(counter) < s_thread.counters.size())
s_thread.counters[static_cast<size_t>(counter)] += value;
}
u64 NewGeneration()
{
static std::atomic<u64> generation{0};
return Requested() ? generation.fetch_add(1, std::memory_order_relaxed) + 1 : 0;
}
SampleRoot::SampleRoot(bool force_for_test)
{
m_denominator = force_for_test ? 1 : SAMPLE_MASK + 1;
if (!Enabled() || s_thread.sampling)
return;
++s_thread.roots;
auto& x = s_thread.rng;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
if (!force_for_test && (x & SAMPLE_MASK) != 0)
return;
++s_thread.selected;
s_thread.stack.Reset();
m_start = Now();
m_active = s_thread.stack.Begin(Region::Slice, m_start);
s_thread.sampling = m_active;
}
SampleRoot::~SampleRoot()
{
if (!m_active)
return;
const u64 end = Now();
s_thread.stack.End(end);
s_thread.sampling = false;
#ifdef _WIN32
// Sampled wall times, NOT exact total CPU times. Nested regions are exclusive here.
for (size_t i = 0; i < s_thread.stack.Timings().size(); ++i)
{
const auto& timing = s_thread.stack.Timings()[i];
if (timing.calls == 0)
continue;
TraceLoggingWrite(s_dolphin_perf, "ScopeSample", TraceLoggingUInt32(SCHEMA, "Schema"),
TraceLoggingUInt64(m_start, "BeginQPC"), TraceLoggingUInt64(end, "EndQPC"),
TraceLoggingUInt64(s_thread.selected, "Sample"),
TraceLoggingUInt32(m_denominator, "SamplingDenominator"),
TraceLoggingUInt32(static_cast<u32>(i), "Region"),
TraceLoggingUInt64(timing.calls, "Calls"),
TraceLoggingUInt64(timing.inclusive, "InclusiveQPC"),
TraceLoggingUInt64(timing.exclusive, "ExclusiveQPC"),
TraceLoggingUInt64(s_thread.stack.Errors(), "AccountingErrors"));
}
#endif
}
Scope::Scope(Region region)
{
if (s_thread.sampling)
m_active = s_thread.stack.Begin(region, Now());
}
Scope::~Scope()
{
if (m_active)
s_thread.stack.End(Now());
}
void Snapshot(u64 core_id, u64 instructions, u64 native_instructions, u64 slow_reads,
u64 slow_writes, u64 compiles, u64 fallbacks, u32 pc, bool waiting)
{
if (!Enabled())
return;
const u64 now = Now();
if (now - s_thread.last_snapshot < Frequency())
return;
s_thread.last_snapshot = now;
#ifdef _WIN32
TraceLoggingWrite(
s_dolphin_perf, "Snapshot", TraceLoggingUInt32(SCHEMA, "Schema"),
TraceLoggingUInt64(now, "QPC"), TraceLoggingUInt64(Frequency(), "QPCFrequency"),
TraceLoggingUInt64(core_id, "CoreId"), TraceLoggingUInt64(instructions, "Instructions"),
TraceLoggingUInt64(native_instructions, "NativeInstructions"),
TraceLoggingUInt64(slow_reads, "SlowReads"), TraceLoggingUInt64(slow_writes, "SlowWrites"),
TraceLoggingUInt64(compiles, "Compiles"), TraceLoggingUInt64(fallbacks, "Fallbacks"),
TraceLoggingUInt32(pc, "GuestPC"), TraceLoggingBool(waiting, "Waiting"),
TraceLoggingUInt64(s_thread.roots, "Roots"),
TraceLoggingUInt64(s_thread.selected, "SelectedRoots"));
for (size_t i = 0; i < s_thread.counters.size(); ++i)
{
TraceLoggingWrite(s_dolphin_perf, "Counter", TraceLoggingUInt32(SCHEMA, "Schema"),
TraceLoggingUInt64(now, "QPC"),
TraceLoggingUInt32(static_cast<u32>(i), "Counter"),
TraceLoggingUInt64(s_thread.counters[i], "Value"));
}
#endif
}
void CodeMap(const char* domain, const void* owner, const void* begin, const void* end,
u32 guest_pc, u64 identity, const char* kind)
{
if (!begin || reinterpret_cast<uintptr_t>(end) <= reinterpret_cast<uintptr_t>(begin) ||
!Enabled())
return;
#ifdef _WIN32
TraceLoggingWrite(s_dolphin_perf, "CodeMap", TraceLoggingUInt32(SCHEMA, "Schema"),
TraceLoggingUInt64(Now(), "QPC"), TraceLoggingString(domain, "Domain"),
TraceLoggingPointer(owner, "Owner"), TraceLoggingPointer(begin, "Begin"),
TraceLoggingPointer(end, "End"), TraceLoggingUInt32(guest_pc, "GuestPC"),
TraceLoggingUInt64(identity, "Identity"), TraceLoggingString(kind, "Kind"));
#endif
}
void CodeReset(const char* domain, const void* owner, const void* begin, const void* end)
{
CodeMap(domain, owner, begin, end, 0, 0, "invalidate");
}
void Frame(const char* kind, s64 guest_ticks, u32 guest_frequency)
{
if (!Enabled())
return;
#ifdef _WIN32
TraceLoggingWrite(s_dolphin_perf, "Frame", TraceLoggingUInt32(SCHEMA, "Schema"),
TraceLoggingUInt64(Now(), "QPC"),
TraceLoggingUInt64(Frequency(), "QPCFrequency"),
TraceLoggingString(kind, "Kind"), TraceLoggingInt64(guest_ticks, "GuestTicks"),
TraceLoggingUInt32(guest_frequency, "GuestFrequency"));
#endif
}
void Marker(const char* name)
{
if (!Enabled())
return;
#ifdef _WIN32
TraceLoggingWrite(s_dolphin_perf, "Marker", TraceLoggingUInt32(SCHEMA, "Schema"),
TraceLoggingUInt64(Now(), "QPC"),
TraceLoggingUInt64(Frequency(), "QPCFrequency"),
TraceLoggingString(name, "Name"));
#endif
}
void Queue(const void* owner, u64 frames, u64 bytes, u64 oldest_qpc, u64 peak)
{
if (!Enabled())
return;
#ifdef _WIN32
TraceLoggingWrite(s_dolphin_perf, "USBQueue", TraceLoggingUInt32(SCHEMA, "Schema"),
TraceLoggingUInt64(Now(), "QPC"), TraceLoggingPointer(owner, "Owner"),
TraceLoggingUInt64(frames, "Frames"), TraceLoggingUInt64(bytes, "Bytes"),
TraceLoggingUInt64(oldest_qpc, "OldestQPC"),
TraceLoggingUInt64(peak, "PeakFrames"));
#endif
}
} // namespace Common::PerformanceTrace