Skip to content
92 changes: 92 additions & 0 deletions src/PerfView/PerfViewData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,11 @@ protected virtual Action<Action> OpenImpl(Window parentWindow, StatusBar worker)
};
}

/// <summary>
/// Called when a stack window is launched but after the processes have been selected.
/// </summary>
protected internal virtual void OnStackWindowLaunch(Window parentWindow, List<int> processIDs, string sourceName) { }

protected internal virtual void ConfigureStackWindow(string stackSourceName, StackWindow stackWindow) { }
/// <summary>
/// Allows you to do a first action after everything is done.
Expand Down Expand Up @@ -4646,6 +4651,9 @@ public override void Open(Window parentWindow, StatusBar worker, Action doAfter
SetProcessFilter(incPat);
}

// Call the launch hook to perform any initialization needed after process selection
DataFile.OnStackWindowLaunch(parentWindow, processIDs, SourceName);

Viewer.StatusBar.StartWork("Looking up high importance PDBs that are locally cached", delegate
{
// TODO This is probably a hack that it is here.
Expand Down Expand Up @@ -4742,6 +4750,7 @@ protected internal virtual void FirstAction(StackWindow stackWindow)
{
DataFile.FirstAction(stackWindow);
}

public override ImageSource Icon { get { return GuiApp.MainWindow.Resources["StackSourceBitmapImage"] as ImageSource; } }

// If set, we don't show the process selection dialog.
Expand Down Expand Up @@ -6860,6 +6869,83 @@ string GetAllocationType(CallStackIndex csi)
}

#region private

/// <summary>
/// Checks if RuntimeStart events exist for the selected processes and warns if missing.
/// This indicates whether the processes were started after tracing began, which is required
/// for proper type information in unsampled allocation traces.
/// </summary>
private void WarnAboutMissingTypeInfoForProcesses(Window parentWindow, List<int> processIDs, string sourceName)
{
bool showWarning = true;

// Only check for allocation-related views
if (!sourceName.Equals("GC Heap Alloc Ignore Free") &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hardcoding is pretty rough. There are a lot of individual strings of things like "GC Heap Net Mem" in the codebase. It would be nice if these were some kind of SourceKind enum that translated to this string instead of hardcoding everywhere, but that's a change or topic for another day.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a fair point. I'll think on this one.

!sourceName.Equals("GC Heap Net Mem") &&
!sourceName.Equals("Gen 2 Object Deaths"))
{
return;
}

TraceLog traceLog = TryGetTraceLog();
if (traceLog == null)
{
return;
}

// Check if we have RuntimeStart events for the selected processes
HashSet<int> selectedProcessesWithRuntimeStartEvents = new HashSet<int>();

if (processIDs != null && processIDs.Count > 0)
{
using TraceLogEventSource source = traceLog.Events.GetSource();

source.Clr.RuntimeStart += delegate (RuntimeInformationTraceData data)
{
if (processIDs.Contains(data.ProcessID))
{
selectedProcessesWithRuntimeStartEvents.Add(data.ProcessID);
}
};

source.Process();

showWarning = processIDs.Count != selectedProcessesWithRuntimeStartEvents.Count;
}
else
{
// If no specific processes selected, check if any RuntimeStart exists
foreach (var stats in traceLog.Stats)
{
if (stats.ProviderGuid == ClrTraceEventParser.ProviderGuid && stats.EventName == "Runtime/Start")
{
showWarning = false;
break;
}
}
}

// Show warning if RuntimeStart not found for selected processes
if (showWarning)
{
var warning = $"""
WARNING: The '{sourceName}' view may be missing type information.

This can happen when the ETW circular buffer wraps and loses early events including type definitions. Without these type definitions, many types will appear as "UNKNOWN" in the allocation view.

To fix this issue, perform one of the following:
• Re-capture the trace with a shorter duration
• Re-capture the trace with a larger circular buffer size (e.g., /BufferSize:1024)
""";

XamlMessageBox.Show(
parentWindow,
warning,
"Trace May Be Missing Type Information",
MessageBoxButton.OK);
}
}

private static StackSource GetProcessFileRegistryStackSource(TraceLogEventSource eventSource, TextWriter log)
{
TraceLog traceLog = eventSource.TraceLog;
Expand Down Expand Up @@ -7602,6 +7688,12 @@ You must close and reopen this window to get the allocation types.
}
}

protected internal override void OnStackWindowLaunch(Window parentWindow, List<int> processIDs, string sourceName)
{
// Check for RuntimeStart events for the selected processes and warn if missing
WarnAboutMissingTypeInfoForProcesses(parentWindow, processIDs, sourceName);
}

public override bool SupportsProcesses { get { return true; } }

/// <summary>
Expand Down