in src/PerfView/PerfViewData.cs [4312:6203]
protected internal override StackSource OpenStackSourceImpl(string streamName, TextWriter log, double startRelativeMSec = 0, double endRelativeMSec = double.PositiveInfinity, Predicate<TraceEvent> predicate = null)
{
var eventLog = GetTraceLog(log);
bool showOptimizationTiers =
App.CommandLineArgs.ShowOptimizationTiers || streamName.Contains("(with Optimization Tiers)");
if (streamName.StartsWith("CPU"))
{
return eventLog.CPUStacks(null, App.CommandLineArgs, showOptimizationTiers, predicate);
}
// var stackSource = new InternTraceEventStackSource(eventLog);
var stackSource = new MutableTraceEventStackSource(eventLog);
stackSource.ShowUnknownAddresses = App.CommandLineArgs.ShowUnknownAddresses;
stackSource.ShowOptimizationTiers = showOptimizationTiers;
TraceEvents events = eventLog.Events;
if (!streamName.Contains("TaskTree") && !streamName.Contains("Tasks)"))
{
if (predicate != null)
{
events = events.Filter(predicate);
}
}
else
{
startRelativeMSec = 0; // These require activity computers and thus need earlier events.
}
if (startRelativeMSec != 0 || endRelativeMSec != double.PositiveInfinity)
{
events = events.FilterByTime(startRelativeMSec, endRelativeMSec);
}
var eventSource = events.GetSource();
var sample = new StackSourceSample(stackSource);
if (streamName == "Thread Time (with Tasks)")
{
return eventLog.ThreadTimeWithTasksStacks();
}
else if (streamName == "Thread Time (with ReadyThread)")
{
return eventLog.ThreadTimeWithReadyThreadStacks();
}
else if (streamName.StartsWith("ASP.NET Thread Time"))
{
if (streamName == "ASP.NET Thread Time (with Tasks)")
{
return eventLog.ThreadTimeWithTasksAspNetStacks();
}
else
{
return eventLog.ThreadTimeAspNetStacks();
}
}
else if (streamName.StartsWith("Thread Time (with StartStop Activities)"))
{
// Handles the normal and (CPU ONLY) case
var startStopSource = new MutableTraceEventStackSource(eventLog);
var computer = new ThreadTimeStackComputer(eventLog, App.GetSymbolReader(eventLog.FilePath));
computer.UseTasks = true;
computer.GroupByStartStopActivity = true;
computer.ExcludeReadyThread = true;
computer.NoAwaitTime = streamName.Contains("(CPU ONLY)");
computer.GenerateThreadTimeStacks(startStopSource);
return startStopSource;
}
else if (streamName == "Thread Time")
{
return eventLog.ThreadTimeStacks();
}
else if (streamName == "Processes / Files / Registry")
{
return GetProcessFileRegistryStackSource(eventSource, log);
}
else if (streamName == "GC Heap Alloc Ignore Free")
{
var gcHeapSimulators = new GCHeapSimulators(eventLog, eventSource, stackSource, log);
gcHeapSimulators.OnNewGCHeapSimulator = delegate (GCHeapSimulator newHeap)
{
newHeap.OnObjectCreate += delegate (Address objAddress, GCHeapSimulatorObject objInfo)
{
sample.Metric = objInfo.RepresentativeSize;
sample.Count = objInfo.GuessCountBasedOnSize(); // We guess a count from the size.
sample.TimeRelativeMSec = objInfo.AllocationTimeRelativeMSec;
sample.StackIndex = stackSource.Interner.CallStackIntern(objInfo.ClassFrame, objInfo.AllocStack); // Add the type as a pseudo frame.
stackSource.AddSample(sample);
return true;
};
};
eventSource.Process();
stackSource.DoneAddingSamples();
}
else if (streamName.StartsWith("GC Heap Net Mem"))
{
var gcHeapSimulators = new GCHeapSimulators(eventLog, eventSource, stackSource, log);
if (streamName == "GC Heap Net Mem (Coarse Sampling)")
{
gcHeapSimulators.UseOnlyAllocTicks = true;
m_extraTopStats = "Sampled only 100K bytes";
}
gcHeapSimulators.OnNewGCHeapSimulator = delegate (GCHeapSimulator newHeap)
{
newHeap.OnObjectCreate += delegate (Address objAddress, GCHeapSimulatorObject objInfo)
{
sample.Metric = objInfo.RepresentativeSize;
sample.Count = objInfo.GuessCountBasedOnSize(); // We guess a count from the size.
sample.TimeRelativeMSec = objInfo.AllocationTimeRelativeMSec;
sample.StackIndex = stackSource.Interner.CallStackIntern(objInfo.ClassFrame, objInfo.AllocStack); // Add the type as a pseudo frame.
stackSource.AddSample(sample);
return true;
};
newHeap.OnObjectDestroy += delegate (double time, int gen, Address objAddress, GCHeapSimulatorObject objInfo)
{
sample.Metric = -objInfo.RepresentativeSize;
sample.Count = -(objInfo.GuessCountBasedOnSize()); // We guess a count from the size.
sample.TimeRelativeMSec = time;
sample.StackIndex = stackSource.Interner.CallStackIntern(objInfo.ClassFrame, objInfo.AllocStack); // We remove the same stack we added at alloc.
stackSource.AddSample(sample);
};
newHeap.OnGC += delegate (double time, int gen)
{
sample.Metric = float.Epsilon;
sample.Count = 1;
sample.TimeRelativeMSec = time;
StackSourceCallStackIndex processStack = stackSource.GetCallStackForProcess(newHeap.Process);
StackSourceFrameIndex gcFrame = stackSource.Interner.FrameIntern("GC Occurred Gen(" + gen + ")");
sample.StackIndex = stackSource.Interner.CallStackIntern(gcFrame, processStack);
stackSource.AddSample(sample);
};
};
eventSource.Process();
stackSource.DoneAddingSamples();
}
else if (streamName.StartsWith("Gen 2 Object Deaths"))
{
var gcHeapSimulators = new GCHeapSimulators(eventLog, eventSource, stackSource, log);
if (streamName == "Gen 2 Object Deaths (Coarse Sampling)")
{
gcHeapSimulators.UseOnlyAllocTicks = true;
m_extraTopStats = "Sampled only 100K bytes";
}
gcHeapSimulators.OnNewGCHeapSimulator = delegate (GCHeapSimulator newHeap)
{
newHeap.OnObjectDestroy += delegate (double time, int gen, Address objAddress, GCHeapSimulatorObject objInfo)
{
if (2 <= gen)
{
sample.Metric = objInfo.RepresentativeSize;
sample.Count = objInfo.GuessCountBasedOnSize(); // We guess a count from the size.
sample.TimeRelativeMSec = objInfo.AllocationTimeRelativeMSec;
sample.StackIndex = stackSource.Interner.CallStackIntern(objInfo.ClassFrame, objInfo.AllocStack);
stackSource.AddSample(sample);
}
};
newHeap.OnGC += delegate (double time, int gen)
{
sample.Metric = float.Epsilon;
sample.Count = 1;
sample.TimeRelativeMSec = time;
StackSourceCallStackIndex processStack = stackSource.GetCallStackForProcess(newHeap.Process);
StackSourceFrameIndex gcFrame = stackSource.Interner.FrameIntern("GC Occurred Gen(" + gen + ")");
sample.StackIndex = stackSource.Interner.CallStackIntern(gcFrame, processStack);
stackSource.AddSample(sample);
};
};
eventSource.Process();
stackSource.DoneAddingSamples();
}
else if (streamName == "GC Heap Alloc Ignore Free (Coarse Sampling)")
{
TypeNameSymbolResolver typeNameSymbolResolver = new TypeNameSymbolResolver(FilePath, log);
bool seenBadAllocTick = false;
eventSource.Clr.GCAllocationTick += delegate (GCAllocationTickTraceData data)
{
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
var stackIndex = stackSource.GetCallStack(data.CallStackIndex(), data);
var typeName = data.TypeName;
if (string.IsNullOrEmpty(typeName))
{
// Attempt to resolve the type name.
TraceLoadedModule module = data.Process().LoadedModules.GetModuleContainingAddress(data.TypeID, data.TimeStampRelativeMSec);
if (module != null)
{
// Resolve the type name.
typeName = typeNameSymbolResolver.ResolveTypeName((int)(data.TypeID - module.ModuleFile.ImageBase), module.ModuleFile, TypeNameSymbolResolver.TypeNameOptions.StripModuleName);
}
}
if (typeName != null && typeName.Length > 0)
{
var nodeIndex = stackSource.Interner.FrameIntern("Type " + typeName);
stackIndex = stackSource.Interner.CallStackIntern(nodeIndex, stackIndex);
}
sample.Metric = data.GetAllocAmount(ref seenBadAllocTick);
if (data.AllocationKind == GCAllocationKind.Large)
{
var nodeIndex = stackSource.Interner.FrameIntern("LargeObject");
stackIndex = stackSource.Interner.CallStackIntern(nodeIndex, stackIndex);
}
sample.StackIndex = stackIndex;
stackSource.AddSample(sample);
};
eventSource.Process();
m_extraTopStats = "Sampled only 100K bytes";
}
else if (streamName == "Exceptions")
{
eventSource.Clr.ExceptionStart += delegate (ExceptionTraceData data)
{
sample.Metric = 1;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
// Create a call stack that ends with the 'throw'
var nodeName = "Throw(" + data.ExceptionType + ") " + data.ExceptionMessage;
var nodeIndex = stackSource.Interner.FrameIntern(nodeName);
sample.StackIndex = stackSource.Interner.CallStackIntern(nodeIndex, stackSource.GetCallStack(data.CallStackIndex(), data));
stackSource.AddSample(sample);
};
eventSource.Kernel.MemoryAccessViolation += delegate (MemoryPageFaultTraceData data)
{
sample.Metric = 1;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
// Create a call stack that ends with the 'throw'
var nodeName = "AccessViolation(ADDR=" + data.VirtualAddress.ToString("x") + ")";
var nodeIndex = stackSource.Interner.FrameIntern(nodeName);
sample.StackIndex = stackSource.Interner.CallStackIntern(nodeIndex, stackSource.GetCallStack(data.CallStackIndex(), data));
stackSource.AddSample(sample);
};
eventSource.Process();
}
else if (streamName == "Pinning At GC Time")
{
// Wire up the GC heap simulations.
GCHeapSimulators gcHeapSimulators = new GCHeapSimulators(eventLog, eventSource, stackSource, log);
// Keep track of the current GC per process
var curGCGen = new int[eventLog.Processes.Count];
var curGCIndex = new int[eventLog.Processes.Count];
eventSource.Clr.GCStart += delegate (Microsoft.Diagnostics.Tracing.Parsers.Clr.GCStartTraceData data)
{
var process = data.Process();
if (process == null)
{
return;
}
curGCGen[(int)process.ProcessIndex] = data.Depth;
curGCIndex[(int)process.ProcessIndex] = data.Count;
};
// Keep track of the live Pinning handles per process.
var allLiveHandles = new Dictionary<Address, GCHandleInfo>[eventLog.Processes.Count];
Action<SetGCHandleTraceData> onSetHandle = delegate (SetGCHandleTraceData data)
{
if (!(data.Kind == GCHandleKind.AsyncPinned || data.Kind == GCHandleKind.Pinned))
{
return;
}
var process = data.Process();
if (process == null)
{
return;
}
var liveHandles = allLiveHandles[(int)process.ProcessIndex];
if (liveHandles == null)
{
allLiveHandles[(int)process.ProcessIndex] = liveHandles = new Dictionary<Address, GCHandleInfo>();
}
GCHandleInfo info;
var handle = data.HandleID;
if (!liveHandles.TryGetValue(handle, out info))
{
liveHandles[handle] = info = new GCHandleInfo();
info.PinStartTimeRelativeMSec = data.TimeStampRelativeMSec;
info.ObjectAddress = data.ObjectID;
info.IsAsync = (data.Kind == GCHandleKind.AsyncPinned || data.Kind == GCHandleKind.DependendAsyncPinned);
info.GCGen = (byte)data.Generation;
info.PinStack = stackSource.GetCallStack(data.CallStackIndex(), data);
// watch this object as it GCs happen (but frankly it should not move).
gcHeapSimulators[process].TrackObject(info.ObjectAddress);
}
};
var clrPrivate = new ClrPrivateTraceEventParser(eventSource);
clrPrivate.GCSetGCHandle += onSetHandle;
eventSource.Clr.GCSetGCHandle += onSetHandle;
Action<DestroyGCHandleTraceData> onDestroyHandle = delegate (DestroyGCHandleTraceData data)
{
var process = data.Process();
if (process == null)
{
return;
}
var liveHandles = allLiveHandles[(int)process.ProcessIndex];
if (liveHandles == null)
{
allLiveHandles[(int)process.ProcessIndex] = liveHandles = new Dictionary<Address, GCHandleInfo>();
}
GCHandleInfo info;
var handle = data.HandleID;
if (liveHandles.TryGetValue(handle, out info))
{
liveHandles.Remove(handle);
}
};
clrPrivate.GCDestroyGCHandle += onDestroyHandle;
eventSource.Clr.GCDestoryGCHandle += onDestroyHandle;
#if false
var cacheAllocated = new Dictionary<Address, bool>();
Action<TraceEvent> onPinnableCacheAllocate = delegate(TraceEvent data)
{
var objectId = (Address) data.PayloadByName("objectId");
cacheAllocated[objectId] = true;
};
eventSource.Dynamic.AddCallbackForProviderEvent("AllocateBuffer", "Microsoft-DotNETRuntime-PinnableBufferCache", onPinnableCacheAllocate);
eventSource.Dynamic.AddCallbackForProviderEvent("AllocateBuffer", "Microsoft-DotNETRuntime-PinnableBufferCache-Mscorlib", onPinnableCacheAllocate);
Action<PinPlugAtGCTimeTraceData> plugAtGCTime = delegate(PinPlugAtGCTimeTraceData data)
{
};
clrPrivate.GCPinPlugAtGCTime += plugAtGCTime;
eventSource.Clr.GCPinObjectAtGCTime += plugAtGCTime;
#endif
// ThreadStacks maps locations in memory of the thread stack to and maps it to a thread.
var threadStacks = new Dictionary<Address, TraceThread>[eventLog.Processes.Count];
// This per-thread information is used solely as a heuristic backstop to try to guess what
// the Pinned handles are when we don't have other information. We can remove it.
var lastHandleInfoForThreads = new PerThreadGCHandleInfo[eventLog.Threads.Count];
// The main event, we have pinning that is happening at GC time.
Action<PinObjectAtGCTimeTraceData> objectAtGCTime = delegate (PinObjectAtGCTimeTraceData data)
{
var thread = data.Thread();
if (thread == null)
{
return;
}
var process = thread.Process;
var liveHandles = allLiveHandles[(int)process.ProcessIndex];
if (liveHandles == null)
{
allLiveHandles[(int)process.ProcessIndex] = liveHandles = new Dictionary<Address, GCHandleInfo>();
}
string pinKind = "UnknownPinned";
double pinStartTimeRelativeMSec = 0;
StackSourceCallStackIndex pinStack = StackSourceCallStackIndex.Invalid;
StackSourceCallStackIndex allocStack = StackSourceCallStackIndex.Invalid;
int gcGen = curGCGen[(int)process.ProcessIndex];
int gcIndex = curGCIndex[(int)process.ProcessIndex];
GCHandleInfo info;
if (liveHandles.TryGetValue(data.HandleID, out info))
{
pinStack = info.PinStack;
if (pinStack != StackSourceCallStackIndex.Invalid)
{
pinStartTimeRelativeMSec = info.PinStartTimeRelativeMSec;
pinKind = "HandlePinned";
gcGen = info.GCGen;
}
else if (data.ObjectID == info.ObjectAddress)
{
pinStartTimeRelativeMSec = info.PinStartTimeRelativeMSec;
}
else
{
info.PinStartTimeRelativeMSec = data.TimeStampRelativeMSec; // Restart trying to guess how long this lives
info.ObjectAddress = data.ObjectID;
}
}
else
{
liveHandles[data.HandleID] = info = new GCHandleInfo();
info.ObjectAddress = data.ObjectID;
info.PinStartTimeRelativeMSec = data.TimeStampRelativeMSec; // We guess the pinning started at this GC.
}
// This is heuristic logic to determine if the pin handles are async or not.
// Basically async handles are themselves pinned and then point at pinned things. Thus
// if you see handles that point near other handles that is likely an async handle.
// TODO I think we can remove this, because we no longer pin the async handle.
if (pinStack == StackSourceCallStackIndex.Invalid)
{
var lastHandleInfo = lastHandleInfoForThreads[(int)thread.ThreadIndex];
if (lastHandleInfo == null)
{
lastHandleInfoForThreads[(int)thread.ThreadIndex] = lastHandleInfo = new PerThreadGCHandleInfo();
}
// If we see a handle that
if (data.HandleID - lastHandleInfo.LikelyAsyncHandleTable1 < 128)
{
pinKind = "LikelyAsyncPinned";
lastHandleInfo.LikelyAsyncHandleTable1 = data.HandleID;
}
else if (data.HandleID - lastHandleInfo.LikelyAsyncHandleTable2 < 128)
{
// This is here for the async array of buffers case.
pinKind = "LikelyAsyncPinned";
lastHandleInfo.LikelyAsyncHandleTable2 = lastHandleInfo.LikelyAsyncHandleTable1;
lastHandleInfo.LikelyAsyncHandleTable1 = data.HandleID;
}
if (data.HandleID - lastHandleInfo.LastObject < 128)
{
pinKind = "LikelyAsyncDependentPinned";
lastHandleInfo.LikelyAsyncHandleTable2 = lastHandleInfo.LikelyAsyncHandleTable1;
lastHandleInfo.LikelyAsyncHandleTable1 = lastHandleInfo.LastHandle;
}
// Remember our values for heuristics we use to determine if it is an async
lastHandleInfo.LastHandle = data.HandleID;
lastHandleInfo.LastObject = data.ObjectID;
}
var objectInfo = gcHeapSimulators[process].GetObjectInfo(data.ObjectID);
if (objectInfo != null)
{
allocStack = objectInfo.AllocStack;
if ((allocStack != StackSourceCallStackIndex.Invalid) && (objectInfo.ClassFrame != StackSourceFrameIndex.Invalid))
{
if (512 <= objectInfo.Size)
{
var frameName = stackSource.GetFrameName(objectInfo.ClassFrame, false);
var size = 1024;
while (size < objectInfo.Size)
{
size = size * 2;
}
frameName += " <= " + (size / 1024).ToString() + "K";
allocStack = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern(frameName), allocStack);
}
else
{
allocStack = stackSource.Interner.CallStackIntern(objectInfo.ClassFrame, allocStack);
}
}
}
// If we did not get pinning information, see if it is a stack pin
if (pinStack == StackSourceCallStackIndex.Invalid)
{
const Address allocQuantum = 0x10000 - 1; // 64K, must be a power of 2.
var threadStack = threadStacks[(int)process.ProcessIndex];
if (threadStack == null)
{
threadStacks[(int)process.ProcessIndex] = threadStack = new Dictionary<Address, TraceThread>();
foreach (var procThread in process.Threads)
{
// Round up to the next 64K boundary
var loc = (procThread.UserStackBase + allocQuantum) & ~allocQuantum;
// We assume thread stacks are .5 meg (8 * 64K) Growing down.
for (int i = 0; i < 8; i++)
{
threadStack[loc] = procThread;
loc -= (allocQuantum + 1);
}
}
}
Address roundUp = (data.HandleID + allocQuantum) & ~allocQuantum;
TraceThread stackThread;
if (threadStack.TryGetValue(roundUp, out stackThread) && stackThread.StartTimeRelativeMSec <= data.TimeStampRelativeMSec && data.TimeStampRelativeMSec < stackThread.EndTimeRelativeMSec)
{
pinKind = "StackPinned";
pinStack = stackSource.GetCallStackForThread(stackThread);
}
}
/***** OK we now have all the information we collected, create the sample. *****/
sample.StackIndex = StackSourceCallStackIndex.Invalid;
// Choose the stack to use
if (allocStack != StackSourceCallStackIndex.Invalid)
{
sample.StackIndex = allocStack;
sample.StackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("Allocation Location"), sample.StackIndex);
}
else if (pinStack != StackSourceCallStackIndex.Invalid)
{
sample.StackIndex = pinStack;
sample.StackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("Pinning Location"), sample.StackIndex);
}
else
{
var gcThread = data.Thread();
if (gcThread == null)
{
return; // TODO WARN
}
sample.StackIndex = stackSource.GetCallStackForThread(gcThread);
sample.StackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("GC Location"), sample.StackIndex);
}
// Add GC Number
sample.StackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("GC_NUM " + gcIndex), sample.StackIndex);
// Duration of the pin.
var pinDuration = "UNKNOWN";
if (pinStartTimeRelativeMSec != 0)
{
var pinDurationMSec = data.TimeStampRelativeMSec - pinStartTimeRelativeMSec;
var roundedDuration = Math.Pow(10.0, Math.Ceiling(Math.Log10(pinDurationMSec)));
pinDuration = "<= " + roundedDuration.ToString("n");
}
var pinDurationInfo = "PINNED_FOR " + pinDuration + " msec";
sample.StackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern(pinDurationInfo), sample.StackIndex);
// Add the Pin Kind;
sample.StackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern(pinKind), sample.StackIndex);
// Add the type and size
var typeName = data.TypeName;
if (data.ObjectSize > 0)
{
sample.StackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("Type " + typeName + " Size: 0x" + data.ObjectSize.ToString("x")), sample.StackIndex);
}
// Add the generation.
sample.StackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("Generation " + gcGen), sample.StackIndex);
// _sample.StackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("Handle 0x" + data.HandleID.ToString("x") + " Object 0x" + data.ObjectID.ToString("x")), _sample.StackIndex);
// We now have the stack, fill in the rest of the _sample and add it to the stack source.
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
sample.Metric = 1;
stackSource.AddSample(sample);
};
eventSource.Clr.GCPinObjectAtGCTime += objectAtGCTime;
clrPrivate.GCPinObjectAtGCTime += objectAtGCTime; // TODO FIX NOW REMOVE AFTER PRIVATE IS GONE
eventSource.Process();
stackSource.DoneAddingSamples();
}
else if (streamName == "Pinning")
{
var clrPrivate = new ClrPrivateTraceEventParser(eventSource);
var liveHandles = new Dictionary<long, GCHandleInfo>();
int maxLiveHandles = 0;
double maxLiveHandleRelativeMSec = 0;
Action<SetGCHandleTraceData> onSetHandle = delegate (SetGCHandleTraceData data)
{
if (!(data.Kind == GCHandleKind.AsyncPinned || data.Kind == GCHandleKind.Pinned))
{
return;
}
GCHandleInfo info;
var handle = (long)data.HandleID;
if (!liveHandles.TryGetValue(handle, out info))
{
liveHandles[handle] = info = new GCHandleInfo();
if (liveHandles.Count > maxLiveHandles)
{
maxLiveHandles = liveHandles.Count;
maxLiveHandleRelativeMSec = data.TimeStampRelativeMSec;
}
info.PinStartTimeRelativeMSec = data.TimeStampRelativeMSec;
info.ObjectAddress = data.ObjectID;
// TODO deal with nulling out.
string nodeName = (data.Kind == GCHandleKind.Pinned) ? "SinglePinned" : "AsyncPinned";
StackSourceFrameIndex frameIndex = stackSource.Interner.FrameIntern(nodeName);
StackSourceCallStackIndex callStackIndex = stackSource.Interner.CallStackIntern(frameIndex, stackSource.GetCallStack(data.CallStackIndex(), data));
// Add the generation.
nodeName = "Generation " + data.Generation;
frameIndex = stackSource.Interner.FrameIntern(nodeName);
info.PinStack = stackSource.Interner.CallStackIntern(frameIndex, callStackIndex);
}
};
clrPrivate.GCSetGCHandle += onSetHandle;
eventSource.Clr.GCSetGCHandle += onSetHandle;
Action<DestroyGCHandleTraceData> onDestroyHandle = delegate (DestroyGCHandleTraceData data)
{
GCHandleInfo info;
var handle = (long)data.HandleID;
if (liveHandles.TryGetValue(handle, out info))
{
LogGCHandleLifetime(stackSource, sample, info, data.TimeStampRelativeMSec, log);
liveHandles.Remove(handle);
}
};
clrPrivate.GCDestroyGCHandle += onDestroyHandle;
eventSource.Clr.GCDestoryGCHandle += onDestroyHandle;
eventSource.Process();
// Pick up any handles that were never destroyed.
foreach (var info in liveHandles.Values)
{
LogGCHandleLifetime(stackSource, sample, info, eventLog.SessionDuration.TotalMilliseconds, log);
}
stackSource.DoneAddingSamples();
log.WriteLine("The maximum number of live pinning handles is {0} at {1:n3} Msec ", maxLiveHandles, maxLiveHandleRelativeMSec);
}
else if (streamName == "Heap Snapshot Pinning")
{
GCPinnedObjectAnalyzer pinnedObjectAnalyzer = new GCPinnedObjectAnalyzer(FilePath, eventLog, stackSource, sample, log);
pinnedObjectAnalyzer.Execute(GCPinnedObjectViewType.PinnedHandles);
}
else if (streamName == "Heap Snapshot Pinned Object Allocation")
{
GCPinnedObjectAnalyzer pinnedObjectAnalyzer = new GCPinnedObjectAnalyzer(FilePath, eventLog, stackSource, sample, log);
pinnedObjectAnalyzer.Execute(GCPinnedObjectViewType.PinnedObjectAllocations);
}
else if (streamName == "CCW Ref Count")
{
// TODO use the callback model. We seem to have an issue getting the names however.
foreach (var data in events.ByEventType<CCWRefCountChangeTraceData>())
{
sample.Metric = 1;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
var stackIndex = stackSource.GetCallStack(data.CallStackIndex(), data);
var operation = data.Operation;
if (operation.StartsWith("Release", StringComparison.OrdinalIgnoreCase))
{
sample.Metric = -1;
}
var ccwRefKindName = "CCW " + operation;
var ccwRefKindIndex = stackSource.Interner.FrameIntern(ccwRefKindName);
stackIndex = stackSource.Interner.CallStackIntern(ccwRefKindIndex, stackIndex);
var ccwRefCountName = "CCW NewRefCnt " + data.NewRefCount.ToString();
var ccwRefCountIndex = stackSource.Interner.FrameIntern(ccwRefCountName);
stackIndex = stackSource.Interner.CallStackIntern(ccwRefCountIndex, stackIndex);
var ccwInstanceName = "CCW Instance 0x" + data.COMInterfacePointer.ToString("x");
var ccwInstanceIndex = stackSource.Interner.FrameIntern(ccwInstanceName);
stackIndex = stackSource.Interner.CallStackIntern(ccwInstanceIndex, stackIndex);
var ccwTypeName = "CCW Type " + data.NameSpace + "." + data.ClassName;
var ccwTypeIndex = stackSource.Interner.FrameIntern(ccwTypeName);
stackIndex = stackSource.Interner.CallStackIntern(ccwTypeIndex, stackIndex);
sample.StackIndex = stackIndex;
stackSource.AddSample(sample);
}
foreach (var data in events.ByEventType<CCWRefCountChangeAnsiTraceData>())
{
sample.Metric = 1;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
var stackIndex = stackSource.GetCallStack(data.CallStackIndex(), data);
var operation = data.Operation;
if (operation.StartsWith("Release", StringComparison.OrdinalIgnoreCase))
{
sample.Metric = -1;
}
var ccwRefKindName = "CCW " + operation;
var ccwRefKindIndex = stackSource.Interner.FrameIntern(ccwRefKindName);
stackIndex = stackSource.Interner.CallStackIntern(ccwRefKindIndex, stackIndex);
var ccwRefCountName = "CCW NewRefCnt " + data.NewRefCount.ToString();
var ccwRefCountIndex = stackSource.Interner.FrameIntern(ccwRefCountName);
stackIndex = stackSource.Interner.CallStackIntern(ccwRefCountIndex, stackIndex);
var ccwInstanceName = "CCW Instance 0x" + data.COMInterfacePointer.ToString("x");
var ccwInstanceIndex = stackSource.Interner.FrameIntern(ccwInstanceName);
stackIndex = stackSource.Interner.CallStackIntern(ccwInstanceIndex, stackIndex);
var ccwTypeName = "CCW Type " + data.NameSpace + "." + data.ClassName;
var ccwTypeIndex = stackSource.Interner.FrameIntern(ccwTypeName);
stackIndex = stackSource.Interner.CallStackIntern(ccwTypeIndex, stackIndex);
sample.StackIndex = stackIndex;
stackSource.AddSample(sample);
}
}
else if (streamName == ".NET Native CCW Ref Count")
{
// TODO FIX NOW, investigate the missing events. All we know is that incs and dec are not
// consistent with the RefCount value that is in the events.
GuiApp.MainWindow.Dispatcher.BeginInvoke((Action)delegate ()
{
MessageBox.Show(GuiApp.MainWindow,
"Warning: the Interop CCW events on which this data is based seem to be incomplete.\r\n" +
"There seem to be missing instrumentation, which make the referenct counts unreliable\r\n"
, "Data May be Incorrect");
});
var objectToTypeMap = new Dictionary<long, Address>(1000);
var typeToNameMap = new Dictionary<Address, string>(100);
var interopTraceEventParser = new InteropTraceEventParser(eventSource);
Action<double, long, int, int, StackSourceCallStackIndex> handleCWWInfoArgs = (double timestamp, long objectID, int refCount, int metric, StackSourceCallStackIndex stackIndex) =>
{
sample.Metric = metric;
sample.TimeRelativeMSec = timestamp;
var ccwRefKindName = $"CCW {(metric >= 0 ? "AddRef" : "Release")}";
var ccwRefKindNameIndex = stackSource.Interner.FrameIntern(ccwRefKindName);
stackIndex = stackSource.Interner.CallStackIntern(ccwRefKindNameIndex, stackIndex);
var objectId = "Object ID 0x" + objectID.ToString("x");
var objectIdIndex = stackSource.Interner.FrameIntern(objectId);
stackIndex = stackSource.Interner.CallStackIntern(objectIdIndex, stackIndex);
Address typeId;
if (objectToTypeMap.TryGetValue(objectID, out typeId))
{
string objectType = "Object Type ";
string typeName;
if (typeToNameMap.TryGetValue(typeId, out typeName))
{
objectType += typeName;
}
else
{
objectType += "0x" + typeId;
}
var objectTypeIndex = stackSource.Interner.FrameIntern(objectType);
stackIndex = stackSource.Interner.CallStackIntern(objectTypeIndex, stackIndex);
}
var ccwRefCount = "CCW NewRefCnt " + refCount;
var ccwRefCountIndex = stackSource.Interner.FrameIntern(ccwRefCount.ToString());
stackIndex = stackSource.Interner.CallStackIntern(ccwRefCountIndex, stackIndex);
sample.StackIndex = stackIndex;
stackSource.AddSample(sample);
};
TypeNameSymbolResolver typeNameSymbolResolver = new TypeNameSymbolResolver(FilePath, log);
interopTraceEventParser.AddCallbackForEvent<TaskCCWCreationArgs>(null, args =>
{
if (!objectToTypeMap.ContainsKey(args.objectID))
{
objectToTypeMap.Add(args.objectID, args.targetObjectIDType);
}
// Attempt to resolve the type name.
if (!typeToNameMap.ContainsKey(args.targetObjectIDType))
{
TraceLoadedModule module = args.Process().LoadedModules.GetModuleContainingAddress(args.targetObjectIDType, args.TimeStampRelativeMSec);
if (module != null)
{
string typeName = typeNameSymbolResolver.ResolveTypeName((int)(args.targetObjectIDType - module.ModuleFile.ImageBase), module.ModuleFile, TypeNameSymbolResolver.TypeNameOptions.StripModuleName);
if (typeName != null)
{
typeToNameMap.Add(args.targetObjectIDType, typeName);
}
}
}
});
#region TaskCCWQueryRuntimeClassNameArgs commented for a while. TODO: get type info from pdb
//interopTraceEventParser.AddCallbackForEvents<TaskCCWQueryRuntimeClassNameArgs>(args =>
//{
// sample.Metric = 0;
// sample.TimeRelativeMSec = args.TimeStampRelativeMSec;
// var stackIndex = stackSource.GetCallStack(args.CallStackIndex(), args);
// var ccwRefKindName = "CCW QueryRuntimeClassName " + args.runtimeClassName;
// var ccwRefKindNameIndex = stackSource.Interner.FrameIntern(ccwRefKindName);
// stackIndex = stackSource.Interner.CallStackIntern(ccwRefKindNameIndex, stackIndex);
// sample.StackIndex = stackIndex;
// stackSource.AddSample(sample);
//});
#endregion
interopTraceEventParser.AddCallbackForEvents<TaskCCWRefCountIncArgs>(args =>
handleCWWInfoArgs(args.TimeStampRelativeMSec, args.objectID, args.refCount, 1, stackSource.GetCallStack(args.CallStackIndex(), args)));
interopTraceEventParser.AddCallbackForEvents<TaskCCWRefCountDecArgs>(args =>
handleCWWInfoArgs(args.TimeStampRelativeMSec, args.objectID, args.refCount, -1, stackSource.GetCallStack(args.CallStackIndex(), args)));
eventSource.Process();
}
else if (streamName == "Windows Handle Ref Count")
{
var allocationsStacks = new Dictionary<long, StackSourceCallStackIndex>(200);
Action<string, Address, int, int, TraceEvent> onHandleEvent = delegate (string handleTypeName, Address objectInstance, int handleInstance, int handleProcess, TraceEvent data)
{
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
sample.StackIndex = StackSourceCallStackIndex.Invalid;
sample.Metric = 1;
// Closes use the call stack of the allocation site if possible (since that is more helpful)
if (data.Opcode == (TraceEventOpcode)33) // CloseHandle
{
sample.Metric = -1;
long key = (((long)handleProcess) << 32) + handleInstance;
StackSourceCallStackIndex stackIndex;
if (allocationsStacks.TryGetValue(key, out stackIndex))
{
sample.StackIndex = stackIndex;
}
// TODO should we keep track of the ref count and remove the entry when it drops past zero?
}
// If this not a close() (Or if we could not find a stack for the close() make up a call stack from the event.
if (sample.StackIndex == StackSourceCallStackIndex.Invalid)
{
StackSourceCallStackIndex stackIndex = stackSource.GetCallStack(data.CallStackIndex(), data);
// We want all stacks to be int he process where the handle exists. But this not always the case
// If that happened abandon the stack and make up a pseudo stack that lets you know that happened.
if (handleProcess != data.ProcessID)
{
stackIndex = StackSourceCallStackIndex.Invalid;
TraceProcess process = eventLog.Processes.GetProcess(handleProcess, data.TimeStampRelativeMSec);
if (process != null)
{
stackIndex = stackSource.GetCallStackForProcess(process);
}
var markerIndex = stackSource.Interner.FrameIntern("Handle Allocated Out of Process");
stackIndex = stackSource.Interner.CallStackIntern(markerIndex, stackIndex);
}
var nameIndex = stackSource.Interner.FrameIntern(data.EventName);
stackIndex = stackSource.Interner.CallStackIntern(nameIndex, stackIndex);
var instanceName = "Handle Instance " + handleInstance.ToString("n0") + " (0x" + handleInstance.ToString("x") + ")";
var instanceIndex = stackSource.Interner.FrameIntern(instanceName);
stackIndex = stackSource.Interner.CallStackIntern(instanceIndex, stackIndex);
var handleTypeIndex = stackSource.Interner.FrameIntern("Handle Type " + handleTypeName);
stackIndex = stackSource.Interner.CallStackIntern(handleTypeIndex, stackIndex);
//var objectName = "Object Instance 0x" + objectInstance.ToString("x");
//var objectIndex = stackSource.Interner.FrameIntern(objectName);
//stackIndex = stackSource.Interner.CallStackIntern(objectIndex, stackIndex);
sample.StackIndex = stackIndex;
long key = (((long)handleProcess) << 32) + handleInstance;
allocationsStacks[key] = stackIndex;
}
stackSource.AddSample(sample);
};
eventSource.Kernel.AddCallbackForEvents<ObjectHandleTraceData>(data => onHandleEvent(data.ObjectTypeName, data.Object, data.Handle, data.ProcessID, data));
eventSource.Kernel.AddCallbackForEvents<ObjectDuplicateHandleTraceData>(data => onHandleEvent(data.ObjectTypeName, data.Object, data.TargetHandle, data.TargetProcessID, data));
eventSource.Process();
}
else if (streamName.StartsWith("Processor"))
{
eventSource.Kernel.PerfInfoSample += delegate (SampledProfileTraceData data)
{
StackSourceCallStackIndex stackIndex;
var callStackIdx = data.CallStackIndex();
if (callStackIdx == CallStackIndex.Invalid)
{
return;
}
stackIndex = stackSource.GetCallStack(callStackIdx, data);
var processorPriority = "Processor (" + data.ProcessorNumber + ") Priority (" + data.Priority + ")";
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern(processorPriority), stackIndex);
sample.StackIndex = stackIndex;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
sample.Metric = 1;
stackSource.AddSample(sample);
};
eventSource.Process();
}
else if (streamName.StartsWith("Any"))
{
ActivityComputer activityComputer = null;
StartStopActivityComputer startStopComputer = null;
bool isAnyTaskTree = (streamName == "Any TaskTree");
bool isAnyWithTasks = (streamName == "Any Stacks (with Tasks)");
bool isAnyWithStartStop = (streamName == "Any Stacks (with StartStop Activities)"); // These have the call stacks
bool isAnyStartStopTreeNoCallStack = (streamName == "Any StartStopTree"); // These have just the start-stop activities.
if (isAnyTaskTree || isAnyWithTasks || isAnyWithStartStop || isAnyStartStopTreeNoCallStack)
{
activityComputer = new ActivityComputer(eventSource, GetSymbolReader(log));
// Log a pseudo-event that indicates when the activity dies
activityComputer.Stop += delegate (TraceActivity activity, TraceEvent data)
{
// TODO This is a clone of the logic below, factor it.
TraceThread thread = data.Thread();
if (thread != null)
{
return;
}
StackSourceCallStackIndex stackIndex;
if (isAnyTaskTree)
{
// Compute the stack where frames using an activity Name as a frame name.
stackIndex = activityComputer.GetActivityStack(stackSource, activityComputer.GetCurrentActivity(thread));
}
else if (isAnyStartStopTreeNoCallStack)
{
stackIndex = startStopComputer.GetStartStopActivityStack(stackSource, startStopComputer.GetCurrentStartStopActivity(thread, data), thread.Process);
}
else
{
Func<TraceThread, StackSourceCallStackIndex> topFrames = null;
if (isAnyWithStartStop)
{
topFrames = delegate (TraceThread topThread) { return startStopComputer.GetCurrentStartStopActivityStack(stackSource, thread, topThread); };
}
// Use the call stack
stackIndex = activityComputer.GetCallStack(stackSource, data, topFrames);
}
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("ActivityStop " + activity.ToString()), stackIndex);
sample.StackIndex = stackIndex;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
sample.Metric = 1;
stackSource.AddSample(sample);
};
if (isAnyWithStartStop || isAnyStartStopTreeNoCallStack)
{
startStopComputer = new StartStopActivityComputer(eventSource, activityComputer);
}
}
StackSourceFrameIndex blockingFrame = stackSource.Interner.FrameIntern("Event Kernel/Thread/BLOCKING CSwitch");
StackSourceFrameIndex cswitchEventFrame = stackSource.Interner.FrameIntern("Event Windows Kernel/Thread/CSwitch");
StackSourceFrameIndex readyThreadEventFrame = stackSource.Interner.FrameIntern("Event Windows Kernel/Dispatcher/ReadyThread");
StackSourceFrameIndex sampledProfileFrame = stackSource.Interner.FrameIntern("Event Windows Kernel/PerfInfo/Sample");
eventSource.AllEvents += delegate (TraceEvent data)
{
// Get most of the stack (we support getting the normal call stack as well as the task stack.
StackSourceCallStackIndex stackIndex;
if (activityComputer != null)
{
TraceThread thread = data.Thread();
if (thread == null)
{
return;
}
if (isAnyTaskTree)
{
// Compute the stack where frames using an activity Name as a frame name.
stackIndex = activityComputer.GetActivityStack(stackSource, activityComputer.GetCurrentActivity(thread));
}
else if (isAnyStartStopTreeNoCallStack)
{
stackIndex = startStopComputer.GetStartStopActivityStack(stackSource, startStopComputer.GetCurrentStartStopActivity(thread, data), thread.Process);
}
else
{
Func<TraceThread, StackSourceCallStackIndex> topFrames = null;
if (isAnyWithStartStop)
{
topFrames = delegate (TraceThread topThread) { return startStopComputer.GetCurrentStartStopActivityStack(stackSource, thread, topThread); };
}
// Use the call stack
stackIndex = activityComputer.GetCallStack(stackSource, data, topFrames);
}
}
else
{
// Normal case, get the calls stack of frame names.
var callStackIdx = data.CallStackIndex();
if (callStackIdx != CallStackIndex.Invalid)
{
stackIndex = stackSource.GetCallStack(callStackIdx, data);
}
else
{
stackIndex = StackSourceCallStackIndex.Invalid;
}
}
var asCSwitch = data as CSwitchTraceData;
if (asCSwitch != null)
{
if (activityComputer == null) // Just a plain old any-stacks
{
var callStackIdx = asCSwitch.BlockingStack();
if (callStackIdx != CallStackIndex.Invalid)
{
StackSourceCallStackIndex blockingStackIndex = stackSource.GetCallStack(callStackIdx, data);
// Make an entry for the blocking stacks as well.
blockingStackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("EventData OldThreadState " + asCSwitch.OldThreadState), blockingStackIndex);
sample.StackIndex = stackSource.Interner.CallStackIntern(blockingFrame, blockingStackIndex);
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
sample.Metric = 1;
stackSource.AddSample(sample);
}
}
if (stackIndex != StackSourceCallStackIndex.Invalid)
{
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("EventData NewProcessName " + asCSwitch.NewProcessName), stackIndex);
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("EventData OldProcessName " + asCSwitch.OldProcessName), stackIndex);
stackIndex = stackSource.Interner.CallStackIntern(cswitchEventFrame, stackIndex);
}
goto ADD_SAMPLE;
}
if (stackIndex == StackSourceCallStackIndex.Invalid)
{
return;
}
var asSampledProfile = data as SampledProfileTraceData;
if (asSampledProfile != null)
{
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("EventData Priority " + asSampledProfile.Priority), stackIndex);
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("EventData Processor " + asSampledProfile.ProcessorNumber), stackIndex);
stackIndex = stackSource.Interner.CallStackIntern(sampledProfileFrame, stackIndex);
goto ADD_SAMPLE;
}
var asReadyThread = data as DispatcherReadyThreadTraceData;
if (asReadyThread != null)
{
var awakenedName = "EventData Readied Thread " + asReadyThread.AwakenedThreadID +
" Proc " + asReadyThread.AwakenedProcessID;
var awakenedIndex = stackSource.Interner.FrameIntern(awakenedName);
stackIndex = stackSource.Interner.CallStackIntern(awakenedIndex, stackIndex);
stackIndex = stackSource.Interner.CallStackIntern(readyThreadEventFrame, stackIndex);
goto ADD_SAMPLE;
}
// TODO FIX NOW remove for debugging activity stuff.
#if false
var activityId = data.ActivityID;
if (activityId != Guid.Empty && ActivityComputer.IsActivityPath(activityId))
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("ActivityPath " + ActivityComputer.ActivityPathString(activityId)), stackIndex);
#endif
var asObjectAllocated = data as ObjectAllocatedArgs;
if (asObjectAllocated != null)
{
var size = "EventData Size 0x" + asObjectAllocated.Size.ToString("x");
var sizeIndex = stackSource.Interner.FrameIntern(size);
stackIndex = stackSource.Interner.CallStackIntern(sizeIndex, stackIndex);
goto ADD_EVENT_FRAME;
}
var asAllocTick = data as GCAllocationTickTraceData;
if (asAllocTick != null)
{
var frameIdx = stackSource.Interner.FrameIntern("EventData Kind " + asAllocTick.AllocationKind);
stackIndex = stackSource.Interner.CallStackIntern(frameIdx, stackIndex);
frameIdx = stackSource.Interner.FrameIntern("EventData Size " + asAllocTick.AllocationAmount64);
stackIndex = stackSource.Interner.CallStackIntern(frameIdx, stackIndex);
var typeName = asAllocTick.TypeName;
if (string.IsNullOrEmpty(typeName))
{
typeName = "TypeId 0x" + asAllocTick.TypeID;
}
frameIdx = stackSource.Interner.FrameIntern("EventData TypeName " + typeName);
stackIndex = stackSource.Interner.CallStackIntern(frameIdx, stackIndex);
goto ADD_EVENT_FRAME;
}
var asSampleObjectAllocated = data as GCSampledObjectAllocationTraceData;
if (asSampleObjectAllocated != null)
{
var size = "EventData Size 0x" + asSampleObjectAllocated.TotalSizeForTypeSample.ToString("x");
var sizeIndex = stackSource.Interner.FrameIntern(size);
stackIndex = stackSource.Interner.CallStackIntern(sizeIndex, stackIndex);
goto ADD_EVENT_FRAME;
}
var asSetGCHandle = data as SetGCHandleTraceData;
if (asSetGCHandle != null)
{
var handleName = "EventData GCHandleKind " + asSetGCHandle.Kind.ToString();
var handleIndex = stackSource.Interner.FrameIntern(handleName);
stackIndex = stackSource.Interner.CallStackIntern(handleIndex, stackIndex);
goto ADD_EVENT_FRAME;
}
var asPageAccess = data as MemoryPageAccessTraceData;
if (asPageAccess != null)
{
sample.Metric = 4; // Convenience since these are 4K pages
// EMit the kind, which may have a file name argument.
var pageKind = asPageAccess.PageKind;
string fileName = asPageAccess.FileName;
if (fileName == null)
{
fileName = "";
}
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern(pageKind.ToString() + " " + fileName), stackIndex);
// If it is the range of a module, log that as well, as well as it bucket.
var address = asPageAccess.VirtualAddress;
var process = data.Process();
if (process != null)
{
var module = process.LoadedModules.GetModuleContainingAddress(address, asPageAccess.TimeStampRelativeMSec);
if (module != null)
{
if (module.ModuleFile != null && module.ModuleFile.ImageSize != 0)
{
// Create a node that indicates where in the file (in buckets) the access was from
double normalizeDistance = (address - module.ImageBase) / ((double)module.ModuleFile.ImageSize);
if (0 <= normalizeDistance && normalizeDistance < 1)
{
const int numBuckets = 20;
int bucket = (int)(normalizeDistance * numBuckets);
int bucketSizeInPages = module.ModuleFile.ImageSize / (numBuckets * 4096);
string bucketName = "Image Bucket " + bucket + " Size " + bucketSizeInPages + " Pages";
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern(bucketName), stackIndex);
}
}
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("EventData Image " + module.ModuleFile.FilePath), stackIndex);
}
}
goto ADD_EVENT_FRAME;
}
var asPMCCounter = data as PMCCounterProfTraceData;
if (asPMCCounter != null)
{
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("EventData Processor " + asPMCCounter.ProcessorNumber), stackIndex);
var source = "EventData ProfileSourceID " + asPMCCounter.ProfileSource;
var sourceIndex = stackSource.Interner.FrameIntern(source);
stackIndex = stackSource.Interner.CallStackIntern(sourceIndex, stackIndex);
goto ADD_EVENT_FRAME;
}
var asFileCreate = data as FileIOCreateTraceData;
if (asFileCreate != null)
{
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("CreateOptions: " + asFileCreate.CreateOptions), stackIndex);
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("FileAttributes: " + asFileCreate.FileAttributes), stackIndex);
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("ShareAccess: " + asFileCreate.ShareAccess), stackIndex);
// stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("CreateDispostion: " + asFileCreate.CreateDispostion), stackIndex);
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("FileName: " + asFileCreate.FileName), stackIndex);
goto ADD_EVENT_FRAME;
}
// Tack on additional info about the event.
var fieldNames = data.PayloadNames;
for (int i = 0; i < fieldNames.Length; i++)
{
var fieldName = fieldNames[i];
if (0 <= fieldName.IndexOf("Name", StringComparison.OrdinalIgnoreCase) ||
fieldName == "OpenPath" || fieldName == "Url" || fieldName == "Uri" || fieldName == "ConnectionId" ||
fieldName == "ExceptionType" || 0 <= fieldName.IndexOf("Message", StringComparison.OrdinalIgnoreCase))
{
var value = data.PayloadString(i);
var fieldNodeName = "EventData " + fieldName + " " + value;
var fieldNodeIndex = stackSource.Interner.FrameIntern(fieldNodeName);
stackIndex = stackSource.Interner.CallStackIntern(fieldNodeIndex, stackIndex);
}
}
ADD_EVENT_FRAME:
// Tack on event name
var eventNodeName = "Event " + data.ProviderName + "/" + data.EventName;
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern(eventNodeName), stackIndex);
ADD_SAMPLE:
sample.StackIndex = stackIndex;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
sample.Metric = 1;
stackSource.AddSample(sample);
};
eventSource.Process();
}
else if (streamName == "Managed Load")
{
eventSource.Clr.LoaderModuleLoad += delegate (ModuleLoadUnloadTraceData data)
{
sample.Metric = 1;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
// Create a call stack that ends with 'Disk READ <fileName> (<fileDirectory>)'
var nodeName = "Load " + data.ModuleILPath;
var nodeIndex = stackSource.Interner.FrameIntern(nodeName);
sample.StackIndex = stackSource.Interner.CallStackIntern(nodeIndex, stackSource.GetCallStack(data.CallStackIndex(), data));
stackSource.AddSample(sample);
};
eventSource.Process();
}
else if (streamName == "Disk I/O")
{
var diskStartStack = new Dictionary<Address, StackSourceCallStackIndex>(50);
// On a per-disk basis remember when the last Disk I/O completed.
var lastDiskEndMSec = new GrowableArray<double>(4);
eventSource.Kernel.AddCallbackForEvents<DiskIOInitTraceData>(delegate (DiskIOInitTraceData data)
{
diskStartStack[data.Irp] = stackSource.GetCallStack(data.CallStackIndex(), data);
});
eventSource.Kernel.AddCallbackForEvents<DiskIOTraceData>(delegate (DiskIOTraceData data)
{
StackSourceCallStackIndex stackIdx;
if (diskStartStack.TryGetValue(data.Irp, out stackIdx))
{
diskStartStack.Remove(data.Irp);
}
else
{
stackIdx = StackSourceCallStackIndex.Invalid;
}
var diskNumber = data.DiskNumber;
if (diskNumber >= lastDiskEndMSec.Count)
{
lastDiskEndMSec.Count = diskNumber + 1;
}
// Create a call stack that ends with 'Disk READ <fileName> (<fileDirectory>)'
var filePath = data.FileName;
if (filePath.Length == 0)
{
filePath = "UNKNOWN";
}
var nodeName = "I/O Size 0x" + data.TransferSize.ToString("x");
var nodeIndex = stackSource.Interner.FrameIntern(nodeName);
stackIdx = stackSource.Interner.CallStackIntern(nodeIndex, stackIdx);
nodeName = string.Format("Disk {0} DiskNum({1}) {2} ({3})", data.OpcodeName, diskNumber,
GetFileName(filePath), GetDirectoryName(filePath));
// The time it took actually using the disk is the smaller of either
// The elapsed time (because there were no other entries in the disk queue)
// OR the time since the last I/O completed (since that is when this one will start using the disk.
var elapsedMSec = data.ElapsedTimeMSec;
double serviceTimeMSec = elapsedMSec;
double durationSinceLastIOMSec = data.TimeStampRelativeMSec - lastDiskEndMSec[diskNumber];
lastDiskEndMSec[diskNumber] = elapsedMSec;
if (durationSinceLastIOMSec < serviceTimeMSec)
{
serviceTimeMSec = durationSinceLastIOMSec;
// There is queuing delay, indicate this by adding a sample that represents the queueing delay.
var queueStackIdx = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("Time in Disk Queue " + diskNumber), stackIdx);
sample.StackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern(nodeName), queueStackIdx);
sample.Metric = (float)(elapsedMSec - serviceTimeMSec);
sample.TimeRelativeMSec = data.TimeStampRelativeMSec - elapsedMSec;
stackSource.AddSample(sample);
}
stackIdx = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("Service Time Disk " + diskNumber), stackIdx);
sample.StackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern(nodeName), stackIdx);
sample.Metric = (float)serviceTimeMSec;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec - serviceTimeMSec;
stackSource.AddSample(sample);
});
eventSource.Process();
m_extraTopStats = " Metric is MSec";
}
else if (streamName == "Server Request CPU")
{
ServerRequestScenarioConfiguration scenarioConfiguration = new ServerRequestScenarioConfiguration(eventLog);
ComputingResourceStateMachine stateMachine = new ComputingResourceStateMachine(stackSource, scenarioConfiguration, ComputingResourceViewType.CPU);
stateMachine.Execute();
}
else if (streamName == "Server Request Thread Time")
{
ServerRequestScenarioConfiguration scenarioConfiguration = new ServerRequestScenarioConfiguration(eventLog);
ComputingResourceStateMachine stateMachine = new ComputingResourceStateMachine(stackSource, scenarioConfiguration, ComputingResourceViewType.ThreadTime);
stateMachine.Execute();
}
else if (streamName == "Server Request Managed Allocation")
{
ServerRequestScenarioConfiguration scenarioConfiguration = new ServerRequestScenarioConfiguration(eventLog);
ComputingResourceStateMachine stateMachine = new ComputingResourceStateMachine(stackSource, scenarioConfiguration, ComputingResourceViewType.Allocations);
stateMachine.Execute();
}
else if (streamName == "Execution Tracing")
{
TraceLogEventSource source = eventLog.Events.GetSource();
Action<TraceEvent> tracingCallback = delegate (TraceEvent data)
{
string assemblyName = (string)data.PayloadByName("assembly");
string typeName = (string)data.PayloadByName("type");
string methodName = (string)data.PayloadByName("method");
string frameName = string.Format("{0}!{1}.{2}", assemblyName, typeName, methodName);
StackSourceCallStackIndex callStackIndex = stackSource.GetCallStack(data.CallStackIndex(), data);
StackSourceFrameIndex nodeIndex = stackSource.Interner.FrameIntern(frameName);
callStackIndex = stackSource.Interner.CallStackIntern(nodeIndex, callStackIndex);
sample.Count = 1;
sample.Metric = 1;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
sample.StackIndex = callStackIndex;
stackSource.AddSample(sample);
};
source.Dynamic.AddCallbackForProviderEvent("MethodCallLogger", "MethodEntry", tracingCallback);
source.Process();
}
else if (streamName == "File Queries")
{
eventSource.Kernel.AddCallbackForEvents<FileIOInfoTraceData>(delegate (FileIOInfoTraceData data)
{
sample.Metric = 1;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
StackSourceCallStackIndex stackIdx = stackSource.GetCallStack(data.CallStackIndex(), data);
// Create a call stack that ends with 'Disk READ <fileName> (<fileDirectory>)'
var filePath = data.FileName;
if (filePath.Length == 0)
{
filePath = "UNKNOWN";
}
var nodeName = string.Format("File {0}: {1} ({2})", data.OpcodeName,
GetFileName(filePath), GetDirectoryName(filePath));
var nodeIndex = stackSource.Interner.FrameIntern(nodeName);
stackIdx = stackSource.Interner.CallStackIntern(nodeIndex, stackIdx);
sample.StackIndex = stackIdx;
stackSource.AddSample(sample);
});
eventSource.Process();
}
else if (streamName == "Directory Enumerations")
{
eventSource.Kernel.AddCallbackForEvents<FileIODirEnumTraceData>(delegate (FileIODirEnumTraceData data)
{
sample.Metric = 1;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
StackSourceCallStackIndex stackIdx = stackSource.GetCallStack(data.CallStackIndex(), data);
var directoryPath = data.DirectoryName;
if (directoryPath.Length == 0)
{
directoryPath = "UNKNOWN";
}
var nodeName = string.Format("Directory {0}: {1}", data.OpcodeName, directoryPath);
var nodeIndex = stackSource.Interner.FrameIntern(nodeName);
stackIdx = stackSource.Interner.CallStackIntern(nodeIndex, stackIdx);
sample.StackIndex = stackIdx;
stackSource.AddSample(sample);
});
eventSource.Process();
}
else if (streamName == "File I/O")
{
eventSource.Kernel.AddCallbackForEvents<FileIOReadWriteTraceData>(delegate (FileIOReadWriteTraceData data)
{
sample.Metric = (float)data.IoSize;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
StackSourceCallStackIndex stackIdx = stackSource.GetCallStack(data.CallStackIndex(), data);
// Create a call stack that ends with 'Disk READ <fileName> (<fileDirectory>)'
var filePath = data.FileName;
if (filePath.Length == 0)
{
filePath = "UNKNOWN";
}
var nodeName = string.Format("File {0}: {1} ({2})", data.OpcodeName,
GetFileName(filePath), GetDirectoryName(filePath));
var nodeIndex = stackSource.Interner.FrameIntern(nodeName);
stackIdx = stackSource.Interner.CallStackIntern(nodeIndex, stackIdx);
sample.StackIndex = stackIdx;
stackSource.AddSample(sample);
});
eventSource.Process();
}
else if (streamName == "Image Load")
{
var loadedImages = new Dictionary<Address, StackSourceCallStackIndex>(100);
Action<ImageLoadTraceData> imageLoadUnload = delegate (ImageLoadTraceData data)
{
// TODO this is not really correct, it assumes process IDs < 64K and images bases don't use lower bits
// but it is true
Address imageKey = data.ImageBase + (Address)data.ProcessID;
sample.Metric = data.ImageSize;
if (data.Opcode == TraceEventOpcode.Stop)
{
sample.StackIndex = StackSourceCallStackIndex.Invalid;
StackSourceCallStackIndex allocIdx;
if (loadedImages.TryGetValue(imageKey, out allocIdx))
{
sample.StackIndex = allocIdx;
}
sample.Metric = -sample.Metric;
}
else
{
// Create a call stack that ends with 'Load <fileName> (<fileDirectory>)'
var fileName = data.FileName;
var nodeName = "Image Load " + GetFileName(fileName) + " (" + GetDirectoryName(fileName) + ")";
var nodeIndex = stackSource.Interner.FrameIntern(nodeName);
sample.StackIndex = stackSource.Interner.CallStackIntern(nodeIndex, stackSource.GetCallStack(data.CallStackIndex(), data));
loadedImages[imageKey] = sample.StackIndex;
}
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
stackSource.AddSample(sample);
};
eventSource.Kernel.ImageLoad += imageLoadUnload;
eventSource.Kernel.ImageUnload += imageLoadUnload;
eventSource.Process();
}
else if (streamName == "Net Virtual Alloc")
{
var droppedEvents = 0;
var memStates = new MemState[eventLog.Processes.Count];
eventSource.Kernel.AddCallbackForEvents<VirtualAllocTraceData>(delegate (VirtualAllocTraceData data)
{
bool isAlloc = false;
if ((data.Flags & (
VirtualAllocTraceData.VirtualAllocFlags.MEM_COMMIT |
VirtualAllocTraceData.VirtualAllocFlags.MEM_DECOMMIT |
VirtualAllocTraceData.VirtualAllocFlags.MEM_RELEASE)) != 0)
{
// Can't use data.Process() because some of the virtual allocs occur in the process that started the
// process and occur before the process start event, which is what Process() uses to find it.
// TODO this code assumes that process launch is within 1 second and process IDs are not aggressively reused.
var processWhereMemoryAllocated = data.Log().Processes.GetProcess(data.ProcessID, data.TimeStampRelativeMSec + 1000);
if (processWhereMemoryAllocated == null)
{
droppedEvents++;
return;
}
var processIndex = processWhereMemoryAllocated.ProcessIndex;
var memState = memStates[(int)processIndex];
if (memState == null)
{
memState = memStates[(int)processIndex] = new MemState();
}
// Commit and decommit not both on together.
Debug.Assert((data.Flags &
(VirtualAllocTraceData.VirtualAllocFlags.MEM_COMMIT | VirtualAllocTraceData.VirtualAllocFlags.MEM_DECOMMIT)) !=
(VirtualAllocTraceData.VirtualAllocFlags.MEM_COMMIT | VirtualAllocTraceData.VirtualAllocFlags.MEM_DECOMMIT));
var stackIndex = StackSourceCallStackIndex.Invalid;
if ((data.Flags & VirtualAllocTraceData.VirtualAllocFlags.MEM_COMMIT) != 0)
{
isAlloc = true;
// Some of the early allocations are actually by the process that starts this process. Don't use their stacks
// But do count them.
var processIDAllocatingMemory = processWhereMemoryAllocated.ProcessID; // This is not right, but it sets the condition properly below
var thread = data.Thread();
if (thread != null)
{
processIDAllocatingMemory = thread.Process.ProcessID;
}
if (data.TimeStampRelativeMSec >= processWhereMemoryAllocated.StartTimeRelativeMsec && processIDAllocatingMemory == processWhereMemoryAllocated.ProcessID)
{
stackIndex = stackSource.GetCallStack(data.CallStackIndex(), data);
}
else
{
stackIndex = stackSource.GetCallStackForProcess(processWhereMemoryAllocated);
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("Allocated In Parent Process"), stackIndex);
}
}
memState.Update(data.BaseAddr, data.Length, isAlloc, stackIndex,
delegate (long metric, StackSourceCallStackIndex allocStack)
{
Debug.Assert(allocStack != StackSourceCallStackIndex.Invalid);
Debug.Assert(metric != 0); // They should trim this already.
sample.Metric = metric;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
sample.StackIndex = allocStack;
stackSource.AddSample(sample);
// Debug.WriteLine("Sample Proc {0,12} Time {1,8:f3} Length 0x{2:x} Metric 0x{3:x} Stack {4,8} Cum {5,8}", process.Name, sample.TimeRelativeMSec, data.Length, (int) sample.Metric, (int)sample.StackIndex, memState.TotalMem);
});
}
});
eventSource.Process();
if (droppedEvents != 0)
{
log.WriteLine("WARNING: {0} events were dropped because their process could not be determined.", droppedEvents);
}
}
else if (streamName == "Net Virtual Reserve")
{
// Mapped file (which includes image loads) logic.
var mappedImages = new Dictionary<Address, StackSourceCallStackIndex>(100);
Action<MapFileTraceData> mapUnmapFile = delegate (MapFileTraceData data)
{
sample.Metric = data.ViewSize;
// If it is a UnMapFile or MapFileDCStop event
if (data.Opcode == (TraceEventOpcode)38)
{
Debug.Assert(data.OpcodeName == "UnmapFile");
sample.StackIndex = StackSourceCallStackIndex.Invalid;
StackSourceCallStackIndex allocIdx;
if (mappedImages.TryGetValue(data.FileKey, out allocIdx))
{
sample.StackIndex = allocIdx;
mappedImages.Remove(data.FileKey);
}
sample.Metric = -sample.Metric;
}
else
{
Debug.Assert(data.OpcodeName == "MapFile" || data.OpcodeName == "MapFileDCStart");
// Create a call stack that ends with 'MapFile <fileName> (<fileDirectory>)'
var nodeName = "MapFile";
var fileName = data.FileName;
if (fileName.Length > 0)
{
nodeName = nodeName + " " + GetFileName(fileName) + " (" + GetDirectoryName(fileName) + ")";
}
var nodeIndex = stackSource.Interner.FrameIntern(nodeName);
sample.StackIndex = stackSource.Interner.CallStackIntern(nodeIndex, stackSource.GetCallStack(data.CallStackIndex(), data));
mappedImages[data.FileKey] = sample.StackIndex;
}
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
stackSource.AddSample(sample);
};
eventSource.Kernel.FileIOMapFile += mapUnmapFile;
eventSource.Kernel.FileIOUnmapFile += mapUnmapFile;
eventSource.Kernel.FileIOMapFileDCStart += mapUnmapFile;
// Virtual Alloc logic
var droppedEvents = 0;
var memStates = new MemState[eventLog.Processes.Count];
var virtualReserverFrame = stackSource.Interner.FrameIntern("VirtualReserve");
eventSource.Kernel.AddCallbackForEvents<VirtualAllocTraceData>(delegate (VirtualAllocTraceData data)
{
bool isAlloc = false;
if ((data.Flags & (
VirtualAllocTraceData.VirtualAllocFlags.MEM_COMMIT |
VirtualAllocTraceData.VirtualAllocFlags.MEM_RESERVE |
VirtualAllocTraceData.VirtualAllocFlags.MEM_RELEASE)) != 0)
{
// Can't use data.Process() because some of the virtual allocs occur in the process that started the
// process and occur before the process start event, which is what Process() uses to find it.
// TODO this code assumes that process launch is within 1 second and process IDs are not aggressively reused.
var processWhereMemoryAllocated = data.Log().Processes.GetProcess(data.ProcessID, data.TimeStampRelativeMSec + 1000);
if (processWhereMemoryAllocated == null)
{
droppedEvents++;
return;
}
var processIndex = processWhereMemoryAllocated.ProcessIndex;
var memState = memStates[(int)processIndex];
if (memState == null)
{
memState = memStates[(int)processIndex] = new MemState();
}
// Commit and decommit not both on together.
Debug.Assert((data.Flags &
(VirtualAllocTraceData.VirtualAllocFlags.MEM_COMMIT | VirtualAllocTraceData.VirtualAllocFlags.MEM_DECOMMIT)) !=
(VirtualAllocTraceData.VirtualAllocFlags.MEM_COMMIT | VirtualAllocTraceData.VirtualAllocFlags.MEM_DECOMMIT));
// Reserve and release not both on together.
Debug.Assert((data.Flags &
(VirtualAllocTraceData.VirtualAllocFlags.MEM_RESERVE | VirtualAllocTraceData.VirtualAllocFlags.MEM_RELEASE)) !=
(VirtualAllocTraceData.VirtualAllocFlags.MEM_RESERVE | VirtualAllocTraceData.VirtualAllocFlags.MEM_RELEASE));
// You allocate by committing or reserving. We have already filtered out decommits which have no effect on reservation.
// Thus the only memRelease is the only one that frees.
var stackIndex = StackSourceCallStackIndex.Invalid;
if ((data.Flags & (VirtualAllocTraceData.VirtualAllocFlags.MEM_COMMIT | VirtualAllocTraceData.VirtualAllocFlags.MEM_RESERVE)) != 0)
{
isAlloc = true;
// Some of the early allocations are actually by the process that starts this process. Don't use their stacks
// But do count them.
var processIDAllocatingMemory = processWhereMemoryAllocated.ProcessID; // This is not right, but it sets the condition properly below
var thread = data.Thread();
if (thread != null)
{
processIDAllocatingMemory = thread.Process.ProcessID;
}
if (data.TimeStampRelativeMSec >= processWhereMemoryAllocated.StartTimeRelativeMsec && processIDAllocatingMemory == processWhereMemoryAllocated.ProcessID)
{
stackIndex = stackSource.GetCallStack(data.CallStackIndex(), data);
}
else
{
stackIndex = stackSource.GetCallStackForProcess(processWhereMemoryAllocated);
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("Allocated In Parent Process"), stackIndex);
}
stackIndex = stackSource.Interner.CallStackIntern(virtualReserverFrame, stackIndex);
}
memState.Update(data.BaseAddr, data.Length, isAlloc, stackIndex,
delegate (long metric, StackSourceCallStackIndex allocStack)
{
Debug.Assert(allocStack != StackSourceCallStackIndex.Invalid);
Debug.Assert(metric != 0); // They should trim this already.
sample.Metric = metric;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
sample.StackIndex = allocStack;
stackSource.AddSample(sample);
// Debug.WriteLine("Sample Proc {0,12} Time {1,8:f3} Length 0x{2:x} Metric 0x{3:x} Stack {4,8} Cum {5,8}", process.Name, sample.TimeRelativeMSec, data.Length, (int) sample.Metric, (int)sample.StackIndex, memState.TotalMem);
});
}
});
eventSource.Process();
if (droppedEvents != 0)
{
log.WriteLine("WARNING: {0} events were dropped because their process could not be determined.", droppedEvents);
}
}
else if (streamName == "Net OS Heap Alloc")
{
// We index by heap address and then within the heap we remember the allocation stack
var heaps = new Dictionary<Address, Dictionary<Address, StackSourceSample>>();
var heapParser = new HeapTraceProviderTraceEventParser(eventSource);
Dictionary<Address, StackSourceSample> lastHeapAllocs = null;
// These three variables are used in the local function GetAllocationType defined below.
// and are used to look up type names associated with the native allocations.
var loadedModules = new Dictionary<TraceModuleFile, NativeSymbolModule>();
var allocationTypeNames = new Dictionary<CallStackIndex, string>();
var symReader = GetSymbolReader(log, SymbolReaderOptions.CacheOnly);
Address lastHeapHandle = 0;
float peakMetric = 0;
StackSourceSample peakSample = null;
float cumMetric = 0;
float sumCumMetric = 0;
int cumCount = 0;
heapParser.HeapTraceAlloc += delegate (HeapAllocTraceData data)
{
var allocs = lastHeapAllocs;
if (data.HeapHandle != lastHeapHandle)
allocs = GetHeap(data.HeapHandle, heaps, ref lastHeapAllocs, ref lastHeapHandle);
var callStackIndex = data.CallStackIndex();
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
sample.Metric = data.AllocSize;
sample.StackIndex = stackSource.GetCallStack(callStackIndex, data);
// Add the 'Alloc < XXX' pseudo node.
var nodeIndex = stackSource.Interner.FrameIntern(GetAllocName((uint)data.AllocSize));
sample.StackIndex = stackSource.Interner.CallStackIntern(nodeIndex, sample.StackIndex);
// Add the 'Type ALLOCATION_TYPE' if available.
string allocationType = GetAllocationType(callStackIndex);
if (allocationType != null)
{
nodeIndex = stackSource.Interner.FrameIntern("Type " + allocationType);
sample.StackIndex = stackSource.Interner.CallStackIntern(nodeIndex, sample.StackIndex);
}
var addedSample = stackSource.AddSample(sample);
allocs[data.AllocAddress] = addedSample;
cumMetric += sample.Metric;
if (cumMetric > peakMetric)
{
peakMetric = cumMetric;
peakSample = addedSample;
}
sumCumMetric += cumMetric;
cumCount++;
/*****************************************************************************/
// Performs a stack crawl to match the best typename to this allocation.
// Returns null if no typename was found.
// This updates loadedModules and allocationTypeNames. It reads symReader/eventLog.
string GetAllocationType(CallStackIndex csi)
{
if (!allocationTypeNames.TryGetValue(csi, out var typeName))
{
const int frameLimit = 25; // typically you need about 10 frames to get out of the OS functions
// to get to a frame that has type information. We'll search up this many frames
// before giving up on getting type information for the allocation.
int frameCount = 0;
for (var current = csi; current != CallStackIndex.Invalid && frameCount < frameLimit; current = eventLog.CallStacks.Caller(current), frameCount++)
{
var module = eventLog.CodeAddresses.ModuleFile(eventLog.CallStacks.CodeAddressIndex(current));
if (module == null)
continue;
if (!loadedModules.TryGetValue(module, out var symbolModule))
{
loadedModules[module] = symbolModule =
(module.PdbSignature != Guid.Empty
? symReader.FindSymbolFilePath(module.PdbName, module.PdbSignature, module.PdbAge, module.FilePath)
: symReader.FindSymbolFilePathForModule(module.FilePath)) is string pdb
? symReader.OpenNativeSymbolFile(pdb)
: null;
}
typeName = symbolModule?.GetTypeForHeapAllocationSite(
(uint)(eventLog.CodeAddresses.Address(eventLog.CallStacks.CodeAddressIndex(current)) - module.ImageBase)
) ?? typeName;
}
allocationTypeNames[csi] = typeName;
}
return typeName;
}
};
heapParser.HeapTraceFree += delegate (HeapFreeTraceData data)
{
var allocs = lastHeapAllocs;
if (data.HeapHandle != lastHeapHandle)
{
allocs = GetHeap(data.HeapHandle, heaps, ref lastHeapAllocs, ref lastHeapHandle);
}
StackSourceSample alloc;
if (allocs.TryGetValue(data.FreeAddress, out alloc))
{
cumMetric -= alloc.Metric;
sumCumMetric += cumMetric;
cumCount++;
allocs.Remove(data.FreeAddress);
Debug.Assert(alloc.Metric >= 0);
sample.Metric = -alloc.Metric;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
sample.StackIndex = alloc.StackIndex;
stackSource.AddSample(sample);
}
};
heapParser.HeapTraceReAlloc += delegate (HeapReallocTraceData data)
{
// Reallocs that actually move stuff will cause a Alloc and delete event
// so there is nothing to do for those events. But when the address is
// the same we need to resize
if (data.OldAllocAddress != data.NewAllocAddress)
{
return;
}
var allocs = lastHeapAllocs;
if (data.HeapHandle != lastHeapHandle)
{
allocs = GetHeap(data.HeapHandle, heaps, ref lastHeapAllocs, ref lastHeapHandle);
}
// This is a clone of the Free code
StackSourceSample alloc;
if (allocs.TryGetValue(data.OldAllocAddress, out alloc))
{
cumMetric -= alloc.Metric;
sumCumMetric += cumMetric;
cumCount++;
allocs.Remove(data.OldAllocAddress);
Debug.Assert(alloc.Metric == data.OldAllocSize);
sample.Metric = -alloc.Metric;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
sample.StackIndex = alloc.StackIndex;
stackSource.AddSample(sample);
}
// This is a clone of the Alloc code (sigh don't clone code)
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
sample.Metric = data.NewAllocSize;
var nodeIndex = stackSource.Interner.FrameIntern(GetAllocName((uint)data.NewAllocSize));
sample.StackIndex = stackSource.Interner.CallStackIntern(nodeIndex, stackSource.GetCallStack(data.CallStackIndex(), data));
var addedSample = stackSource.AddSample(sample);
allocs[data.NewAllocAddress] = addedSample;
cumMetric += sample.Metric;
if (cumMetric > peakMetric)
{
peakMetric = cumMetric;
peakSample = addedSample;
}
sumCumMetric += cumMetric;
cumCount++;
};
heapParser.HeapTraceDestroy += delegate (HeapTraceData data)
{
// Heap is dieing, kill all objects in it.
var allocs = lastHeapAllocs;
if (data.HeapHandle != lastHeapHandle)
{
allocs = GetHeap(data.HeapHandle, heaps, ref lastHeapAllocs, ref lastHeapHandle);
}
foreach (StackSourceSample alloc in allocs.Values)
{
// TODO this is a clone of the free code.
cumMetric -= alloc.Metric;
sumCumMetric += cumMetric;
cumCount++;
Debug.Assert(alloc.Metric >= 0);
sample.Metric = -alloc.Metric;
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
sample.StackIndex = alloc.StackIndex;
stackSource.AddSample(sample);
}
};
eventSource.Process();
var aveCumMetric = sumCumMetric / cumCount;
log.WriteLine("Peak Heap Size: {0:n3} MB Average Heap size: {1:n3} MB", peakMetric / 1000000.0F, aveCumMetric / 1000000.0F);
if (peakSample != null)
{
log.WriteLine("Peak happens at {0:n3} Msec into the trace.", peakSample.TimeRelativeMSec);
}
log.WriteLine("Trimming alloc-free pairs < 3 msec apart: Before we have {0:n1}K events now {1:n1}K events",
cumCount / 1000.0, stackSource.SampleIndexLimit / 1000.0);
return stackSource;
}
else if (streamName == "Server GC")
{
Microsoft.Diagnostics.Tracing.Analysis.TraceLoadedDotNetRuntimeExtensions.NeedLoadedDotNetRuntimes(eventSource);
Microsoft.Diagnostics.Tracing.Analysis.TraceProcessesExtensions.AddCallbackOnProcessStart(eventSource, proc =>
{
Microsoft.Diagnostics.Tracing.Analysis.TraceProcessesExtensions.SetSampleIntervalMSec(proc, (float)eventLog.SampleProfileInterval.TotalMilliseconds);
Microsoft.Diagnostics.Tracing.Analysis.TraceLoadedDotNetRuntimeExtensions.SetMutableTraceEventStackSource(proc, stackSource);
});
eventSource.Process();
return stackSource;
}
else if(streamName == "Anti-Malware Real-Time Scan")
{
RealtimeAntimalwareComputer computer = new RealtimeAntimalwareComputer(eventSource, stackSource);
computer.Execute();
return stackSource;
}
else
{
throw new Exception("Unknown stream " + streamName);
}
log.WriteLine("Produced {0:n3}K events", stackSource.SampleIndexLimit / 1000.0);
stackSource.DoneAddingSamples();
return stackSource;
}