Skip to content

Commit

Permalink
Add support for process start
Browse files Browse the repository at this point in the history
  • Loading branch information
xoofx committed Dec 25, 2024
1 parent c4ce36e commit bbb1669
Show file tree
Hide file tree
Showing 7 changed files with 293 additions and 58 deletions.
39 changes: 37 additions & 2 deletions src/Ultra.Core/Model/UTraceProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// See license.txt file in the project root for full license information.

using System.Runtime.CompilerServices;

using System.Runtime.InteropServices;
using XenoAtom.Collections;

namespace Ultra.Core.Model;
Expand All @@ -16,7 +16,17 @@ public sealed class UTraceProcess
/// <summary>
/// Gets or sets the process ID.
/// </summary>
public ulong ProcessID { get; set; }
public int ProcessID { get; set; }

/// <summary>
/// Gets or sets the start time of the process.
/// </summary>
public DateTime StartTime { get; set; }

/// <summary>
/// Gets or sets the end time of the process.
/// </summary>
public DateTime EndTime { get; set; }

/// <summary>
/// Gets or sets the file path of the process.
Expand All @@ -28,6 +38,21 @@ public sealed class UTraceProcess
/// </summary>
public string CommandLine { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the architecture of the process.
/// </summary>
public Architecture ProcessArchitecture { get; set; }

/// <summary>
/// Gets or sets the runtime identifier of the process.
/// </summary>
public string RuntimeIdentifier { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the operating system description of the process.
/// </summary>
public string OSDescription { get; set; } = string.Empty;

/// <summary>
/// Gets the list of threads in the traced process.
/// </summary>
Expand All @@ -52,4 +77,14 @@ public sealed class UTraceProcess
/// Gets the list of call stacks in the traced process.
/// </summary>
public UCallStackList CallStacks { get; } = new();

/// <summary>
/// Checks if the given time is within the time range of the process.
/// </summary>
/// <param name="time">The time to check.</param>
/// <returns>True if the time is within the time range of the process, false otherwise.</returns>
public bool WithinTimeRange(DateTime time)
{
return EndTime.Ticks > 0 ? time >= StartTime && time <= EndTime : time >= StartTime;
}
}
153 changes: 109 additions & 44 deletions src/Ultra.Core/Parser/UltraEventPipeProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// Licensed under the BSD-Clause 2 license.
// See license.txt file in the project root for full license information.

using System.Diagnostics.Tracing;
using System.Numerics;
using System.Runtime.CompilerServices;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
Expand All @@ -20,28 +18,26 @@ internal class UltraEventPipeProcessor
{
private readonly EventPipeEventSource _samplerEventSource;
private readonly UltraSamplerParser _samplerParser;
private ProcessState? _currentProcessState;

private readonly UTraceProcess _process = new();
private readonly UTraceModuleList _modules;
private readonly UTraceManagedMethodList _managedMethods;
private UnsafeDictionary<ulong, ThreadSamplerState> _threadSamplingStates = new();
private readonly UTraceSession _session = new();

private readonly UnsafeDictionary<int, ProcessState> _processes = new(1);

private readonly EventPipeEventSource? _clrEventSource;
private readonly ClrRundownTraceEventParser? _clrRundownTraceEventParser;

public UltraEventPipeProcessor(EventPipeEventSource samplerEventSource)
{
_samplerEventSource = samplerEventSource;
_modules = _process.Modules;
_managedMethods = _process.ManagedMethods;

_samplerParser = new UltraSamplerParser(samplerEventSource);

// NativeCallstack and NativeModule
_samplerParser.EventNativeCallstack += SamplerParserOnEventNativeCallstack;
_samplerParser.EventNativeModule += SamplerParserOnEventNativeModule;
_samplerParser.EventNativeThreadStart += SamplerParserOnEventNativeThreadStart;
_samplerParser.EventNativeThreadStop += SamplerParserOnEventNativeThreadStop;
_samplerParser.EventNativeProcessStart += SamplerParserOnEventNativeProcessStart;
_samplerParser.Source.Dynamic.AddCallbackForProviderEvent("Microsoft-DotNETCore-EventPipe", "ProcessInfo", SamplerProcessInfo);
}

Expand Down Expand Up @@ -99,18 +95,32 @@ public UltraEventPipeProcessor(EventPipeEventSource samplerEventSource, EventPip
_clrRundownTraceEventParser.MethodILToNativeMapDCStop += ProcessMethodILToNativeMap;
}

private void SamplerParserOnEventNativeProcessStart(UltraNativeProcessStartTraceEvent processStartEvent)
{
var processState = GetProcessState(processStartEvent);
var process = processState.CurrentProcess!;

process.StartTime = processStartEvent.StartTimeUtc;
process.ProcessArchitecture = processStartEvent.ProcessArchitecture;
process.RuntimeIdentifier = processStartEvent.RuntimeIdentifier.ToString();
process.OSDescription = processStartEvent.OSDescription.ToString();

processState.SetCurrentProcess(process);
_session.Processes.Add(process);
}

private void ClrOnGCRestartEEStart(GCNoUserDataTraceData gcRestartEE)
{
var gcRestartEEMarker = new GCRestartExecutionEngineTraceMarker()
{
StartTime = UTimeSpan.FromMilliseconds(gcRestartEE.TimeStampRelativeMSec)
};
GetThreadSamplingState((ulong)gcRestartEE.ThreadID).PendingGCRestartExecutionEngineTraceMarkers.Add(gcRestartEEMarker);
GetProcessState(gcRestartEE).GetThreadSamplingState((ulong)gcRestartEE.ThreadID).PendingGCRestartExecutionEngineTraceMarkers.Add(gcRestartEEMarker);
}

private void ClrOnGCRestartEEStop(GCNoUserDataTraceData gcRestartEE)
{
var threadState = GetThreadSamplingState((ulong)gcRestartEE.ThreadID);
var threadState = GetThreadSamplingState(gcRestartEE, (ulong)gcRestartEE.ThreadID);
if (threadState.PendingGCRestartExecutionEngineTraceMarkers.Count == 0) return;
var gcRestartEEMarker = threadState.PendingGCRestartExecutionEngineTraceMarkers.Pop();
gcRestartEEMarker.Duration = TimeSpan.FromMilliseconds(gcRestartEE.TimeStampRelativeMSec) - gcRestartEEMarker.StartTime.Value;
Expand All @@ -126,12 +136,12 @@ private void ClrOnGCSuspendEEStart(GCSuspendEETraceData gcSuspendEE)
Count = gcSuspendEE.Count
};

GetThreadSamplingState((ulong)gcSuspendEE.ThreadID).PendingGCSuspendExecutionEngineTraceMarkers.Add(gcSuspendEEMarker);
GetThreadSamplingState(gcSuspendEE, (ulong)gcSuspendEE.ThreadID).PendingGCSuspendExecutionEngineTraceMarkers.Add(gcSuspendEEMarker);
}

private void ClrOnGCSuspendEEStop(GCNoUserDataTraceData obj)
{
var threadState = GetThreadSamplingState((ulong)obj.ThreadID);
var threadState = GetThreadSamplingState(obj, (ulong)obj.ThreadID);
if (threadState.PendingGCSuspendExecutionEngineTraceMarkers.Count == 0) return;
var gcSuspendEEMarker = threadState.PendingGCSuspendExecutionEngineTraceMarkers.Pop();
gcSuspendEEMarker.Duration = TimeSpan.FromMilliseconds(obj.TimeStampRelativeMSec) - gcSuspendEEMarker.StartTime.Value;
Expand All @@ -140,7 +150,7 @@ private void ClrOnGCSuspendEEStop(GCNoUserDataTraceData obj)

private void ClrOnGCStop(GCEndTraceData obj)
{
var threadState = GetThreadSamplingState((ulong)obj.ThreadID);
var threadState = GetThreadSamplingState(obj, (ulong)obj.ThreadID);
if (threadState.PendingGCEvents.Count == 0) return;
var gcEvent = threadState.PendingGCEvents.Pop();
gcEvent.Duration = TimeSpan.FromMilliseconds(obj.TimeStampRelativeMSec) - gcEvent.StartTime.Value;
Expand All @@ -157,7 +167,7 @@ private void ClrOnGCStart(GCStartTraceData gcStart)
Depth = gcStart.Depth,
GCType = gcStart.Type.ToString()
};
GetThreadSamplingState((ulong)gcStart.ThreadID).PendingGCEvents.Push(gcEvent);
GetThreadSamplingState(gcStart, (ulong)gcStart.ThreadID).PendingGCEvents.Push(gcEvent);
}

private void ClrOnGCAllocationTick(GCAllocationTickTraceData allocationTick)
Expand All @@ -177,12 +187,12 @@ private void ClrOnGCAllocationTick(GCAllocationTickTraceData allocationTick)
HeapIndex = allocationTick.HeapIndex
};

