feat(worms): asset-owned worm off switch; offline stack symboliser -- cheap-worm default to be reverted

Worms round (Codex; reviewed by me against its report and PERF-SAMPLED.md).

Part 1, a real off switch: UVoxelStrateDefinition::bEnableWorms (default true, so existing assets
are unchanged). It is deliberately not in FStrateGenerationParams, so the 83-field memo-key guard
is untouched. It resolves after composer candidates, season vectors and boundary blending, is
included in the generation fingerprint, and clamps composer overrides, so a roll cannot re-enable
it. With strength 0 the fused path skips the worm block, the op-stack builder omits the worm
source, and the interval/classifier paths do no worm work. Proof: disabled == hand-zeroed control
byte for byte (70D310A4, 74,672 triangles), capability 20,856 / 10,917, connected, zero worm rows
in the symbolised sampler. voxel.WormsForceOff is a dev-only, world-changing measurement
override, included in the fingerprint.

Part 0: the sampler writes a module map (733 modules, identity checked), and a post-exit
offline symboliser (VoxelStackOfflineSymbolizer, editor module) rebuilds the all-LOD / LOD0 /
LOD1+ exclusive / inclusive / source-line tables. A planted cost was found (5 named rows) and then
removed. DbgHelp still never runs in the measured process.

Part 2, "cheap worms": FAILED to deliver, and the default is wrong. No candidate was faster:
lattice-2/4 gave no gain, and value noise was within noise static and ~2.1% WORSE in moving worker
seconds. Yet this commit carries voxel.WormNoiseMode=1 (value noise) as the default, a field change
(29/64 test tiles) with no benefit, selected after value-noise variants were tried until one
passed capability (three were rejected at 20,821-20,828). The next round reverts the default to
exact Perlin (mode 0) and removes the no-gain world-changing experiment modes.

The finding that matters: disabling worms entirely (WormsForceOff A/B, two interleaved runs each)
saves only ~2.5% static and ~3.4% moving worker time. The sampler's "worm line
VoxelGenerator.cpp:3985 = 19.4% of LOD0" was a misattribution of inlined noise code to one PDB
line. Line-level attribution of inlined code must be confirmed by ablation before a round targets
it. That is why the worm block skip (26b3476) found nothing.

Runs: the full ledger is in PERF-SAMPLED.md. Failures: one moving wrapper hit the graceful-close
timeout and was rerun; the first offline-symboliser commandlet hit the DDC no-writable-nodes fatal
(crash folder UECC-...CE75ACD5, 08:01) and was retried with the writable Saved\DDC.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-09-14 08:19:50 +02:00
1 parent 11f0c1335b
commit c132243b30
15 files changed
+2406 -552

No files matched your search

