public StackSource GetStackSource(TraceEvents events) { if (events == null) { ThrowHelper.ThrowArgumentNullException(nameof(events)); } TraceLog log = events.Log; var stackSource = new MutableTraceEventStackSource(log) { ShowUnknownAddresses = true }; var eventSource = events.GetSource(); var sample = new StackSourceSample(stackSource); var kernelTraceEventParser = new KernelTraceEventParser(eventSource); kernelTraceEventParser.PerfInfoSample += delegate(SampledProfileTraceData data) { sample.Metric = events.Log.SampleProfileInterval.Milliseconds; sample.TimeRelativeMSec = data.TimeStampRelativeMSec; sample.StackIndex = stackSource.GetCallStack(data.CallStackIndex(), data); stackSource.AddSample(sample); }; eventSource.Process(); return(stackSource); }
public StackSource GetStackSource(TraceEvents events) { if (events == null) { ThrowHelper.ThrowArgumentNullException(nameof(events)); } var stackSource = new MutableTraceEventStackSource(events.Log) { ShowUnknownAddresses = true }; var eventSource = events.GetSource(); var sample = new StackSourceSample(stackSource); var clrTraceEventParser = new ClrTraceEventParser(eventSource); clrTraceEventParser.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.Process(); return(stackSource); }
public static StackSource AnyStacks(this TraceLog eventLog, TraceProcess process = null, bool showUnknownAddresses = false, Predicate <TraceEvent> predicate = null) { var stackSource = new MutableTraceEventStackSource(eventLog); var sample = new StackSourceSample(stackSource); TraceEvents events = process == null?eventLog.Events.Filter(x => (predicate == null || predicate(x)) && x.ProcessID != 0) : process.EventsInProcess.Filter(x => predicate == null || predicate(x)); var eventSource = events.GetSource(); eventSource.AllEvents += data => { var callStackIdx = data.CallStackIndex(); StackSourceCallStackIndex stackIndex = callStackIdx != CallStackIndex.Invalid ? stackSource.GetCallStack(callStackIdx, data) : StackSourceCallStackIndex.Invalid; var eventNodeName = "Event " + data.ProviderName + "/" + data.EventName; stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern(eventNodeName), stackIndex); sample.StackIndex = stackIndex; sample.TimeRelativeMSec = data.TimeStampRelativeMSec; sample.Metric = 1; stackSource.AddSample(sample); }; eventSource.Process(); stackSource.DoneAddingSamples(); return(stackSource); }
/// <summary> /// Cache/split IP Module/Frame /// </summary> /// <param name="sample"></param> /// <param name="stackSource"></param> /// <returns></returns> private StackFrame GetIpStackFrame(StackSourceSample sample, ParallelLinuxPerfScriptStackSource stackSource) { if (sample.SampleIndex == StackSourceSampleIndex.Invalid) { return(new StackFrame(String.Empty, String.Empty)); } var si = sample.StackIndex; StackFrame sf; if (IpStackFrames.TryGetValue((int)si, out sf)) { return(sf); } else { var frame = stackSource.GetFrameIndex(si); var frameName = stackSource.Interner.GetFrameName(frame, false); var frameNameSplit = frameName.Split('!'); if (frameNameSplit.Length == 2) { sf = new StackFrame(frameNameSplit[0], frameNameSplit[1]); IpStackFrames.TryAdd((int)si, sf); return(sf); } return(null); } }
internal TraceEventStackSource(TraceLog log) { m_log = log; m_goodTopModuleIndex = ModuleFileIndex.Invalid; m_curSample = new StackSourceSample(this); m_curSample.Metric = log.SampleProfileInterval100ns / 10000.0F; }
public void Read(TextReader reader) { var sample = new StackSourceSample(this); sample.Metric = 1; for (; ;) { var line = reader.ReadLine(); if (line == null) { break; } if (StackForLine != null) { sample.StackIndex = StackForLine(Interner, line); } else { // Form the stack for this entry (trivial one element stack) var frameIndex = Interner.FrameIntern(line); sample.StackIndex = Interner.CallStackIntern(frameIndex, StackSourceCallStackIndex.Invalid); } if (sample.StackIndex != StackSourceCallStackIndex.Invalid) { AddSample(sample); } } Interner.DoneInterning(); }
internal TraceEventStackSource(TraceLog log) { m_log = log; m_goodTopModuleIndex = ModuleFileIndex.Invalid; m_curSample = new StackSourceSample(this); m_curSample.Metric = (float)log.SampleProfileInterval.TotalMilliseconds; }
/// <summary> /// Convert a StackSourceSample produced by a sub-source into one suitable for the aggregate source. /// </summary> /// <param name="input">The StackSourceSample to convert.</param> /// <param name="storage">A place to but the returned sampled (will become the return value).</param> /// <param name="sourceIdx">The index of the source from which the sample came.</param> /// <returns>The converted sample.</returns> /// <remarks> /// If ConvertSample is called again, all previous samples produced by ConvertSample may no longer be used. /// </remarks> private StackSourceSample ConvertSample(StackSourceSample input, StackSourceSample storage, int sourceIdx) { storage.Metric = input.Metric; // We normalize all the scenarios so that they start on their first sample time. var timeOrigin = m_firstSampleTime[sourceIdx]; if (timeOrigin < 0) { timeOrigin = m_firstSampleTime[sourceIdx] = input.TimeRelativeMSec; } storage.TimeRelativeMSec = input.TimeRelativeMSec - timeOrigin; storage.StackIndex = m_stackMap.IndexOf(sourceIdx, input.StackIndex); storage.Scenario = sourceIdx - 1; if (m_sampleMap != null) { storage.SampleIndex = m_sampleMap.IndexOf(sourceIdx - 1, input.SampleIndex); } else { storage.SampleIndex = StackSourceSampleIndex.Invalid; } return(storage); }
public StackSource GetStackSource(TraceEvents events) { if (events == null) { ThrowHelper.ThrowArgumentNullException(nameof(events)); } var stackSource = new MutableTraceEventStackSource(events.Log); var eventSource = events.GetSource(); var sample = new StackSourceSample(stackSource); var clrTraceEventParser = new ClrTraceEventParser(eventSource); clrTraceEventParser.GCAllocationTick += delegate(GCAllocationTickTraceData data) { var size = data.AllocationAmount64; sample.Metric = size; sample.Count = 1; sample.TimeRelativeMSec = data.TimeStampRelativeMSec; sample.StackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("Type " + data.TypeName), stackSource.GetCallStack(data.CallStackIndex(), data)); if (data.AllocationKind == GCAllocationKind.Large) { sample.StackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern("LargeObject"), sample.StackIndex); } stackSource.AddSample(sample); }; eventSource.Process(); return(stackSource); }
public StackSource GetStackSource(TraceEvents events) { if (events == null) { ThrowHelper.ThrowArgumentNullException(nameof(events)); } var stackSource = new MutableTraceEventStackSource(events.Log) { ShowUnknownAddresses = true }; var eventSource = events.GetSource(); var sample = new StackSourceSample(stackSource); var clrTraceEventParser = new ClrTraceEventParser(eventSource); clrTraceEventParser.ContentionStart += delegate(ContentionTraceData data) { sample.Metric = 1; sample.TimeRelativeMSec = data.TimeStampRelativeMSec; string nodeName = data.ContentionFlags == ContentionFlags.Native ? "Native Contention" : "Managed Contention"; var nodeIndex = stackSource.Interner.FrameIntern(nodeName); sample.StackIndex = stackSource.Interner.CallStackIntern(nodeIndex, stackSource.GetCallStack(data.CallStackIndex(), data)); stackSource.AddSample(sample); }; eventSource.Process(); return(stackSource); }
internal FileScanOperationCollection(TraceLog traceLog, MutableTraceEventStackSource stackSource) { _traceLog = traceLog; _stackSource = stackSource; _sample = new StackSourceSample(_stackSource); _engineThreadToScanMap = new FileScanOperation[_traceLog.Threads.Count]; }
/// <summary> /// Create a stack source from 'graph'. samplingRatio is the ratio of size of the graph to /// the size of the actual heap (if you only sampled part of it). Counts are scaled by the /// inverse of this so that the expected size of the graph works out. /// /// log is were to send diagnostic messages (can be null) /// /// countMultipliers is an array (indexed by type Index), that will be used to multiply the /// counts in 'graph' when generating the stack source (thus if Type T has count 5 and /// countMultipliers[T] = 10 then the stack source will return 50. This is used to scale /// sampled graphs. /// </summary> public MemoryGraphStackSource(Graph graph, TextWriter log, float[] countMultipliers = null) { m_asMemoryGraph = graph as MemoryGraph; m_graph = graph; m_log = log; m_nodeStorage = graph.AllocNodeStorage(); m_childStorage = graph.AllocNodeStorage(); m_typeStorage = graph.AllocTypeNodeStorage(); m_sampleStorage = new StackSourceSample(this); m_countMultipliers = countMultipliers; // We need to reduce the graph to a tree. Each node is assigned a unique 'parent' which is its // parent in a spanning tree of the graph. // The +1 is for orphan node support. m_parent = new NodeIndex[(int)graph.NodeIndexLimit + 1]; // If it is a memory stack source (it pretty much always is), figure out the maximum address. // We use addresses as 'time' for stacks so that the 'when' field in perfView is meaningful. MemoryGraph asMemoryGraph = graph as MemoryGraph; if (asMemoryGraph != null) { for (NodeIndex idx = 0; idx < asMemoryGraph.NodeIndexLimit; idx++) { Address endAddress = asMemoryGraph.GetAddress(idx) + (uint)asMemoryGraph.GetNode(idx, m_nodeStorage).Size; if (m_maxAddress < endAddress) { m_maxAddress = endAddress; } } } }
/// <summary> /// Initialize a new AggregateStackSource. /// </summary> /// <param name="sources">An IEnumerable of KeyValuePairs mapping source names to StackSources.</param> public AggregateStackSource(IEnumerable <KeyValuePair <string, StackSource> > sources) { m_sourceCount = sources.Count() + 1; // +1 for the pseudo-source. m_sourceNames = new string[m_sourceCount]; m_sources = new StackSource[m_sourceCount]; // We initialize this when we see the first sample m_firstSampleTime = new double[m_sourceCount]; for (int j = 0; j < m_firstSampleTime.Length; j++) { m_firstSampleTime[j] = double.NegativeInfinity; } // True if all sub-sources support retrieving samples by index. bool supportsSamples = true; // The time limit for this source. m_RelativeMSecLimit = 0.0f; int i = 1; // Unpack sources. foreach (var pair in sources) { var name = pair.Key; var source = pair.Value; m_sourceNames[i] = name; m_sources[i] = source; i++; m_RelativeMSecLimit = Math.Max(m_RelativeMSecLimit, source.SampleTimeRelativeMSecLimit); if (source.SampleIndexLimit == 0) { supportsSamples = false; } } // Set up pseudo-source. m_sources[0] = m_pseudo = new PseudoStackSource(m_sourceNames); // Set up our returned sample. m_sampleStorage = new StackSourceSample(this); // Set up index maps. m_stackMap = new IndexMap(m_sources.Select(s => s.CallStackIndexLimit)); m_frameMap = new IndexMap(m_sources.Select(s => s.CallFrameIndexLimit)); if (supportsSamples) { // The sampleMap has size (m_sourceCount - 1) because m_pseudo doesn't have samples. m_sampleMap = new IndexMap(m_sources.Skip(1).Select(s => s.SampleIndexLimit)); } else { m_sampleMap = null; } }
/// <summary> /// Override /// </summary> public void ParallelForEach(Action <StackSourceSample> callback, bool[] scenariosIncluded, int desiredParallelism = 0) { Parallel.For(0, ScenarioCount, delegate(int src, ParallelLoopState state) { var sampleStorage = new StackSourceSample(this); if (scenariosIncluded == null || scenariosIncluded[src]) { m_sources[src + 1].ForEach(sample => callback(ConvertSample(sample, sampleStorage, src + 1))); } }); }
private string GetProcess(StackSourceSample sample, ParallelLinuxPerfScriptStackSource stackSource) { if (sample.SampleIndex == StackSourceSampleIndex.Invalid) { return(String.Empty); } string process; SampleProcessDict.TryGetValue((long)sample.SampleIndex, out process); return(process); }
private static void PrintStack(IConsole console, int threadId, StackSourceSample stackSourceSample, StackSource stackSource) { console.Out.WriteLine($"Thread (0x{threadId:X}):"); var stackIndex = stackSourceSample.StackIndex; while (!stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), verboseName: false).StartsWith("Thread (")) { console.Out.WriteLine($" {stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), verboseName: false)}" .Replace("UNMANAGED_CODE_TIME", "[Native Frames]")); stackIndex = stackSource.GetCallerIndex(stackIndex); } console.Out.WriteLine(); }
public ComputingResourceStateMachine(MutableTraceEventStackSource outputStackSource, ScenarioConfiguration configuration, ComputingResourceViewType viewType) { m_OutputStackSource = outputStackSource; m_Sample = new StackSourceSample(outputStackSource); m_Configuration = configuration; m_ViewType = viewType; m_ThreadState = new ComputingResourceThreadState[configuration.TraceLog.Threads.Count]; for (int i = 0; i < m_ThreadState.Length; ++i) { m_ThreadState[i] = new ComputingResourceThreadState(i); } }
private static void PrintStack(string threadName, StackSourceSample stackSourceSample, StackSource stackSource) { Console.WriteLine($"Stack for {threadName}:"); var stackIndex = stackSourceSample.StackIndex; while (!stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), verboseName: false).StartsWith("Thread (")) { Console.WriteLine($" {stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), verboseName: false)}"); stackIndex = stackSource.GetCallerIndex(stackIndex); } Console.WriteLine(); }
private int GetThreadId(StackSourceSample sample, ParallelLinuxPerfScriptStackSource stackSource) { if (sample.SampleIndex == StackSourceSampleIndex.Invalid) { return(-1); } int tid; SampleThreadDict.TryGetValue((long)sample.SampleIndex, out tid); return(tid); }
/// <summary> /// After creating a MultableTraceEventStackSource, you add the samples you want and then call DoneAddingSamples /// From that point on you have a fine, read-only stacks source. /// </summary> public StackSourceSample AddSample(StackSourceSample sample) { var sampleCopy = new StackSourceSample(sample); sampleCopy.SampleIndex = (StackSourceSampleIndex)m_samples.Count; m_samples.Add(sampleCopy); if (sampleCopy.TimeRelMSec > m_sampleTimeRelMSecLimit) { m_sampleTimeRelMSecLimit = sampleCopy.TimeRelMSec; } return(sampleCopy); }
public static StackSource Exceptions(this TraceEvents events, TraceProcess process = null, Predicate <TraceEvent> predicate = null) { // optimization only if (process != null) { var start = Math.Max(events.StartTimeRelativeMSec, process.StartTimeRelativeMsec); var end = Math.Min(events.EndTimeRelativeMSec, process.EndTimeRelativeMsec); events = events.FilterByTime(start, end); events = events.Filter(x => (predicate == null || predicate(x)) && x.ProcessID == process.ProcessID); } else { events = events.Filter(x => (predicate == null || predicate(x)) && x.ProcessID != 0); // TODO: Is it really correc that x.ProcessID != 0 should be there? What if we want see these? } var eventSource = events.GetSource(); var stackSource = new MutableTraceEventStackSource(events.Log) { ShowUnknownAddresses = true }; var sample = new StackSourceSample(stackSource); eventSource.Clr.ExceptionStart += 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 += 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(); stackSource.DoneAddingSamples(); return(stackSource); }
/// <summary> /// Creates a new TraceEventStackSource given a list of events 'events' from a TraceLog /// </summary> /// <param name="events"></param> public TraceEventStackSource(TraceEvents events) { Debug.Assert(m_log == null); if (events != null) { m_log = events.Log; } m_goodTopModuleIndex = ModuleFileIndex.Invalid; m_curSample = new StackSourceSample(this); m_curSample.Metric = (float)events.Log.SampleProfileInterval.TotalMilliseconds; m_events = events; m_maxPseudoStack = m_log.CodeAddresses.Count * 2 + m_log.Threads.Count; // This really is a guess as to how many stacks we need. You can have as many as codeAddresses*threads }
internal GCPinnedObjectAnalyzer( string etlFilePath, TraceLog traceLog, MutableTraceEventStackSource stackSource, StackSourceSample sample, TextWriter log) { _HeapSnapshotFilePath = GetHeapSnapshotPath(etlFilePath); _TraceLog = traceLog; _StackSource = stackSource; _Sample = sample; _Log = log; }
public TraceEventStackSource(TraceEvents events) { Debug.Assert(m_log == null); if (events != null) { m_log = events.Log; } m_goodTopModuleIndex = ModuleFileIndex.Invalid; m_curSample = new StackSourceSample(this); m_curSample.Metric = events.Log.SampleProfileInterval100ns / 10000.0F; m_events = events; m_maxPseudoStack = m_log.CodeAddresses.MaxCodeAddressIndex; // This really is a guess as to how many stacks we need. }
/// <summary> /// Implements HistogramController interface /// </summary> public override void AddSample(Histogram histogram, StackSourceSample sample) { double bucketDuration = BucketDuration; double startSampleInBucket = sample.TimeRelativeMSec; int bucketIndex = (int)((sample.TimeRelativeMSec - Start) / bucketDuration); Debug.Assert(0 <= bucketIndex && bucketIndex <= BucketCount); if (Tree.ScalingPolicy == ScalingPolicyKind.TimeMetric) { // place the metric in each of the buckets it overlaps with. var nextBucketStart = GetStartTimeForBucket((HistogramCharacterIndex)(bucketIndex + 1)); // The Math.Abs is a bit of a hack. The problem is that that sample does not // represent time for a DIFF (because we negated it) but I rely on the fact // that we only negate it so I can undo it double endSample = sample.TimeRelativeMSec + Math.Abs(sample.Metric); var metricSign = sample.Metric > 0 ? 1 : -1; for (; ;) { if (BucketCount <= bucketIndex) { break; } var metricInBucket = Math.Min(nextBucketStart, endSample) - startSampleInBucket; histogram.AddMetric((float)metricInBucket * metricSign, bucketIndex); bucketIndex++; startSampleInBucket = nextBucketStart; nextBucketStart += bucketDuration; if (startSampleInBucket > endSample) { break; } } } else { // Put the sample in the right bucket. Note that because we allow inclusive times on the end // point we could get bucketIndex == Length, so put that sample in the last bucket. if (bucketIndex >= BucketCount) { bucketIndex = BucketCount - 1; } histogram.AddMetric(sample.Metric, bucketIndex); } }
public override StackSourceSample GetSampleByIndex(StackSourceSampleIndex sampleIndex) { if (m_sample == null) { m_sample = new StackSourceSample(this); } var allocs = m_gcHeap.Allocs; m_sample.SampleIndex = sampleIndex;; m_sample.StackIndex = (StackSourceCallStackIndex)(m_clrProfiler.StackIdLimit + (int)allocs[(int)sampleIndex].AllocId); m_sample.Metric = allocs[(int)sampleIndex].Size; m_sample.TimeRelativeMSec = allocs[(int)sampleIndex].MsecFromStart; return(m_sample); }
/// <summary> /// Given a Linux event gotten from the trace, make its corresponding sample for the stack source. /// </summary> public StackSourceSample CreateSampleFor(LinuxEvent linuxEvent, BlockedTimeAnalyzer blockedTimeAnalyzer) { IEnumerable <Frame> frames = linuxEvent.CallerStacks; StackSourceCallStackIndex stackIndex = this.currentStackIndex; var sample = new StackSourceSample(this); sample.TimeRelativeMSec = linuxEvent.TimeMSec; sample.Metric = 1; stackIndex = this.InternFrames(frames.GetEnumerator(), stackIndex, linuxEvent.ProcessID, linuxEvent.ThreadID, blockedTimeAnalyzer); sample.StackIndex = stackIndex; return(sample); }
/// <summary> /// Given a Linux event gotten from the trace, make its corresponding sample for the stack source. /// </summary> public StackSourceSample CreateSampleFor(LinuxEvent linuxEvent, BlockedTimeAnalyzer blockedTimeAnalyzer) { IEnumerable <Frame> frames = linuxEvent.CallerStacks; StackSourceCallStackIndex stackIndex = currentStackIndex; var sample = new StackSourceSample(this); sample.TimeRelativeMSec = linuxEvent.TimeMSec - StartTimeStampMSec; sample.Metric = (float)linuxEvent.Period; stackIndex = InternFrames(frames.GetEnumerator(), stackIndex, linuxEvent.ProcessID, linuxEvent.ThreadID, doThreadTime ? blockedTimeAnalyzer : null); sample.StackIndex = stackIndex; return(sample); }
/// <summary> /// Mark the thread as unblocked. /// </summary> public void LogBlockingStop( ComputingResourceStateMachine stateMachine, TraceThread thread, TraceEvent data) { if ((null == stateMachine) || (null == thread) || (null == data)) { return; } // Only add a sample if the thread was blocked. if (ThreadBlocked) { StackSourceSample sample = stateMachine.Sample; MutableTraceEventStackSource stackSource = stateMachine.StackSource; // Set the time and metric. sample.TimeRelativeMSec = this.BlockTimeStartRelativeMSec; sample.Metric = (float)(data.TimeStampRelativeMSec - this.BlockTimeStartRelativeMSec); /* Generate the stack trace. */ CallStackIndex traceLogCallStackIndex = data.CallStackIndex(); ScenarioThreadState scenarioThreadState = stateMachine.Configuration.ScenarioThreadState[ThreadIndex]; StackSourceCallStackIndex callStackIndex = scenarioThreadState.GetCallStackIndex(stateMachine.StackSource, thread, data); // Add the thread. StackSourceFrameIndex threadFrameIndex = stackSource.Interner.FrameIntern(thread.VerboseThreadName); callStackIndex = stackSource.Interner.CallStackIntern(threadFrameIndex, callStackIndex); // Add the full call stack. callStackIndex = stackSource.GetCallStack(traceLogCallStackIndex, callStackIndex, null); // Add Pseud-frames representing the kind of resource. StackSourceFrameIndex frameIndex = stackSource.Interner.FrameIntern("BLOCKED TIME"); callStackIndex = stackSource.Interner.CallStackIntern(frameIndex, callStackIndex); // Add the call stack to the sample. sample.StackIndex = callStackIndex; // Add the sample. stackSource.AddSample(sample); // Mark the thread as executing. BlockTimeStartRelativeMSec = -1; } }
public override StackSourceSample GetSampleByIndex(StackSourceSampleIndex sampleIndex) { if (m_sample == null) { m_sample = new StackSourceSample(this); } var stackId = (ProfilerStackTraceID)m_calls[(int)sampleIndex]; m_sample.SampleIndex = sampleIndex; // subtract 1 so 0 (clrprofiler Scentinal) becomes CallStackIndex Sentinal m_sample.StackIndex = (StackSourceCallStackIndex)(stackId - 1); var method = m_clrProfiler.Method(stackId); var stats = (MethodStats)method.UserData; m_sample.Metric = (float)(((double)method.size) / stats.count); return(m_sample); }
internal static IDictionary<int, GCProcess> Collect( TraceEventSource source, float sampleIntervalMSec, IDictionary<int, GCProcess> perProc = null, MutableTraceEventStackSource stackSource = null, Predicate<TraceEvent> filterFunc = null) { if (perProc == null) { perProc = new Dictionary<int, GCProcess>(); } source.Kernel.AddCallbackForEvents(delegate (ProcessCtrTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } var stats = perProc.GetOrCreate(data.ProcessID); stats.PeakVirtualMB = ((double)data.PeakVirtualSize) / 1000000.0; stats.PeakWorkingSetMB = ((double)data.PeakWorkingSetSize) / 1000000.0; }); Action<RuntimeInformationTraceData> doAtRuntimeStart = delegate (RuntimeInformationTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } var stats = perProc.GetOrCreate(data.ProcessID); stats.RuntimeVersion = "V " + data.VMMajorVersion.ToString() + "." + data.VMMinorVersion + "." + data.VMBuildNumber + "." + data.VMQfeNumber; stats.StartupFlags = data.StartupFlags; stats.Bitness = (data.RuntimeDllPath.ToLower().Contains("framework64") ? 64 : 32); if (stats.CommandLine == null) stats.CommandLine = data.CommandLine; }; // log at both startup and rundown //var clrRundown = new ClrRundownTraceEventParser(source); //clrRundown.RuntimeStart += doAtRuntimeStart; source.Clr.AddCallbackForEvent("Runtime/Start", doAtRuntimeStart); Action<ProcessTraceData> processStartCallback = data => { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } var stats = perProc.GetOrCreate(data.ProcessID); if (!string.IsNullOrEmpty(data.KernelImageFileName)) { // When we just have an EventSource (eg, the source was created by // ETWTraceEventSource), we don't necessarily have the process names // decoded - it all depends on whether we have seen a ProcessStartGroup // event or not. When the trace was taken after the process started we // know we didn't see such an event. string name = GetImageName(data.KernelImageFileName); // Strictly speaking, this is not really fixing it 'cause // it doesn't handle when a process with the same name starts // with the same pid. The chance of that happening is really small. if (stats.isDead == true) { stats = new GCProcess(); stats.Init(data); perProc[data.ProcessID] = stats; } } var commandLine = data.CommandLine; if (!string.IsNullOrEmpty(commandLine)) stats.CommandLine = commandLine; }; source.Kernel.AddCallbackForEvent("Process/Start", processStartCallback); source.Kernel.AddCallbackForEvent("Process/DCStart", processStartCallback); Action<ProcessTraceData> processEndCallback = delegate (ProcessTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } var stats = perProc.GetOrCreate(data.ProcessID); if (string.IsNullOrEmpty(stats.ProcessName)) { stats.ProcessName = GetImageName(data.KernelImageFileName); } if (data.OpcodeName == "Stop") { stats.isDead = true; } }; source.Kernel.AddCallbackForEvent("Process/Stop", processEndCallback); source.Kernel.AddCallbackForEvent("Process/DCStop", processEndCallback); #if (!CAP) CircularBuffer<ThreadWorkSpan> RecentThreadSwitches = new CircularBuffer<ThreadWorkSpan>(10000); source.Kernel.AddCallbackForEvent("Thread/CSwitch", delegate (CSwitchTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } RecentThreadSwitches.Add(new ThreadWorkSpan(data)); GCProcess stats; if (perProc.TryGetValue(data.ProcessID, out stats)) { stats.ThreadId2Priority[data.NewThreadID] = data.NewThreadPriority; if (stats.IsServerGCThread(data.ThreadID) > -1) { stats.ServerGcHeap2ThreadId[data.ProcessorNumber] = data.ThreadID; } } foreach (var gcProcess in perProc.Values) { GCEvent _event = gcProcess.GetCurrentGC(); // If we are in the middle of a GC. if (_event != null) { if ((_event.Type != GCType.BackgroundGC) && (gcProcess.isServerGCUsed == 1)) { _event.AddServerGcThreadSwitch(new ThreadWorkSpan(data)); } } } }); CircularBuffer<ThreadWorkSpan> RecentCpuSamples = new CircularBuffer<ThreadWorkSpan>(1000); StackSourceSample sample = new StackSourceSample(stackSource); source.Kernel.AddCallbackForEvent("PerfInfo/Sample", delegate (SampledProfileTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } RecentCpuSamples.Add(new ThreadWorkSpan(data)); GCProcess processWithGc = null; foreach (var gcProcess in perProc.Values) { GCEvent e = gcProcess.GetCurrentGC(); // If we are in the middle of a GC. if (e != null) { if ((e.Type != GCType.BackgroundGC) && (gcProcess.isServerGCUsed == 1)) { e.AddServerGcSample(new ThreadWorkSpan(data)); processWithGc = gcProcess; } } } if (stackSource != null && processWithGc != null) { GCEvent e = processWithGc.GetCurrentGC(); sample.Metric = 1; sample.TimeRelativeMSec = data.TimeStampRelativeMSec; var nodeName = string.Format("Server GCs #{0} in {1} (PID:{2})", e.GCNumber, processWithGc.ProcessName, processWithGc.ProcessID); var nodeIndex = stackSource.Interner.FrameIntern(nodeName); sample.StackIndex = stackSource.Interner.CallStackIntern(nodeIndex, stackSource.GetCallStack(data.CallStackIndex(), data)); stackSource.AddSample(sample); } GCProcess stats; if (perProc.TryGetValue(data.ProcessID, out stats)) { if (stats.IsServerGCThread(data.ThreadID) > -1) { stats.ServerGcHeap2ThreadId[data.ProcessorNumber] = data.ThreadID; } var cpuIncrement = sampleIntervalMSec; stats.ProcessCpuMSec += cpuIncrement; GCEvent _event = stats.GetCurrentGC(); // If we are in the middle of a GC. if (_event != null) { bool isThreadDoingGC = false; if ((_event.Type != GCType.BackgroundGC) && (stats.isServerGCUsed == 1)) { int heapIndex = stats.IsServerGCThread(data.ThreadID); if (heapIndex != -1) { _event.AddServerGCThreadTime(heapIndex, cpuIncrement); isThreadDoingGC = true; } } else if (data.ThreadID == stats.suspendThreadIDGC) { _event.GCCpuMSec += cpuIncrement; isThreadDoingGC = true; } else if (stats.IsBGCThread(data.ThreadID)) { Debug.Assert(stats.currentBGC != null); if (stats.currentBGC != null) stats.currentBGC.GCCpuMSec += cpuIncrement; isThreadDoingGC = true; } if (isThreadDoingGC) { stats.GCCpuMSec += cpuIncrement; } } } }); #endif source.Clr.AddCallbackForEvent("GC/SuspendEEStart", delegate (GCSuspendEETraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } var stats = perProc.GetOrCreate(data.ProcessID); switch (data.Reason) { case GCSuspendEEReason.SuspendForGC: stats.suspendThreadIDGC = data.ThreadID; break; case GCSuspendEEReason.SuspendForGCPrep: stats.suspendThreadIDBGC = data.ThreadID; break; default: stats.suspendThreadIDOther = data.ThreadID; break; } stats.suspendTimeRelativeMSec = data.TimeStampRelativeMSec; }); // In 2.0 we didn't have this event. source.Clr.AddCallbackForEvent("GC/SuspendEEStop", delegate (GCNoUserDataTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc.GetOrCreate(data.ProcessID); if ((stats.suspendThreadIDBGC > 0) && (stats.currentBGC != null)) { stats.currentBGC._SuspendDurationMSec += data.TimeStampRelativeMSec - stats.suspendTimeRelativeMSec; } stats.suspendEndTimeRelativeMSec = data.TimeStampRelativeMSec; }); source.Clr.AddCallbackForEvent("GC/RestartEEStop", delegate (GCNoUserDataTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc.GetOrCreate(data.ProcessID); GCEvent _event = stats.GetCurrentGC(); if (_event != null) { if (_event.Type == GCType.BackgroundGC) { stats.AddConcurrentPauseTime(_event, data.TimeStampRelativeMSec); } else { Debug.Assert(_event.PauseStartRelativeMSec != 0); // In 2.0 Concurrent GC, since we don't know the GC's type we can't tell if it's concurrent // or not. But we know we don't have nested GCs there so simply check if we have received the // GCStop event; if we have it means it's a blocking GC; otherwise it's a concurrent GC so // simply add the pause time to the GC without making the GC complete. if (_event.GCDurationMSec == 0) { Debug.Assert(_event.is20Event); _event.isConcurrentGC = true; stats.AddConcurrentPauseTime(_event, data.TimeStampRelativeMSec); } else { _event.PauseDurationMSec = data.TimeStampRelativeMSec - _event.PauseStartRelativeMSec; if (_event.HeapStats != null) { _event.isComplete = true; stats.lastCompletedGC = _event; } } } } // We don't change between a GC end and the pause resume. //Debug.Assert(stats.allocTickAtLastGC == stats.allocTickCurrentMB); // Mark that we are not in suspension anymore. stats.suspendTimeRelativeMSec = -1; stats.suspendThreadIDOther = -1; stats.suspendThreadIDBGC = -1; stats.suspendThreadIDGC = -1; }); source.Clr.AddCallbackForEvent("GC/AllocationTick", delegate (GCAllocationTickTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc.GetOrCreate(data.ProcessID); if (stats.HasAllocTickEvents == false) { stats.HasAllocTickEvents = true; } double valueMB = data.GetAllocAmount(ref stats.SeenBadAllocTick) / 1000000.0; if (data.AllocationKind == GCAllocationKind.Small) { // Would this do the right thing or is it always 0 for SOH since AllocationAmount // is an int??? stats.allocTickCurrentMB[0] += valueMB; } else { stats.allocTickCurrentMB[1] += valueMB; } }); source.Clr.AddCallbackForEvent("GC/Start", delegate (GCStartTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc.GetOrCreate(data.ProcessID); // We need to filter the scenario where we get 2 GCStart events for each GC. if ((stats.suspendThreadIDGC > 0) && !((stats.events.Count > 0) && stats.events[stats.events.Count - 1].GCNumber == data.Count)) { GCEvent _event = new GCEvent(stats); Debug.Assert(0 <= data.Depth && data.Depth <= 2); // _event.GCGeneration = data.Depth; Old style events only have this in the GCStop event. _event.Reason = data.Reason; _event.GCNumber = data.Count; _event.Type = data.Type; _event.Index = stats.events.Count; _event.is20Event = data.IsClassicProvider; bool isEphemeralGCAtBGCStart = false; // Detecting the ephemeral GC that happens at the beginning of a BGC. if (stats.events.Count > 0) { GCEvent lastGCEvent = stats.events[stats.events.Count - 1]; if ((lastGCEvent.Type == GCType.BackgroundGC) && (!lastGCEvent.isComplete) && (data.Type == GCType.NonConcurrentGC)) { isEphemeralGCAtBGCStart = true; } } Debug.Assert(stats.suspendTimeRelativeMSec != -1); if (isEphemeralGCAtBGCStart) { _event.PauseStartRelativeMSec = data.TimeStampRelativeMSec; } else { _event.PauseStartRelativeMSec = stats.suspendTimeRelativeMSec; if (stats.suspendEndTimeRelativeMSec == -1) { stats.suspendEndTimeRelativeMSec = data.TimeStampRelativeMSec; } _event._SuspendDurationMSec = stats.suspendEndTimeRelativeMSec - stats.suspendTimeRelativeMSec; } _event.GCStartRelativeMSec = data.TimeStampRelativeMSec; stats.events.Add(_event); if (_event.Type == GCType.BackgroundGC) { stats.currentBGC = _event; _event.ProcessCpuAtLastGC = stats.ProcessCpuAtLastGC; } #if (!CAP) if ((_event.Type != GCType.BackgroundGC) && (stats.isServerGCUsed == 1)) { _event.SetUpServerGcHistory(); foreach (var s in RecentCpuSamples) _event.AddServerGcSample(s); foreach (var s in RecentThreadSwitches) _event.AddServerGcThreadSwitch(s); } #endif } }); source.Clr.AddCallbackForEvent("GC/PinObjectAtGCTime", delegate (PinObjectAtGCTimeTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc.GetOrCreate(data.ProcessID); GCEvent _event = stats.GetCurrentGC(); if (_event != null) { if (!_event.PinnedObjects.ContainsKey(data.ObjectID)) { _event.PinnedObjects.Add(data.ObjectID, data.ObjectSize); } else { _event.duplicatedPinningReports++; } } }); // Some builds have this as a public event, and some have it as a private event. // All will move to the private event, so we'll remove this code afterwards. source.Clr.AddCallbackForEvent("GC/PinPlugAtGCTime", delegate (PinPlugAtGCTimeTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc.GetOrCreate(data.ProcessID); GCEvent _event = stats.GetCurrentGC(); if (_event != null) { // ObjectID is supposed to be an IntPtr. But "Address" is defined as UInt64 in // TraceEvent. _event.PinnedPlugs.Add(new GCEvent.PinnedPlug(data.PlugStart, data.PlugEnd)); } }); source.Clr.AddCallbackForEvent("GC/Mark", delegate (GCMarkWithTypeTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc.GetOrCreate(data.ProcessID); stats.AddServerGCThreadFromMark(data.ThreadID, data.HeapNum); GCEvent _event = stats.GetCurrentGC(); if (_event != null) { if (_event.PerHeapMarkTimes == null) { _event.PerHeapMarkTimes = new Dictionary<int, GCEvent.MarkInfo>(); } if (!_event.PerHeapMarkTimes.ContainsKey(data.HeapNum)) { _event.PerHeapMarkTimes.Add(data.HeapNum, new GCEvent.MarkInfo()); } _event.PerHeapMarkTimes[data.HeapNum].MarkTimes[(int)data.Type] = data.TimeStampRelativeMSec; _event.PerHeapMarkTimes[data.HeapNum].MarkPromoted[(int)data.Type] = data.Promoted; } }); source.Clr.AddCallbackForEvent("GC/GlobalHeapHistory", delegate (GCGlobalHeapHistoryTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc.GetOrCreate(data.ProcessID); stats.ProcessGlobalHistory(data); }); source.Clr.AddCallbackForEvent("GC/PerHeapHistory", delegate (GCPerHeapHistoryTraceData3 data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc.GetOrCreate(data.ProcessID); stats.ProcessPerHeapHistory(data); }); #if HAS_PRIVATE_GC_EVENTS // See if the source knows about the CLR Private provider, if it does, then var gcPrivate = new ClrPrivateTraceEventParser(source); gcPrivate.GCPinPlugAtGCTime += delegate (PinPlugAtGCTimeTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc[data]; GCEvent _event = stats.GetCurrentGC(); if (_event != null) { // ObjectID is supposed to be an IntPtr. But "Address" is defined as UInt64 in // TraceEvent. _event.PinnedPlugs.Add(new GCEvent.PinnedPlug(data.PlugStart, data.PlugEnd)); } }; // Sometimes at the end of a trace I see only some mark events are included in the trace and they // are not in order, so need to anticipate that scenario. gcPrivate.GCMarkStackRoots += delegate (GCMarkTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc[data]; stats.AddServerGCThreadFromMark(data.ThreadID, data.HeapNum); GCEvent _event = stats.GetCurrentGC(); if (_event != null) { if (_event.PerHeapMarkTimes == null) { _event.PerHeapMarkTimes = new Dictionary<int, GCEvent.MarkInfo>(); } if (!_event.PerHeapMarkTimes.ContainsKey(data.HeapNum)) { _event.PerHeapMarkTimes.Add(data.HeapNum, new GCEvent.MarkInfo(false)); } _event.PerHeapMarkTimes[data.HeapNum].MarkTimes[(int)MarkRootType.MarkStack] = data.TimeStampRelativeMSec; } }; gcPrivate.GCMarkFinalizeQueueRoots += delegate (GCMarkTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc[data]; GCEvent _event = stats.GetCurrentGC(); if (_event != null) { if ((_event.PerHeapMarkTimes != null) && _event.PerHeapMarkTimes.ContainsKey(data.HeapNum)) { _event.PerHeapMarkTimes[data.HeapNum].MarkTimes[(int)MarkRootType.MarkFQ] = data.TimeStampRelativeMSec; } } }; gcPrivate.GCMarkHandles += delegate (GCMarkTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc[data]; GCEvent _event = stats.GetCurrentGC(); if (_event != null) { if ((_event.PerHeapMarkTimes != null) && _event.PerHeapMarkTimes.ContainsKey(data.HeapNum)) { _event.PerHeapMarkTimes[data.HeapNum].MarkTimes[(int)MarkRootType.MarkHandles] = data.TimeStampRelativeMSec; } } }; gcPrivate.GCMarkCards += delegate (GCMarkTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc[data]; GCEvent _event = stats.GetCurrentGC(); if (_event != null) { if ((_event.PerHeapMarkTimes != null) && _event.PerHeapMarkTimes.ContainsKey(data.HeapNum)) { _event.PerHeapMarkTimes[data.HeapNum].MarkTimes[(int)MarkRootType.MarkOlder] = data.TimeStampRelativeMSec; } } }; gcPrivate.GCGlobalHeapHistory += delegate (GCGlobalHeapHistoryTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc[data]; stats.ProcessGlobalHistory(data); }; gcPrivate.GCPerHeapHistory += delegate (GCPerHeapHistoryTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc[data]; stats.ProcessPerHeapHistory(data); }; gcPrivate.GCBGCStart += delegate (GCNoUserDataTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc[data]; if (stats.currentBGC != null) { if (stats.backgroundGCThreads == null) { stats.backgroundGCThreads = new Dictionary<int, object>(16); } stats.backgroundGCThreads[data.ThreadID] = null; } }; #endif source.Clr.AddCallbackForEvent("GC/Stop", delegate (GCEndTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc.GetOrCreate(data.ProcessID); GCEvent _event = stats.GetCurrentGC(); if (_event != null) { _event.GCDurationMSec = data.TimeStampRelativeMSec - _event.GCStartRelativeMSec; _event.GCGeneration = data.Depth; _event.GCEnd(); } }); source.Clr.AddCallbackForEvent("GC/HeapStats", delegate (GCHeapStatsTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc.GetOrCreate(data.ProcessID); GCEvent _event = stats.GetCurrentGC(); var sizeAfterMB = (data.GenerationSize1 + data.GenerationSize2 + data.GenerationSize3) / 1000000.0; if (_event != null) { _event.HeapStats = (GCHeapStatsTraceData)data.Clone(); if (_event.Type == GCType.BackgroundGC) { _event.ProcessCpuMSec = stats.ProcessCpuMSec - _event.ProcessCpuAtLastGC; _event.DurationSinceLastRestartMSec = data.TimeStampRelativeMSec - stats.lastRestartEndTimeRelativeMSec; } else { _event.ProcessCpuMSec = stats.ProcessCpuMSec - stats.ProcessCpuAtLastGC; _event.DurationSinceLastRestartMSec = _event.PauseStartRelativeMSec - stats.lastRestartEndTimeRelativeMSec; } if (stats.HasAllocTickEvents) { _event.HasAllocTickEvents = true; _event.AllocedSinceLastGCBasedOnAllocTickMB[0] = stats.allocTickCurrentMB[0] - stats.allocTickAtLastGC[0]; _event.AllocedSinceLastGCBasedOnAllocTickMB[1] = stats.allocTickCurrentMB[1] - stats.allocTickAtLastGC[1]; } // This is where a background GC ends. if ((_event.Type == GCType.BackgroundGC) && (stats.currentBGC != null)) { stats.currentBGC.isComplete = true; stats.lastCompletedGC = stats.currentBGC; stats.currentBGC = null; } if (_event.isConcurrentGC) { Debug.Assert(_event.is20Event); _event.isComplete = true; stats.lastCompletedGC = _event; } } stats.ProcessCpuAtLastGC = stats.ProcessCpuMSec; stats.allocTickAtLastGC[0] = stats.allocTickCurrentMB[0]; stats.allocTickAtLastGC[1] = stats.allocTickCurrentMB[1]; stats.lastRestartEndTimeRelativeMSec = data.TimeStampRelativeMSec; }); source.Clr.AddCallbackForEvent("GC/TerminateConcurrentThread", delegate (GCTerminateConcurrentThreadTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc.GetOrCreate(data.ProcessID); if (stats.backgroundGCThreads != null) { stats.backgroundGCThreads = null; } }); #if HAS_PRIVATE_GC_EVENTS gcPrivate.GCBGCAllocWaitStart += delegate (BGCAllocWaitTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc[data]; Debug.Assert(stats.currentBGC != null); if (stats.currentBGC != null) { stats.currentBGC.AddLOHWaitThreadInfo(data.ThreadID, data.TimeStampRelativeMSec, data.Reason, true); } }; gcPrivate.GCBGCAllocWaitStop += delegate (BGCAllocWaitTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess stats = perProc[data]; GCEvent _event = stats.GetLastBGC(); if (_event != null) { _event.AddLOHWaitThreadInfo(data.ThreadID, data.TimeStampRelativeMSec, data.Reason, false); } }; gcPrivate.GCJoin += delegate (GCJoinTraceData data) { if (filterFunc != null && !filterFunc.Invoke(data)) { return; } GCProcess gcProcess = perProc[data]; GCEvent _event = gcProcess.GetCurrentGC(); if (_event != null) { _event.AddGcJoin(data); } }; source.Process(); #endif return perProc; }