GetThreadSamplingState((ulong)allocationTick.ThreadID).Thread.Markers.Add(allocationTickEvent);
GetThreadSamplingState(allocationTick, (ulong)allocationTick.ThreadID).Thread.Markers.Add(allocationTickEvent);
}

private void ClrOnGCHeapStats(GCHeapStatsTraceData evt)
{
var threadState = GetThreadSamplingState((ulong)evt.ThreadID);
var threadState = GetThreadSamplingState(evt, (ulong)evt.ThreadID);
var gcHeapStats = new GCHeapStatsTraceMarker()
{
StartTime = UTimeSpan.FromMilliseconds(evt.TimeStampRelativeMSec),
Expand All @@ -209,7 +219,7 @@ private void ClrOnGCHeapStats(GCHeapStatsTraceData evt)

private void ClrOnMethodJittingStarted(MethodJittingStartedTraceData methodJittingStarted)
{
var threadState = GetThreadSamplingState((ulong)methodJittingStarted.ThreadID);
var threadState = GetThreadSamplingState(methodJittingStarted, (ulong)methodJittingStarted.ThreadID);

var signature = methodJittingStarted.MethodSignature;
var indexOfParent = signature.IndexOf('(');
Expand All @@ -233,7 +243,8 @@ private void ClrOnMethodJittingStarted(MethodJittingStartedTraceData methodJitti

private void ProcessMethodILToNativeMap(MethodILToNativeMapTraceData obj)
{
if (!_managedMethods.TryFindMethodById(obj.MethodID, out var method))
var managedMethods = GetProcessState(obj).CurrentProcess!.ManagedMethods;
if (!managedMethods.TryFindMethodById(obj.MethodID, out var method))
{
return;
}
Expand Down Expand Up @@ -279,7 +290,7 @@ private void ProcessMethodUnloadVerbose(MethodLoadUnloadVerboseTraceData obj)
private void ProcessMethodLoadVerbose(MethodLoadUnloadVerboseTraceData method)
{
// Log Jit Marker
var threadState = GetThreadSamplingState((ulong)method.ThreadID);
var threadState = GetThreadSamplingState(method, (ulong)method.ThreadID);
if (threadState.PendingJitCompiles.TryGetValue(method.MethodID, out var jitCompileMarker))
{
jitCompileMarker.Duration = TimeSpan.FromMilliseconds(method.TimeStampRelativeMSec) -
Expand All @@ -288,12 +299,14 @@ private void ProcessMethodLoadVerbose(MethodLoadUnloadVerboseTraceData method)
threadState.PendingJitCompiles.Remove(method.MethodID);
}

_managedMethods.GetOrCreateManagedMethod(method.ThreadID, method.ModuleID, method.MethodID, method.MethodNamespace, method.MethodName, method.MethodSignature, method.MethodToken, method.MethodFlags, method.MethodStartAddress, (ulong)method.MethodSize);
var managedMethods = GetProcessState(method).CurrentProcess!.ManagedMethods;
managedMethods.GetOrCreateManagedMethod(method.ThreadID, method.ModuleID, method.MethodID, method.MethodNamespace, method.MethodName, method.MethodSignature, method.MethodToken, method.MethodFlags, method.MethodStartAddress, (ulong)method.MethodSize);
}

private void ProcessModuleLoadUnload(ModuleLoadUnloadTraceData data, bool isLoad, bool isDCStartStop)
{
var module = _modules.GetOrCreateManagedModule(data.ModuleID, data.AssemblyID, data.ModuleILPath);
var modules = GetProcessState(data).CurrentProcess!.Modules;
var module = modules.GetOrCreateManagedModule(data.ModuleID, data.AssemblyID, data.ModuleILPath);

module.ModuleFile.SymbolUuid = data.ManagedPdbSignature;
module.ModuleFile.SymbolFilePath = data.ManagedPdbBuildPath;
Expand All @@ -315,7 +328,8 @@ private void SamplerParserOnEventNativeModule(UltraNativeModuleTraceEvent evt)
{
if (evt.ModulePath is not null)
{
var module = _modules.GetOrCreateNativeModule(evt.LoadAddress, evt.Size, evt.ModulePath);
var modules = GetProcessState(evt).CurrentProcess!.Modules;
var module = modules.GetOrCreateNativeModule(evt.LoadAddress, evt.Size, evt.ModulePath);

if (evt.NativeModuleEventKind == UltraSamplerNativeModuleEventKind.Unloaded)
{
Expand All @@ -334,19 +348,20 @@ private void SamplerParserOnEventNativeModule(UltraNativeModuleTraceEvent evt)

private void SamplerParserOnEventNativeCallstack(UltraNativeCallstackTraceEvent callstackTraceEvent)
{
GetThreadSamplingState(callstackTraceEvent.FrameThreadId).RecordStack(_process, callstackTraceEvent);
var process = GetProcessState(callstackTraceEvent).CurrentProcess!;
GetThreadSamplingState(callstackTraceEvent, callstackTraceEvent.FrameThreadId).RecordStack(process, callstackTraceEvent);
}

private void SamplerParserOnEventNativeThreadStart(UltraNativeThreadStartTraceEvent obj)
{
var thread = GetThreadSamplingState(obj.FrameThreadId).Thread;
var thread = GetThreadSamplingState(obj, obj.FrameThreadId).Thread;
thread.StartTime = UTimeSpan.FromMilliseconds(obj.TimeStampRelativeMSec);
thread.Name = obj.ThreadName;
}

private void SamplerParserOnEventNativeThreadStop(UltraNativeThreadStopTraceEvent obj)
{
var thread = GetThreadSamplingState(obj.FrameThreadId).Thread;
var thread = GetThreadSamplingState(obj, obj.FrameThreadId).Thread;
thread.StopTime = UTimeSpan.FromMilliseconds(obj.TimeStampRelativeMSec);
}

Expand All @@ -355,40 +370,90 @@ public UTraceSession Run()
// Run CLR if available
_clrEventSource?.Process();

_managedMethods.SortMethodAddressRanges();
foreach (var process in _session.Processes)
{
process.ManagedMethods.SortMethodAddressRanges();
}

// Run sampler before CLR
_samplerEventSource.Process();

var session = new UTraceSession();
session.Processes.Add(_process);
_session.NumberOfProcessors = _samplerEventSource.NumberOfProcessors;
_session.StartTime = _samplerEventSource.SessionStartTime;
_session.Duration = _samplerEventSource.SessionDuration;
_session.CpuSpeedMHz = _samplerEventSource.CpuSpeedMHz;

session.NumberOfProcessors = _samplerEventSource.NumberOfProcessors;
session.StartTime = _samplerEventSource.SessionStartTime;
session.Duration = _samplerEventSource.SessionDuration;
session.CpuSpeedMHz = _samplerEventSource.CpuSpeedMHz;
return _session;
}

return session;
private void SamplerProcessInfo(TraceEvent obj)
{
var osInformation = obj.PayloadByName("OSInformation") as string;
var archInformation = obj.PayloadByName("ArchInformation") as string;
//Console.WriteLine(osInformation);
}

private ThreadSamplerState GetThreadSamplingState(ulong threadID)
private ProcessState GetProcessState(TraceEvent evt)
{
if (!_threadSamplingStates.TryGetValue(threadID, out var threadSamplingState))
var processID = evt.ProcessID;
if (_currentProcessState is not null && _currentProcessState.ProcessID == processID)
{
var thread = _process.Threads.GetOrCreateThread(threadID);
threadSamplingState = new(thread);
_threadSamplingStates.Add(threadID, threadSamplingState);
return _currentProcessState;
}
if (!_processes.TryGetValue(processID, out var state))
{
state = new ProcessState()
{
ProcessID = processID,
};
_processes.Add(processID, state);

var process = new UTraceProcess()
{
ProcessID = evt.ProcessID
};
state.SetCurrentProcess(process);
_session.Processes.Add(process);
}
return threadSamplingState;

_currentProcessState = state;
return state;
}

private void SamplerProcessInfo(TraceEvent obj)
private ThreadSamplerState GetThreadSamplingState(TraceEvent evt, ulong threadID)
{
var osInformation = obj.PayloadByName("OSInformation") as string;
var archInformation = obj.PayloadByName("ArchInformation") as string;
//Console.WriteLine(osInformation);
return GetProcessState(evt).GetThreadSamplingState(threadID);
}

private class ProcessState
{
public required int ProcessID;

private UTraceProcess? _process;
private UnsafeDictionary<ulong, ThreadSamplerState> _threadSamplingStates = new();

public void SetCurrentProcess(UTraceProcess process)
{
if (_process != process)
{
_process = process;
_threadSamplingStates.Clear();
}
}

public UTraceProcess? CurrentProcess => _process;

public ThreadSamplerState GetThreadSamplingState(ulong threadID)
{
if (!_threadSamplingStates.TryGetValue(threadID, out var threadSamplingState))
{
var thread = _process!.Threads.GetOrCreateThread(threadID);
threadSamplingState = new(thread);
_threadSamplingStates.Add(threadID, threadSamplingState);
}
return threadSamplingState;
}
}

private class ThreadSamplerState
{
Expand Down
Loading

0 comments on commit bbb1669

Please sign in to comment.