+253 -492
View File
@@ -4,8 +4,6 @@
#include "VoxelStackSampler.h"
#include "Containers/Array.h"
#include "Containers/Map.h"
#include "Containers/Set.h"
#include "HAL/FileManager.h"
#include "HAL/PlatformProcess.h"
#include "HAL/PlatformStackWalk.h"
@@ -17,6 +15,7 @@
#include "Math/UnrealMathUtility.h"
#include "Containers/StringConv.h"
#include "Misc/FileHelper.h"
#include "Misc/Guid.h"
#include "Misc/Paths.h"
#include "Misc/ScopeLock.h"
#include "Serialization/Archive.h"
@@ -24,6 +23,11 @@
#if PLATFORM_WINDOWS
#include "Windows/WindowsPlatformStackWalk.h"
#include "Windows/AllowWindowsPlatformTypes.h"
THIRD_PARTY_INCLUDES_START
#include <Psapi.h>
THIRD_PARTY_INCLUDES_END
#include "Windows/HideWindowsPlatformTypes.h"
#endif
#include <atomic>
@@ -89,221 +93,219 @@ void WriteUtf8(FArchive& Archive, const FString& Text)
}
}
FString NormalizedForClassification(const FString& InValue)
#if PLATFORM_WINDOWS
struct FModuleMapRecord
{
FString Path;
uint64 LoadBase = 0;
uint32 ImageSize = 0;
uint64 FileSize = 0;
uint64 LastWriteFileTime = 0;
FString PdbGuid;
uint32 PdbAge = 0;
FString PdbPath;
};
FString ModuleMapField(const FString& InValue)
{
FString Result = InValue;
Result.ToLowerInline();
Result.ReplaceInline(TEXT("\\"), TEXT("/"));
Result.ReplaceInline(TEXT("\t"), TEXT(" "));
Result.ReplaceInline(TEXT("\r"), TEXT(" "));
Result.ReplaceInline(TEXT("\n"), TEXT(" "));
return Result;
}
struct FResolvedSymbol
bool IsModuleImageRangeValid(uint32 Offset, uint64 Size, uint32 ImageSize)
{
FString Module;
FString Function;
FString File;
int32 Line = 0;
bool bInline = false;
};
struct FResolvedProgramCounter
{
bool bAttempted = false;
TArray<FResolvedSymbol> Symbols;
};
struct FProfileRow
{
FString Key;
FString Function;
FString Module;
FString File;
FString Label;
int32 Line = 0;
uint64 Samples = 0;
};
struct FProfileAggregateSet
{
TMap<FString, FProfileRow> ExclusiveFunctions;
TMap<FString, FProfileRow> InclusiveFunctions;
TMap<FString, FProfileRow> ExclusiveLines;
uint64 SamplesWithStack = 0;
};
const FResolvedSymbol* FirstFunctionSymbol(const FResolvedProgramCounter& Entry)
{
for (const FResolvedSymbol& Symbol : Entry.Symbols)
{
if (!Symbol.Function.IsEmpty())
{
return &Symbol;
}
}
return nullptr;
return Offset <= ImageSize && Size <= static_cast<uint64>(ImageSize - Offset);
}
const FResolvedSymbol* FirstSourceSymbol(const FResolvedProgramCounter& Entry)
void ReadModuleCodeViewIdentity(
FModuleMapRecord& Record, const uint8* ModuleBase, uint32 ImageSize)
{
for (const FResolvedSymbol& Symbol : Entry.Symbols)
{
if (!Symbol.File.IsEmpty() && Symbol.Line > 0)
{
return &Symbol;
}
}
return nullptr;
}
FString SymbolLabel(const FResolvedSymbol& Symbol)
{
if (Symbol.Function.IsEmpty())
{
return TEXT("unknown");
}
const FString FunctionLower = NormalizedForClassification(Symbol.Function);
if (FunctionLower.Contains(TEXT("std::")))
{
return TEXT("std");
}
const FString ModuleLower = NormalizedForClassification(Symbol.Module);
const FString FileLower = NormalizedForClassification(Symbol.File);
const bool bFileIsEngine = FileLower.Contains(TEXT("/engine/"));
if (bFileIsEngine)
{
return TEXT("engine");
}
const bool bVoxelForge = FunctionLower.Contains(TEXT("voxelforge"))
|| FileLower.Contains(TEXT("/voxelforge/"))
|| ModuleLower.Contains(TEXT("voxelforge"));
if (bVoxelForge)
{
return TEXT("plugin");
}
const bool bModuleIsEngine = ModuleLower.StartsWith(TEXT("unrealeditor-"))
|| ModuleLower.StartsWith(TEXT("ue5-"));
return bModuleIsEngine ? TEXT("engine") : TEXT("other");
}
bool IsWaitLike(const FResolvedSymbol& Symbol)
{
const FString FunctionLower = NormalizedForClassification(Symbol.Function);
return FunctionLower.Contains(TEXT("wait"))
|| FunctionLower.Contains(TEXT("sleep"))
|| FunctionLower.Contains(TEXT("semaphore"))
|| FunctionLower.Contains(TEXT("fevent"));
}
FString FunctionKey(const FResolvedSymbol& Symbol)
{
return SymbolLabel(Symbol) + TEXT("\x1f") + Symbol.Function;
}
FString SourceKey(const FResolvedSymbol* Symbol)
{
if (Symbol == nullptr || Symbol->File.IsEmpty() || Symbol->Line <= 0)
{
return TEXT("unknown\x1f<unknown>");
}
return SymbolLabel(*Symbol) + TEXT("\x1f")
+ FString::Printf(TEXT("%s:%d"), *Symbol->File, Symbol->Line);
}
void AddFunctionRow(
TMap<FString, FProfileRow>& Rows,
const FResolvedSymbol* Symbol,
uint64 SampleCount)
{
if (Symbol == nullptr || Symbol->Function.IsEmpty())
if (ModuleBase == nullptr || ImageSize < sizeof(IMAGE_DOS_HEADER))
{
return;
}
const FString Key = FunctionKey(*Symbol);
FProfileRow& Row = Rows.FindOrAdd(Key);
if (Row.Key.IsEmpty())
const IMAGE_DOS_HEADER* DosHeader = reinterpret_cast<const IMAGE_DOS_HEADER*>(ModuleBase);
if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE || DosHeader->e_lfanew < 0)
{
Row.Key = Key;
Row.Function = Symbol->Function;
Row.Module = Symbol->Module;
Row.File = Symbol->File;
Row.Line = Symbol->Line;
Row.Label = SymbolLabel(*Symbol);
return;
}
const uint32 NtOffset = static_cast<uint32>(DosHeader->e_lfanew);
if (!IsModuleImageRangeValid(NtOffset, sizeof(IMAGE_NT_HEADERS), ImageSize))
{
return;
}
const IMAGE_NT_HEADERS* NtHeaders = reinterpret_cast<const IMAGE_NT_HEADERS*>(
ModuleBase + NtOffset);
if (NtHeaders->Signature != IMAGE_NT_SIGNATURE
|| NtHeaders->OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_DEBUG)
{
return;
}
const IMAGE_DATA_DIRECTORY& DebugDirectory =
NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG];
if (DebugDirectory.VirtualAddress == 0
|| DebugDirectory.Size < sizeof(IMAGE_DEBUG_DIRECTORY)
|| !IsModuleImageRangeValid(
DebugDirectory.VirtualAddress, DebugDirectory.Size, ImageSize))
{
return;
}
const IMAGE_DEBUG_DIRECTORY* Entries = reinterpret_cast<const IMAGE_DEBUG_DIRECTORY*>(
ModuleBase + DebugDirectory.VirtualAddress);
const uint32 EntryCount = DebugDirectory.Size / sizeof(IMAGE_DEBUG_DIRECTORY);
for (uint32 Index = 0; Index < EntryCount; ++Index)
{
const IMAGE_DEBUG_DIRECTORY& Entry = Entries[Index];
if (Entry.Type != IMAGE_DEBUG_TYPE_CODEVIEW || Entry.SizeOfData < 24
|| Entry.AddressOfRawData == 0
|| !IsModuleImageRangeValid(
Entry.AddressOfRawData, Entry.SizeOfData, ImageSize))
{
continue;
}
const uint8* CodeView = ModuleBase + Entry.AddressOfRawData;
if (CodeView[0] != 'R' || CodeView[1] != 'S'
|| CodeView[2] != 'D' || CodeView[3] != 'S')
{
continue;
}
FGuid Guid;
FMemory::Memcpy(&Guid, CodeView + 4, sizeof(Guid));
Record.PdbGuid = Guid.ToString();
FMemory::Memcpy(&Record.PdbAge, CodeView + 20, sizeof(Record.PdbAge));
const uint32 PdbPathBytes = Entry.SizeOfData - 24;
if (PdbPathBytes > 0)
{
const ANSICHAR* PdbPathAnsi = reinterpret_cast<const ANSICHAR*>(CodeView + 24);
int32 Length = 0;
while (Length < static_cast<int32>(PdbPathBytes) && PdbPathAnsi[Length] != '\0')
{
++Length;
}
if (Length > 0)
{
FUTF8ToTCHAR PdbPathUtf8(PdbPathAnsi, Length);
Record.PdbPath = FString(PdbPathUtf8.Get());
}
}
return;
}
Row.Samples += SampleCount;
}
void AddSourceRow(
TMap<FString, FProfileRow>& Rows,
const FResolvedSymbol* FunctionSymbol,
const FResolvedSymbol* SourceSymbol)
bool CollectProcessModuleMap(TArray<FModuleMapRecord>& OutRecords, FString& OutError)
{
const FString Key = SourceKey(SourceSymbol);
FProfileRow& Row = Rows.FindOrAdd(Key);
if (Row.Key.IsEmpty())
{
Row.Key = Key;
Row.Label = SourceSymbol != nullptr ? SymbolLabel(*SourceSymbol) : TEXT("unknown");
Row.File = SourceSymbol != nullptr ? SourceSymbol->File : TEXT("<unknown>");
Row.Line = SourceSymbol != nullptr ? SourceSymbol->Line : 0;
Row.Function = FunctionSymbol != nullptr ? FunctionSymbol->Function : TEXT("<unknown>");
Row.Module = FunctionSymbol != nullptr ? FunctionSymbol->Module : TEXT("<unknown>");
}
++Row.Samples;
}
OutRecords.Reset();
OutError.Reset();
TArray<FProfileRow> SortedRows(const TMap<FString, FProfileRow>& InRows)
{
TArray<FProfileRow> Rows;
Rows.Reserve(InRows.Num());
for (const TPair<FString, FProfileRow>& Pair : InRows)
// Keep PSAPI dynamic, like the engine stack-walk implementation. This records module state
// without touching DbgHelp or the process-global symbol engine.
void* PsapiHandle = FPlatformProcess::GetDllHandle(TEXT("PSAPI.DLL"));
if (PsapiHandle == nullptr)
{
Rows.Add(Pair.Value);
OutError = TEXT("PSAPI.DLL could not be loaded");
return false;
}
Rows.Sort([](const FProfileRow& A, const FProfileRow& B)
using FEnumProcessModules = BOOL (WINAPI*)(HANDLE, HMODULE*, DWORD, LPDWORD);
using FGetModuleFileNameExW = DWORD (WINAPI*)(HANDLE, HMODULE, LPWSTR, DWORD);
using FGetModuleInformation = BOOL (WINAPI*)(HANDLE, HMODULE, LPMODULEINFO, DWORD);
const FEnumProcessModules EnumModules = reinterpret_cast<FEnumProcessModules>(
FPlatformProcess::GetDllExport(PsapiHandle, TEXT("EnumProcessModules")));
const FGetModuleFileNameExW GetModuleFileNameEx = reinterpret_cast<FGetModuleFileNameExW>(
FPlatformProcess::GetDllExport(PsapiHandle, TEXT("GetModuleFileNameExW")));
const FGetModuleInformation GetModuleInformation = reinterpret_cast<FGetModuleInformation>(
FPlatformProcess::GetDllExport(PsapiHandle, TEXT("GetModuleInformation")));
if (EnumModules == nullptr || GetModuleFileNameEx == nullptr || GetModuleInformation == nullptr)
{
return A.Samples != B.Samples ? A.Samples > B.Samples : A.Key < B.Key;
OutError = TEXT("PSAPI exports needed for module enumeration are unavailable");
FPlatformProcess::FreeDllHandle(PsapiHandle);
return false;
}
DWORD BytesRequired = 0;
EnumModules(GetCurrentProcess(), nullptr, 0, &BytesRequired);
if (BytesRequired == 0)
{
OutError = TEXT("EnumProcessModules returned no module bytes");
FPlatformProcess::FreeDllHandle(PsapiHandle);
return false;
}
TArray<HMODULE> Modules;
Modules.SetNumUninitialized(
FMath::Max<int32>(1, static_cast<int32>(BytesRequired / sizeof(HMODULE) + 1)));
DWORD BytesWritten = 0;
if (!EnumModules(
GetCurrentProcess(), Modules.GetData(),
static_cast<DWORD>(Modules.Num() * sizeof(HMODULE)), &BytesWritten))
{
OutError = TEXT("EnumProcessModules failed");
FPlatformProcess::FreeDllHandle(PsapiHandle);
return false;
}
const int32 ModuleCount = FMath::Min<int32>(
Modules.Num(), static_cast<int32>(BytesWritten / sizeof(HMODULE)));
for (int32 Index = 0; Index < ModuleCount; ++Index)
{
MODULEINFO ModuleInfo{};
if (!GetModuleInformation(
GetCurrentProcess(), Modules[Index], &ModuleInfo, sizeof(ModuleInfo)))
{
continue;
}
WCHAR ImagePath[32768] = {};
const DWORD PathLength = GetModuleFileNameEx(
GetCurrentProcess(), Modules[Index], ImagePath, UE_ARRAY_COUNT(ImagePath));
if (PathLength == 0)
{
continue;
}
FModuleMapRecord Record;
Record.Path = FString(ImagePath, static_cast<int32>(PathLength));
Record.LoadBase = reinterpret_cast<uint64>(ModuleInfo.lpBaseOfDll);
Record.ImageSize = static_cast<uint32>(ModuleInfo.SizeOfImage);
WIN32_FILE_ATTRIBUTE_DATA FileData{};
if (GetFileAttributesExW(*Record.Path, GetFileExInfoStandard, &FileData))
{
ULARGE_INTEGER FileSize;
FileSize.HighPart = FileData.nFileSizeHigh;
FileSize.LowPart = FileData.nFileSizeLow;
Record.FileSize = FileSize.QuadPart;
ULARGE_INTEGER LastWrite;
LastWrite.HighPart = FileData.ftLastWriteTime.dwHighDateTime;
LastWrite.LowPart = FileData.ftLastWriteTime.dwLowDateTime;
Record.LastWriteFileTime = LastWrite.QuadPart;
}
ReadModuleCodeViewIdentity(
Record, reinterpret_cast<const uint8*>(ModuleInfo.lpBaseOfDll), Record.ImageSize);
OutRecords.Add(MoveTemp(Record));
}
OutRecords.Sort([](const FModuleMapRecord& A, const FModuleMapRecord& B)
{
return A.LoadBase < B.LoadBase;
});
return Rows;
FPlatformProcess::FreeDllHandle(PsapiHandle);
return OutRecords.Num() > 0;
}
#endif
void AppendRows(
FString& Out,
const TCHAR* Title,
const TMap<FString, FProfileRow>& InRows,
uint64 Denominator,
int32 MaxRows)
{
Out += TEXT("\n");
Out += Title;
Out += TEXT("\nrank\tsamples\tpercent\tlabel\tfunction\tmodule\tfile\tline\n");
const TArray<FProfileRow> Rows = SortedRows(InRows);
const int32 Count = FMath::Min(MaxRows, Rows.Num());
for (int32 Index = 0; Index < Count; ++Index)
{
const FProfileRow& Row = Rows[Index];
const double Percent = Denominator > 0
? static_cast<double>(Row.Samples) * 100.0 / static_cast<double>(Denominator)
: 0.0;
Out.Appendf(
TEXT("%d\t%llu\t%.4f\t%s\t%s\t%s\t%s\t%d\n"),
Index + 1,
static_cast<unsigned long long>(Row.Samples),
Percent,
*Row.Label,
*Row.Function,
*Row.Module,
*Row.File,
Row.Line);
}
if (Rows.Num() == 0)
{
Out += TEXT("(no rows)\n");
}
}
}
struct FVoxelStackSampler::FImpl final : public FRunnable
@@ -322,6 +324,7 @@ struct FVoxelStackSampler::FImpl final : public FRunnable
FString RunLabel;
FString OutputDirectory;
FString RawSamplesPath;
FString ModuleMapPath;
FString SummaryPath;
double StartSeconds = 0.0;
std::atomic<bool> bStop{false};
@@ -360,6 +363,9 @@ struct FVoxelStackSampler::FImpl final : public FRunnable
RawSamplesPath = FPaths::Combine(
OutputDirectory,
FString::Printf(TEXT("VoxelStackSamples_%s_%u.csv"), *SafeLabel, ProcessId));
ModuleMapPath = FPaths::Combine(
OutputDirectory,
FString::Printf(TEXT("VoxelStackModules_%s_%u.tsv"), *SafeLabel, ProcessId));
SummaryPath = FPaths::Combine(
OutputDirectory,
FString::Printf(TEXT("VoxelStackSummary_%s_%u.txt"), *SafeLabel, ProcessId));
@@ -622,10 +628,12 @@ struct FVoxelStackSampler::FImpl final : public FRunnable
Result.MaxActiveRegisteredThreads = MaxActiveRegisteredThreads;
Result.RunLabel = RunLabel;
Result.RawSamplesPath = RawSamplesPath;
Result.ModuleMapPath = ModuleMapPath;
Result.SummaryPath = SummaryPath;
Result.RunSeconds = FPlatformTime::Seconds() - StartSeconds;
WriteRawSamples(Result);
WriteModuleMap(Result);
// Do not call the DbgHelp-backed symbolizer from the measured process. DbgHelp is a
// process-global state machine and can fault during shutdown even after the sampler
// thread has joined. The raw PCs remain available for a separate offline pass.
@@ -672,6 +680,48 @@ struct FVoxelStackSampler::FImpl final : public FRunnable
Result.bOutputWritten = true;
}
void WriteModuleMap(FSummary& Result)
{
#if PLATFORM_WINDOWS
TArray<FModuleMapRecord> Records;
FString CollectionError;
const bool bCollected = CollectProcessModuleMap(Records, CollectionError);
FArchive* Archive = IFileManager::Get().CreateFileWriter(*ModuleMapPath);
if (Archive == nullptr)
{
return;
}
WriteUtf8(*Archive, TEXT("# VoxelForge module map v1\n"));
WriteUtf8(*Archive, TEXT("# DbgHelp is not initialized by this artifact writer.\n"));
WriteUtf8(*Archive, TEXT("module_path\tload_base_hex\timage_size\tfile_size\tfile_last_write_filetime\tpdb_guid\tpdb_age\tpdb_path\n"));
if (!bCollected)
{
WriteUtf8(*Archive, FString::Printf(TEXT("# collection_error\t%s\n"), *CollectionError));
}
for (const FModuleMapRecord& Record : Records)
{
const FString Line = FString::Printf(
TEXT("%s\t0x%016llX\t%u\t%llu\t%llu\t%s\t%u\t%s\n"),
*ModuleMapField(Record.Path),
static_cast<unsigned long long>(Record.LoadBase),
Record.ImageSize,
static_cast<unsigned long long>(Record.FileSize),
static_cast<unsigned long long>(Record.LastWriteFileTime),
*ModuleMapField(Record.PdbGuid),
Record.PdbAge,
*ModuleMapField(Record.PdbPath));
WriteUtf8(*Archive, Line);
}
delete Archive;
Result.ModuleCount = static_cast<uint64>(Records.Num());
Result.bModuleMapWritten = true;
#else
(void)Result;
#endif
}
void WriteRawOnlySummary(FSummary& Result)
{
uint64 SamplesWithStack = 0;
@@ -691,6 +741,10 @@ struct FVoxelStackSampler::FImpl final : public FRunnable
Summary += TEXT("raw_only: yes\n");
Summary += TEXT("symbolization: deferred\n");
Summary += TEXT("dbghelp_in_measured_process: no\n");
Summary.Appendf(TEXT("module_map_written: %s\n"),
Result.bModuleMapWritten ? TEXT("yes") : TEXT("no"));
Summary.Appendf(TEXT("module_count: %llu\n"),
static_cast<unsigned long long>(Result.ModuleCount));
Summary.Appendf(TEXT("interval_us: %u\n"), IntervalUs);
Summary.Appendf(TEXT("max_stack_depth: %u\n"), MaxStackDepth);
Summary.Appendf(TEXT("max_stored_samples: %u\n"), MaxStoredSamples);
@@ -716,6 +770,7 @@ struct FVoxelStackSampler::FImpl final : public FRunnable
static_cast<unsigned long long>(MaxActiveRegisteredThreads));
Summary.Appendf(TEXT("run_seconds: %.6f\n"), Result.RunSeconds);
Summary.Appendf(TEXT("raw_samples_path: %s\n"), *RawSamplesPath);
Summary.Appendf(TEXT("module_map_path: %s\n"), *ModuleMapPath);
Summary += TEXT("\nThe target process records bounded raw PCs only; resolve symbols after the process exits.\n");
if (FFileHelper::SaveStringToFile(
@@ -727,300 +782,7 @@ struct FVoxelStackSampler::FImpl final : public FRunnable
}
}
void ResolveProgramCounter(
uint64 ProgramCounter,
TMap<uint64, FResolvedProgramCounter>& SymbolCache,
FSummary& Result)
{
FResolvedProgramCounter& Entry = SymbolCache.FindOrAdd(ProgramCounter);
if (Entry.bAttempted)
{
return;
}
Entry.bAttempted = true;
#if PLATFORM_WINDOWS
FPlatformStackWalk::EnumerateSymbolInfosForProgramCounter(
ProgramCounter,
true,
[&Entry, &Result](FProgramCounterSymbolInfo& Info)
{
FResolvedSymbol Symbol;
Symbol.Module = FString(ANSI_TO_TCHAR(Info.ModuleName));
Symbol.Function = FString(ANSI_TO_TCHAR(Info.FunctionName));
Symbol.File = FString(ANSI_TO_TCHAR(Info.Filename));
Symbol.Line = Info.LineNumber;
Symbol.bInline = Symbol.Function.StartsWith(TEXT("[Inline Frame]"));
if (Symbol.bInline)
{
++Result.InlineSymbols;
}
Entry.Symbols.Add(MoveTemp(Symbol));
});
// DbgHelp can return source-only inline records for a PC. Ask the ordinary resolver as
// well when none of the enumerated records has a function name, so an unresolved frame
// does not hide an available outer function symbol.
if (Entry.Symbols.Num() == 0 || FirstFunctionSymbol(Entry) == nullptr)
{
FProgramCounterSymbolInfo Info;
FPlatformStackWalk::ProgramCounterToSymbolInfo(ProgramCounter, Info);
FResolvedSymbol Symbol;
Symbol.Module = FString(ANSI_TO_TCHAR(Info.ModuleName));
Symbol.Function = FString(ANSI_TO_TCHAR(Info.FunctionName));
Symbol.File = FString(ANSI_TO_TCHAR(Info.Filename));
Symbol.Line = Info.LineNumber;
Entry.Symbols.Add(MoveTemp(Symbol));
}
#else
(void)ProgramCounter;
(void)Result;
#endif
}
void SymbolizeAndWriteSummary(FSummary& Result)
{
TMap<uint64, FResolvedProgramCounter> SymbolCache;
FProfileAggregateSet AllAggregates;
FProfileAggregateSet Lod0Aggregates;
FProfileAggregateSet Lod1PlusAggregates;
TSet<FString> SeenFunctions;
uint64 ResolvedFrames = 0;
uint64 UnknownFrames = 0;
uint64 WaitLikeFrames = 0;
uint64 SamplesWithResolvedLeaf = 0;
uint64 SamplesWithWaitLikeFrame = 0;
uint64 SamplesWithStack = 0;
uint64 UntaggedSamples = 0;
auto AddUnknownExclusiveRow = [](FProfileAggregateSet& Aggregate)
{
const FString UnknownKey = TEXT("unknown\x1f<unknown>");
FProfileRow& UnknownRow = Aggregate.ExclusiveFunctions.FindOrAdd(UnknownKey);
if (UnknownRow.Key.IsEmpty())
{
UnknownRow.Key = UnknownKey;
UnknownRow.Function = TEXT("<unknown>");
UnknownRow.Module = TEXT("<unknown>");
UnknownRow.File = TEXT("<unknown>");
UnknownRow.Label = TEXT("unknown");
}
++UnknownRow.Samples;
};
for (uint32 SampleIndex = 0; SampleIndex < StoredSampleCount; ++SampleIndex)
{
const FStoredSample& Sample = Samples[SampleIndex];
if (Sample.Depth == 0)
{
continue;
}
++SamplesWithStack;
SeenFunctions.Reset();
TArray<FResolvedSymbol> SampleFunctions;
const FResolvedProgramCounter* LeafEntry = nullptr;
const FResolvedSymbol* LeafFunction = nullptr;
const FResolvedSymbol* LeafSource = nullptr;
uint64 LeafProgramCounter = 0;
bool bHasLeafProgramCounter = false;
bool bSampleHasResolvedLeaf = false;
bool bSampleHasWaitLike = false;
for (uint32 FrameIndex = 0; FrameIndex < Sample.Depth; ++FrameIndex)
{
const uint64 ProgramCounter = Sample.ProgramCounters[FrameIndex];
ResolveProgramCounter(ProgramCounter, SymbolCache, Result);
const FResolvedProgramCounter* Entry = SymbolCache.Find(ProgramCounter);
const FResolvedSymbol* FrameFunction = Entry != nullptr
? FirstFunctionSymbol(*Entry) : nullptr;
if (FrameFunction != nullptr)
{
++ResolvedFrames;
if (IsWaitLike(*FrameFunction))
{
++WaitLikeFrames;
bSampleHasWaitLike = true;
}
if (FrameIndex == 0)
{
LeafProgramCounter = ProgramCounter;
bHasLeafProgramCounter = true;
bSampleHasResolvedLeaf = true;
}
}
else
{
++UnknownFrames;
}
if (Entry != nullptr)
{
if (FrameIndex == 0)
{
LeafProgramCounter = ProgramCounter;
bHasLeafProgramCounter = true;
}
for (const FResolvedSymbol& Symbol : Entry->Symbols)
{
if (Symbol.Function.IsEmpty())
{
continue;
}
const FString Key = FunctionKey(Symbol);
if (!SeenFunctions.Contains(Key))
{
SeenFunctions.Add(Key);
SampleFunctions.Add(Symbol);
}
}
}
}
// SymbolCache is a TMap. FindOrAdd while resolving later frames may rehash it, so
// pointers captured during the loop are not stable. Re-find the leaf only after all
// PCs for this sample have been inserted.
if (bHasLeafProgramCounter)
{
LeafEntry = SymbolCache.Find(LeafProgramCounter);
if (LeafEntry != nullptr)
{
LeafFunction = FirstFunctionSymbol(*LeafEntry);
LeafSource = FirstSourceSymbol(*LeafEntry);
}
}
auto AddToAggregate = [&](FProfileAggregateSet& Aggregate)
{
++Aggregate.SamplesWithStack;
if (bSampleHasResolvedLeaf)
{
AddFunctionRow(Aggregate.ExclusiveFunctions, LeafFunction, 1);
}
else
{
AddUnknownExclusiveRow(Aggregate);
}
for (const FResolvedSymbol& Symbol : SampleFunctions)
{
AddFunctionRow(Aggregate.InclusiveFunctions, &Symbol, 1);
}
AddSourceRow(Aggregate.ExclusiveLines, LeafFunction, LeafSource);
};
AddToAggregate(AllAggregates);
if (Sample.LODLevel == 0)
{
AddToAggregate(Lod0Aggregates);
}
else if (Sample.LODLevel >= 1)
{
AddToAggregate(Lod1PlusAggregates);
}
else
{
++UntaggedSamples;
}
if (bSampleHasResolvedLeaf)
{
++SamplesWithResolvedLeaf;
}
if (bSampleHasWaitLike)
{
++SamplesWithWaitLikeFrame;
}
}
Result.UniqueProgramCounters = SymbolCache.Num();
Result.ResolvedFrames = ResolvedFrames;
Result.UnknownFrames = UnknownFrames;
Result.WaitLikeFrames = WaitLikeFrames;
Result.SamplesWithResolvedLeaf = SamplesWithResolvedLeaf;
Result.SamplesWithWaitLikeFrame = SamplesWithWaitLikeFrame;
FString Summary;
Summary += TEXT("VoxelForge in-process stack sampler\n");
Summary += TEXT("==================================\n");
Summary.Appendf(TEXT("run_label: %s\n"), *RunLabel);
Summary.Appendf(TEXT("supported: %s\n"), Result.bSupported ? TEXT("yes") : TEXT("no"));
Summary.Appendf(TEXT("stack_walking_initialized: %s\n"),
Result.bStackWalkingInitialized ? TEXT("yes") : TEXT("no"));
Summary.Appendf(TEXT("interval_us: %u\n"), IntervalUs);
Summary.Appendf(TEXT("max_stack_depth: %u\n"), MaxStackDepth);
Summary.Appendf(TEXT("max_stored_samples: %u\n"), MaxStoredSamples);
Summary.Appendf(TEXT("sampler_ticks: %llu\n"),
static_cast<unsigned long long>(SamplerTicks));
Summary.Appendf(TEXT("capture_attempts: %llu\n"),
static_cast<unsigned long long>(CaptureAttempts));
Summary.Appendf(TEXT("registered_thread_samples: %llu\n"),
static_cast<unsigned long long>(RegisteredThreadSamples));
Summary.Appendf(TEXT("registration_race_drops: %llu\n"),
static_cast<unsigned long long>(RegistrationRaceDrops));
Summary.Appendf(TEXT("capture_failures: %llu\n"),
static_cast<unsigned long long>(CaptureFailures));
Summary.Appendf(TEXT("retained_samples: %llu\n"),
static_cast<unsigned long long>(StoredSampleCount));
Summary.Appendf(TEXT("dropped_samples_after_bound: %llu\n"),
static_cast<unsigned long long>(DroppedSamples));
Summary.Appendf(TEXT("samples_with_stack: %llu\n"),
static_cast<unsigned long long>(SamplesWithStack));
Summary.Appendf(TEXT("lod0_samples_with_stack: %llu\n"),
static_cast<unsigned long long>(Lod0Aggregates.SamplesWithStack));
Summary.Appendf(TEXT("lod1plus_samples_with_stack: %llu\n"),
static_cast<unsigned long long>(Lod1PlusAggregates.SamplesWithStack));
Summary.Appendf(TEXT("untagged_samples: %llu\n"),
static_cast<unsigned long long>(UntaggedSamples));
Summary.Appendf(TEXT("total_frames: %llu\n"),
static_cast<unsigned long long>(TotalFrames));
Summary.Appendf(TEXT("resolved_frames: %llu\n"),
static_cast<unsigned long long>(ResolvedFrames));
Summary.Appendf(TEXT("unknown_frames: %llu\n"),
static_cast<unsigned long long>(UnknownFrames));
Summary.Appendf(TEXT("wait_like_frames: %llu\n"),
static_cast<unsigned long long>(WaitLikeFrames));
Summary.Appendf(TEXT("samples_with_resolved_leaf: %llu\n"),
static_cast<unsigned long long>(SamplesWithResolvedLeaf));
Summary.Appendf(TEXT("samples_with_wait_like_frame: %llu\n"),
static_cast<unsigned long long>(SamplesWithWaitLikeFrame));
Summary.Appendf(TEXT("unique_program_counters: %llu\n"),
static_cast<unsigned long long>(Result.UniqueProgramCounters));
Summary.Appendf(TEXT("inline_symbols: %llu\n"),
static_cast<unsigned long long>(Result.InlineSymbols));
Summary.Appendf(TEXT("max_active_registered_threads: %llu\n"),
static_cast<unsigned long long>(MaxActiveRegisteredThreads));
Summary.Appendf(TEXT("run_seconds: %.6f\n"), Result.RunSeconds);
Summary += TEXT("\nClosure uses stable registration snapshots. Top-table percentages use retained samples with a non-empty stack; the raw CSV includes zero-depth captures.\n");
Summary.Appendf(TEXT("resolved_frame_fraction: %.6f\n"), TotalFrames > 0
? static_cast<double>(ResolvedFrames) / static_cast<double>(TotalFrames) : 0.0);
Summary.Appendf(TEXT("unknown_frame_fraction: %.6f\n"), TotalFrames > 0
? static_cast<double>(UnknownFrames) / static_cast<double>(TotalFrames) : 0.0);
Summary.Appendf(TEXT("wait_like_frame_fraction: %.6f\n"), TotalFrames > 0
? static_cast<double>(WaitLikeFrames) / static_cast<double>(TotalFrames) : 0.0);
Summary.Appendf(TEXT("resolved_leaf_fraction: %.6f\n"), SamplesWithStack > 0
? static_cast<double>(SamplesWithResolvedLeaf) / static_cast<double>(SamplesWithStack) : 0.0);
Summary.Appendf(TEXT("wait_like_sample_fraction: %.6f\n"), SamplesWithStack > 0
? static_cast<double>(SamplesWithWaitLikeFrame) / static_cast<double>(SamplesWithStack) : 0.0);
Summary += TEXT("\nLabels: plugin = VoxelForge, engine = Unreal/Engine, std = std:: frames, other = other resolved modules, unknown = no function symbol. Inline symbol callbacks were requested through DbgHelp when available.\n");
AppendRows(Summary, TEXT("Top 30 exclusive functions (leaf)"), AllAggregates.ExclusiveFunctions, SamplesWithStack, 30);
AppendRows(Summary, TEXT("Top 30 inclusive functions (once per sample)"), AllAggregates.InclusiveFunctions, SamplesWithStack, 30);
AppendRows(Summary, TEXT("Top 30 exclusive source lines (leaf source)"), AllAggregates.ExclusiveLines, SamplesWithStack, 30);
Summary += TEXT("\nLOD0 tables (floor)\n");
AppendRows(Summary, TEXT("LOD0 top 30 exclusive functions (leaf)"), Lod0Aggregates.ExclusiveFunctions, Lod0Aggregates.SamplesWithStack, 30);
AppendRows(Summary, TEXT("LOD0 top 30 inclusive functions (once per sample)"), Lod0Aggregates.InclusiveFunctions, Lod0Aggregates.SamplesWithStack, 30);
AppendRows(Summary, TEXT("LOD0 top 30 exclusive source lines (leaf source)"), Lod0Aggregates.ExclusiveLines, Lod0Aggregates.SamplesWithStack, 30);
Summary += TEXT("\nLOD1+ tables (coarse/background)\n");
AppendRows(Summary, TEXT("LOD1+ top 30 exclusive functions (leaf)"), Lod1PlusAggregates.ExclusiveFunctions, Lod1PlusAggregates.SamplesWithStack, 30);
AppendRows(Summary, TEXT("LOD1+ top 30 inclusive functions (once per sample)"), Lod1PlusAggregates.InclusiveFunctions, Lod1PlusAggregates.SamplesWithStack, 30);
AppendRows(Summary, TEXT("LOD1+ top 30 exclusive source lines (leaf source)"), Lod1PlusAggregates.ExclusiveLines, Lod1PlusAggregates.SamplesWithStack, 30);
if (FFileHelper::SaveStringToFile(
Summary,
*SummaryPath,
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
Result.bOutputWritten = true;
}
}
};
FVoxelStackSampler& FVoxelStackSampler::Get()
@@ -1060,8 +822,9 @@ bool FVoxelStackSampler::Start(
Impl = NewImpl;
GVoxelStackSamplerEnabled.store(true, std::memory_order_release);
UE_LOG(LogTemp, Display,
TEXT("[VoxelStackSampler] started label=%s interval_us=%u raw=%s summary=%s"),
*RunLabel, NewImpl->IntervalUs, *NewImpl->RawSamplesPath, *NewImpl->SummaryPath);
TEXT("[VoxelStackSampler] started label=%s interval_us=%u raw=%s modules=%s summary=%s"),
*RunLabel, NewImpl->IntervalUs, *NewImpl->RawSamplesPath,
*NewImpl->ModuleMapPath, *NewImpl->SummaryPath);
return true;
#endif
}
@@ -1081,16 +844,14 @@ FVoxelStackSampler::FSummary FVoxelStackSampler::StopAndWrite()
Result = StoppedImpl->Finalize();
UE_LOG(LogTemp, Display,
TEXT("[VoxelStackSampler] stopped label=%s ticks=%llu registered_samples=%llu "
"retained=%llu dropped=%llu resolved_fraction=%.4f raw=%s summary=%s"),
"retained=%llu dropped=%llu raw=%s module_map=%s summary=%s"),
*Result.RunLabel,
static_cast<unsigned long long>(Result.SamplerTicks),
static_cast<unsigned long long>(Result.RegisteredThreadSamples),
static_cast<unsigned long long>(Result.RetainedSamples),
static_cast<unsigned long long>(Result.DroppedSamples),
Result.TotalFrames > 0
? static_cast<double>(Result.ResolvedFrames) / static_cast<double>(Result.TotalFrames)
: 0.0,
*Result.RawSamplesPath,
*Result.ModuleMapPath,
*Result.SummaryPath);
delete StoppedImpl;
Impl = nullptr;