Skip to content

Commit be58ecc

Browse files
committed
simplify unknwon moodule state setup, and also improve UX with jitted modules
1 parent 9fb42bc commit be58ecc

2 files changed

Lines changed: 34 additions & 27 deletions

File tree

src/ProfileExplorerCore/Profile/Data/RawProfileData.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,10 +271,12 @@ public DotNetDebugInfoProvider GetOrAddManagedModuleDebugInfo(int processId, str
271271
}
272272

273273
public void LoadingCompleted() {
274-
// Free objects used while reading the profile.
274+
// Free objects used only during trace event processing.
275+
// imagesMap_ is kept alive (should be small) so AddImage
276+
// can be called after loading for synthetic images.
275277
stacksMap_ = null;
276278
contextsMap_ = null;
277-
imagesMap_ = null;
279+
// imagesMap_ = null;
278280
threadsMap_ = null;
279281
stackData_ = null;
280282
lastProcStacks_ = null;

src/ProfileExplorerCore/Profile/ETW/ETWProfileDataProvider.cs

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,14 @@ public sealed class ETWProfileDataProvider : IProfileDataProvider, IDisposable {
5757
// known module loaded in the process. This can be dynamically generated code
5858
// (e.g., LUA JIT, Java JIT), code from unloaded modules, or other unmapped regions.
5959
private const string UnknownModuleName = "[Unknown Module]";
60-
private sealed class UnknownModuleState(ProfileImage image, ProfileModuleBuilder moduleBuilder, ProfileData profileData) {
60+
private sealed class UnknownModuleState(ProfileImage image, ProfileData profileData) {
6161
private readonly object lock_ = new();
6262
private readonly ConcurrentDictionary<int, (IRTextFunction Function, FunctionDebugInfo DebugInfo)> threadFunctions_ = new();
6363
private readonly ProfileData profileData_ = profileData;
64+
private readonly ILoadedDocument document_ = LoadedDocument.CreateDummyDocument(UnknownModuleName);
6465

6566
public ProfileImage Image { get; } = image;
66-
public ProfileModuleBuilder ModuleBuilder { get; } = moduleBuilder;
67+
public ILoadedDocument Document => document_;
6768

6869
/// <summary>
6970
/// Gets or creates a synthetic function for samples with unmapped IPs
@@ -83,17 +84,17 @@ private sealed class UnknownModuleState(ProfileImage image, ProfileModuleBuilder
8384
return existing;
8485
}
8586

86-
var thread = profileData_.FindThread(threadId);
87+
ProfileThread thread = profileData_.FindThread(threadId);
8788
string startModule = ResolveStartModule(thread);
8889
string funcName = FormatFuncName(threadId, thread?.Name, startModule);
8990

9091
// RVA is set to a non-zero value (threadId) so FunctionDebugInfo.IsUnknown
9192
// returns false, and the frame's RVA matches so offset computes to 0.
9293
// Else, IsUnknown is dep on RVA==0 and Size==0, so this is somewhat a
9394
// hack workaround (though similar is used elsewhere in codebase).
94-
var debugInfo = new FunctionDebugInfo(funcName, threadId, 1);
95-
var func = ModuleBuilder.ModuleDocument.AddDummyFunction(funcName);
96-
var result = (func, debugInfo);
95+
FunctionDebugInfo debugInfo = new FunctionDebugInfo(funcName, threadId, 1);
96+
IRTextFunction func = document_.AddDummyFunction(funcName);
97+
(IRTextFunction func, FunctionDebugInfo debugInfo) result = (func, debugInfo);
9798
threadFunctions_[threadId] = result;
9899
return result;
99100
}
@@ -640,6 +641,7 @@ private async Task<ResolvedProfileStack> ProcessUnresolvedStackAsync(ProfileStac
640641
int managedFrames = 0;
641642
int unknownFrames = 0;
642643
int resolvedFrames = 0;
644+
bool prevFrameWasUnknownJit = false;
643645

644646
//? TODO: Stacks with >256 frames are truncated, inclusive time computation is not right then
645647
//? for ex it never gets to main. Easy example is a quicksort impl
@@ -671,18 +673,21 @@ private async Task<ResolvedProfileStack> ProcessUnresolvedStackAsync(ProfileStac
671673
if (frameImage == null) {
672674
unknownFrames++;
673675
// IP doesn't map to any known module (e.g., JITted). Attribute
674-
// to a synthetic module so the work is visible in the profile.
675-
var unknownState = GetUnknownModule(context.ProcessId);
676-
if (unknownState != null) {
677-
(IRTextFunction Function, FunctionDebugInfo DebugInfo) = unknownState.GetOrCreateThreadFunction(context.ThreadId);
678-
ResolvedProfileStackFrameKey unknownFrame = new ResolvedProfileStackFrameKey(DebugInfo, unknownState.Image, false);
679-
resolvedStack.AddFrame(Function, frameIp, DebugInfo.RVA,
680-
frameIndex, unknownFrame, stack, pointerSize);
681-
continue;
676+
// to a synthetic JIT function, but skip consecutive unknown frames
677+
// to avoid recursive self-call chains in the call tree.
678+
if (!prevFrameWasUnknownJit) {
679+
var unknownState = GetUnknownModule(context.ProcessId);
680+
if (unknownState != null) {
681+
(IRTextFunction Function, FunctionDebugInfo DebugInfo) = unknownState.GetOrCreateThreadFunction(context.ThreadId);
682+
ResolvedProfileStackFrameKey unknownFrame = new ResolvedProfileStackFrameKey(DebugInfo, unknownState.Image, false);
683+
resolvedStack.AddFrame(Function, frameIp, DebugInfo.RVA,
684+
frameIndex, unknownFrame, stack, pointerSize);
685+
prevFrameWasUnknownJit = true;
686+
continue;
687+
}
682688
}
683689

684-
resolvedStack.AddFrame(null, frameIp, 0, frameIndex, ResolvedProfileStackFrameKey.Unknown, stack,
685-
pointerSize);
690+
// Consecutive unknown frame or no UnknownModule state -- skip.
686691
continue;
687692
}
688693
}
@@ -731,6 +736,7 @@ private async Task<ResolvedProfileStack> ProcessUnresolvedStackAsync(ProfileStac
731736
resolvedStack.AddFrame(funcPair.Function, frameIp, frameRva, frameIndex,
732737
resolvedFrame, stack, pointerSize);
733738
resolvedFrames++;
739+
prevFrameWasUnknownJit = false; // Known frame breaks unknown frame run.
734740
}
735741

736742
var totalTime = sw.Elapsed;
@@ -746,8 +752,7 @@ private async Task<ResolvedProfileStack> ProcessUnresolvedStackAsync(ProfileStac
746752

747753
/// <summary>
748754
/// Pre-creates the synthetic [Unknown Module] for a process. Must be called
749-
/// single-threaded before parallel sample processing begins, since
750-
/// RawProfileData.AddImageToProcess is not thread-safe.
755+
/// before parallel sample processing begins.
751756
/// </summary>
752757
private void PreCreateUnknownModule(RawProfileData rawProfile, int processId) {
753758
ProfileImage image = new ProfileImage(
@@ -763,14 +768,8 @@ private void PreCreateUnknownModule(RawProfileData rawProfile, int processId) {
763768
rawProfile.AddImageToProcess(processId, image);
764769
profileData_.Modules[image.Id] = image;
765770

766-
ProfileModuleBuilder moduleBuilder = new ProfileModuleBuilder(report_, compilerInfoProvider_);
767-
moduleBuilder.ModuleDocument = LoadedDocument.CreateDummyDocument(UnknownModuleName);
768-
moduleBuilder.Summary = moduleBuilder.ModuleDocument.Summary;
769-
moduleBuilder.Initialized = true;
770-
771-
UnknownModuleState state = new UnknownModuleState(image, moduleBuilder, profileData_);
771+
UnknownModuleState state = new UnknownModuleState(image, profileData_);
772772
unknownModules_[processId] = state;
773-
imageModuleMap_.TryAdd(image.Id, moduleBuilder);
774773

775774
Trace.WriteLine($"Pre-created synthetic {UnknownModuleName} for process {processId}, ImageId={image.Id}");
776775
}
@@ -814,6 +813,12 @@ private ILoadedDocument FindSessionDocuments(string imageName, out List<ILoadedD
814813
}
815814
}
816815

816+
// Include synthetic [Unknown Module] documents so the UI can
817+
// navigate to JIT functions via FindLoadedDocument.
818+
foreach (var state in unknownModules_.Values) {
819+
otherDocuments.Add(state.Document);
820+
}
821+
817822
return exeDocument;
818823
}
819824

0 commit comments

Comments
 (0)