diff --git a/src/PerfView/StackViewer/StackWindow.xaml.cs b/src/PerfView/StackViewer/StackWindow.xaml.cs
index 103c5b908..a403ea215 100644
--- a/src/PerfView/StackViewer/StackWindow.xaml.cs
+++ b/src/PerfView/StackViewer/StackWindow.xaml.cs
@@ -898,7 +898,7 @@ internal void DoSave(object sender, RoutedEventArgs e)
saveDialog.InitialDirectory = Path.GetDirectoryName(DataSource.FilePath);
saveDialog.Title = "File to save view";
saveDialog.DefaultExt = ".perfView.xml.zip";
- saveDialog.Filter = "PerfView view file|*.perfView.xml.zip|Comma Separated Value|*.csv|Speed Scope Format|*.speedscope.json|All Files|*.*";
+ saveDialog.Filter = "PerfView view file|*.perfView.xml.zip|Comma Separated Value|*.csv|Speed Scope|*.speedscope.json|Chromium Trace Event|*.chromium.json|All Files|*.*";
saveDialog.AddExtension = true;
saveDialog.OverwritePrompt = true;
@@ -948,6 +948,10 @@ internal void DoSave(object sender, RoutedEventArgs e)
{
SpeedScopeStackSourceWriter.WriteStackViewAsJson(CallTree.StackSource, m_fileName);
}
+ else if (m_fileName.EndsWith(".chromium.json", StringComparison.OrdinalIgnoreCase))
+ {
+ ChromiumStackSourceWriter.WriteStackViewAsJson(CallTree.StackSource, m_fileName, false);
+ }
else
{
if (m_fileName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
diff --git a/src/TraceEvent/Stacks/ChromiumStackSourceWriter.cs b/src/TraceEvent/Stacks/ChromiumStackSourceWriter.cs
new file mode 100644
index 000000000..a907c3fed
--- /dev/null
+++ b/src/TraceEvent/Stacks/ChromiumStackSourceWriter.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using static Microsoft.Diagnostics.Tracing.Stacks.StackSourceWriterHelper;
+
+namespace Microsoft.Diagnostics.Tracing.Stacks.Formats
+{
+ public class ChromiumStackSourceWriter
+ {
+ ///
+ /// exports provided StackSource to a Chromium Trace File format
+ /// schema: https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/
+ ///
+ public static void WriteStackViewAsJson(StackSource source, string filePath, bool compress)
+ {
+ if (compress && !filePath.EndsWith(".gz", StringComparison.OrdinalIgnoreCase))
+ filePath += ".gz";
+
+ if (File.Exists(filePath))
+ File.Delete(filePath);
+
+ using (var writeStream = compress ? (Stream)new GZipStream(File.Create(filePath), CompressionMode.Compress, leaveOpen: false) : File.Create(filePath))
+ using (var streamWriter = new StreamWriter(writeStream))
+ {
+ Export(source, streamWriter, Path.GetFileNameWithoutExtension(filePath));
+ }
+ }
+
+ #region private
+ private static void Export(StackSource source, TextWriter writer, string name)
+ {
+ var samplesPerThread = GetSortedSamplesPerThread(source);
+
+ var exportedFrameNameToExportedFrameId = new Dictionary();
+ var exportedFrameIdToFrameTuple = new Dictionary();
+ var profileEventsPerThread = new Dictionary>();
+
+ foreach (var pair in samplesPerThread)
+ {
+ var frameIdToSamples = WalkTheStackAndExpandSamples(source, pair.Value, exportedFrameNameToExportedFrameId, exportedFrameIdToFrameTuple);
+
+ var sortedProfileEvents = GetAggregatedOrderedProfileEvents(frameIdToSamples);
+
+ profileEventsPerThread.Add(pair.Key, sortedProfileEvents);
+ };
+
+ WriteToFile(exportedFrameIdToFrameTuple, profileEventsPerThread, writer, name);
+ }
+
+ private static void WriteToFile(Dictionary frameIdToFrameTuple,
+ IReadOnlyDictionary> sortedProfileEventsPerThread,
+ TextWriter writer, string name)
+ {
+ writer.Write("{");
+ writer.Write("\"traceEvents\": [");
+ bool isFirst = true;
+ foreach (var perThread in sortedProfileEventsPerThread.OrderBy(pair => pair.Value.First().RelativeTime))
+ {
+ foreach (var profileEvent in perThread.Value)
+ {
+ if (!isFirst)
+ writer.Write(", ");
+ else
+ isFirst = false;
+
+ writer.Write("{");
+ writer.Write($"\"name\": \"{frameIdToFrameTuple[profileEvent.FrameId].Name}\", ");
+ writer.Write($"\"cat\": \"sampleEvent\", ");
+ writer.Write($"\"ph\": \"{(profileEvent.Type == ProfileEventType.Open ? "B" : "E")}\", ");
+ writer.Write($"\"ts\": {profileEvent.RelativeTime.ToString("R", CultureInfo.InvariantCulture)}, ");
+ writer.Write($"\"pid\": {perThread.Key.ProcessId}, ");
+ writer.Write($"\"tid\": {perThread.Key.Id}, ");
+ writer.Write($"\"sf\": {profileEvent.FrameId}");
+ writer.Write("}");
+ }
+ }
+ writer.Write("], ");
+ writer.Write("\"displayTimeUnit\": \"ms\", ");
+ writer.Write("\"stackFrames\": {");
+ isFirst = true;
+ foreach (var frame in frameIdToFrameTuple)
+ {
+ if (!isFirst)
+ writer.Write(", ");
+ else
+ isFirst = false;
+
+ var frameId = frame.Key;
+ var frameInfo = frame.Value;
+ writer.Write($"\"{frameId}\": {{");
+ writer.Write($"\"name\": \"{frameInfo.Name}\", ");
+ writer.Write($"\"category\": \"{frameInfo.Category}\"");
+ if (frameInfo.ParentId != -1)
+ writer.Write($", \"parent\": {frameInfo.ParentId}");
+ writer.Write("}");
+ }
+ writer.Write("}, ");
+ writer.Write($"\"otherData\": {{ \"name\": \"{name}\" }}");
+ writer.Write("}");
+ }
+ #endregion private
+ }
+}
\ No newline at end of file
diff --git a/src/TraceEvent/Stacks/SpeedScopeStackSourceWriter.cs b/src/TraceEvent/Stacks/SpeedScopeStackSourceWriter.cs
index 7f34b18f7..0a94d3507 100644
--- a/src/TraceEvent/Stacks/SpeedScopeStackSourceWriter.cs
+++ b/src/TraceEvent/Stacks/SpeedScopeStackSourceWriter.cs
@@ -1,9 +1,10 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using static Microsoft.Diagnostics.Tracing.Stacks.StackSourceWriterHelper;
+
namespace Microsoft.Diagnostics.Tracing.Stacks.Formats
{
public static class SpeedScopeStackSourceWriter
@@ -22,20 +23,21 @@ public static void WriteStackViewAsJson(StackSource source, string filePath)
}
#region private
- internal static void Export(StackSource source, TextWriter writer, string name)
+ private static void Export(StackSource source, TextWriter writer, string name)
{
var samplesPerThread = GetSortedSamplesPerThread(source);
var exportedFrameNameToExportedFrameId = new Dictionary();
+ var exportedFrameIdToFrameTuple = new Dictionary();
var profileEventsPerThread = new Dictionary>();
foreach(var pair in samplesPerThread)
{
- var frameIdToSamples = WalkTheStackAndExpandSamples(source, pair.Value, exportedFrameNameToExportedFrameId);
+ var frameIdToSamples = WalkTheStackAndExpandSamples(source, pair.Value, exportedFrameNameToExportedFrameId, exportedFrameIdToFrameTuple);
var sortedProfileEvents = GetAggregatedOrderedProfileEvents(frameIdToSamples);
- profileEventsPerThread.Add(pair.Key, sortedProfileEvents);
+ profileEventsPerThread.Add(pair.Key.Name, sortedProfileEvents);
};
var orderedFrameNames = exportedFrameNameToExportedFrameId.OrderBy(pair => pair.Value).Select(pair => pair.Key).ToArray();
@@ -43,201 +45,10 @@ internal static void Export(StackSource source, TextWriter writer, string name)
WriteToFile(profileEventsPerThread, orderedFrameNames, writer, name);
}
- ///
- /// we want to identify the thread for every sample to prevent from
- /// overlaping of samples for the concurrent code so we group the samples by Threads
- /// this method also sorts the samples by relative time (ascending)
- ///
- internal static IReadOnlyDictionary> GetSortedSamplesPerThread(StackSource stackSource)
- {
- var samplesPerThread = new Dictionary>();
-
- stackSource.ForEach(sample =>
- {
- var stackIndex = sample.StackIndex;
-
- while(stackIndex != StackSourceCallStackIndex.Invalid)
- {
- var frameName = stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false);
-
- // we walk the stack up until we find the Thread name
- if (!frameName.StartsWith("Thread ("))
- {
- stackIndex = stackSource.GetCallerIndex(stackIndex);
- continue;
- }
-
- if (!samplesPerThread.TryGetValue(frameName, out var samples))
- samplesPerThread[frameName] = samples = new List();
-
- samples.Add(new Sample(sample.StackIndex, -1, sample.TimeRelativeMSec, sample.Metric, -1));
-
- return;
- }
-
- throw new InvalidOperationException("Sample with no Thread assigned!");
- });
-
- foreach (var samples in samplesPerThread.Values)
- {
- // all samples in the StackSource should be sorted, but we want to ensure it
- samples.Sort(CompareSamples);
- }
-
- return samplesPerThread;
- }
-
- ///
- /// all the samples that we have are leafs (last sample in the call stack)
- /// this method expands those samples to full information
- /// it walks the stack up to the begining and adds a sample for every method on the stack
- /// it's required to build full information
- ///
- internal static IReadOnlyDictionary> WalkTheStackAndExpandSamples(StackSource stackSource, IEnumerable leafs,
- Dictionary exportedFrameNameToExportedFrameId)
- {
- var frameIdToSamples = new Dictionary>();
-
- // we use stack here bacause we want a certain order: from the root to the leaf
- var stackIndexesToHandle = new Stack();
-
- foreach (var leafSample in leafs)
- {
- // walk the stack first
- var stackIndex = leafSample.StackIndex;
- while (stackIndex != StackSourceCallStackIndex.Invalid)
- {
- stackIndexesToHandle.Push(stackIndex);
-
- stackIndex = stackSource.GetCallerIndex(stackIndex);
- }
-
- // add sample for every method on the stack
- int depth = -1;
- int callerFrameId = -1;
- while (stackIndexesToHandle.Count > 0)
- {
- stackIndex = stackIndexesToHandle.Pop();
- depth++;
-
- var frameIndex = stackSource.GetFrameIndex(stackIndex);
- if (frameIndex == StackSourceFrameIndex.Broken || frameIndex == StackSourceFrameIndex.Invalid)
- continue;
-
- var frameName = stackSource.GetFrameName(frameIndex, false);
- if (string.IsNullOrEmpty(frameName))
- continue;
-
- if (!exportedFrameNameToExportedFrameId.TryGetValue(frameName, out int exportedFrameId))
- exportedFrameNameToExportedFrameId.Add(frameName, exportedFrameId = exportedFrameNameToExportedFrameId.Count);
-
- if (!frameIdToSamples.TryGetValue(exportedFrameId, out var samples))
- frameIdToSamples.Add(exportedFrameId, samples = new List());
-
- // the time and metric are the same as for the leaf sample
- // the difference is stack index (not really used from here), caller frame id and depth (used for sorting the exported data)
- samples.Add(new Sample(stackIndex, callerFrameId, leafSample.RelativeTime, leafSample.Metric, depth));
-
- callerFrameId = exportedFrameId;
- }
- }
-
- return frameIdToSamples;
- }
-
- ///
- /// this method aggregates all the singular samples to continuous events
- /// example: samples for Main taken at time 0.1 0.2 0.3 0.4 0.5
- /// are gonna be translated to Main start at 0.1 stop at 0.5
- ///
- internal static IReadOnlyList GetAggregatedOrderedProfileEvents(IReadOnlyDictionary> frameIdToSamples)
- {
- List profileEvents = new List();
-
- foreach (var samplesInfo in frameIdToSamples)
- {
- var frameId = samplesInfo.Key;
- var samples = samplesInfo.Value;
-
- // this should not be required, but I prefer to be sure that the data is sorted
- samples.Sort(CompareSamples);
-
- Sample openSample = samples[0]; // samples are never empty
- for (int i = 1; i < samples.Count; i++)
- {
- if (AreNotContinuous(samples[i - 1], samples[i]))
- {
- AddEvents(profileEvents, openSample, samples[i - 1], frameId);
-
- openSample = samples[i];
- }
- }
-
- // we need to handle the last (or the only one) profile event
- AddEvents(profileEvents, openSample, samples[samples.Count - 1], frameId);
- }
-
- // MUST HAVE!!! the tool expects the profile events in certain order!!
- return OrderForExport(profileEvents).ToArray();
- }
-
- ///
- /// this method checks if both samples do NOT belong to the same profile event
- ///
- private static bool AreNotContinuous(Sample left, Sample right)
- {
- if (left.Depth != right.Depth)
- return true;
- if (left.CallerFrameId != right.CallerFrameId)
- return true;
-
- // 1.2 is a magic number based on some experiments ;)
- return left.RelativeTime + (left.Metric * 1.2) < right.RelativeTime;
- }
-
- ///
- /// this method adds a new profile event for provided samples
- /// it also make sure that a profile event does not open and close at the same time (would be ignored by SpeedScope)
- ///
- private static void AddEvents(List profileEvents, Sample openSample, Sample closeSample, int frameId)
- {
- if (openSample.Depth != closeSample.Depth)
- throw new ArgumentException("Invalid arguments, both samples must be of the same depth");
- if (openSample.RelativeTime == closeSample.RelativeTime + closeSample.Metric)
- throw new ArgumentException("Invalid samples, two samples can not happen at the same time.");
-
- profileEvents.Add(new ProfileEvent(ProfileEventType.Open, frameId, openSample.RelativeTime, openSample.Depth));
- profileEvents.Add(new ProfileEvent(ProfileEventType.Close, frameId, closeSample.RelativeTime + closeSample.Metric, closeSample.Depth));
- }
-
- ///
- /// this method orders the profile events in the order required by SpeedScope
- /// it's just the order of drawing the time graph
- ///
- internal static IEnumerable OrderForExport(IEnumerable profiles)
- {
- return profiles
- .GroupBy(@event => @event.RelativeTime)
- .OrderBy(group => group.Key)
- .SelectMany(group =>
- {
- // MakeSureSamplesDoNotOverlap guarantees that samples do NOT overlap
- // AddEvents guarantees us that there is no event which starts and end at the same time
- // so we don't need to worry about this edge case here
-
- // first of all, we need to emit close events, descending by depth (tool format requires that)
- var closingDescendingByDepth = group.Where(@event => @event.Type == ProfileEventType.Close).OrderByDescending(@event => @event.Depth);
- // then we can open new events, ascending by depth (tool format requires that)
- var openingAscendingByDepth = group.Where(@event => @event.Type == ProfileEventType.Open).OrderBy(@event => @event.Depth);
-
- return closingDescendingByDepth.Concat(openingAscendingByDepth);
- });
- }
-
///
/// writes pre-calculated data to SpeedScope format
///
- internal static void WriteToFile(IReadOnlyDictionary> sortedProfileEventsPerThread,
+ private static void WriteToFile(IReadOnlyDictionary> sortedProfileEventsPerThread,
IReadOnlyList orderedFrameNames, TextWriter writer, string name)
{
writer.Write("{");
@@ -293,64 +104,6 @@ internal static void WriteToFile(IReadOnlyDictionary RelativeTime.ToString(CultureInfo.InvariantCulture);
-
- #region private
- internal StackSourceCallStackIndex StackIndex { get; }
- internal int CallerFrameId { get; }
- internal double RelativeTime { get; }
- internal double Metric { get; }
- internal int Depth { get; }
- #endregion private
- }
-
- internal enum ProfileEventType : byte
- {
- Open = 0, Close = 1
- }
-
- internal struct ProfileEvent
- {
- public ProfileEvent(ProfileEventType type, int frameId, double relativeTime, int depth)
- {
- Type = type;
- FrameId = frameId;
- RelativeTime = relativeTime;
- Depth = depth;
- }
-
- public override string ToString() => $"{RelativeTime.ToString(CultureInfo.InvariantCulture)} {Type} {FrameId}";
-
- #region private
- internal ProfileEventType Type { get; }
- internal int FrameId { get; }
- internal double RelativeTime { get; }
- internal int Depth { get; }
- #endregion private
- }
#endregion private
}
}
diff --git a/src/TraceEvent/Stacks/StackSourceWriterHelper.cs b/src/TraceEvent/Stacks/StackSourceWriterHelper.cs
new file mode 100644
index 000000000..0970e9c9a
--- /dev/null
+++ b/src/TraceEvent/Stacks/StackSourceWriterHelper.cs
@@ -0,0 +1,330 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace Microsoft.Diagnostics.Tracing.Stacks
+{
+ internal static class StackSourceWriterHelper
+ {
+ ///
+ /// we want to identify the thread for every sample to prevent from
+ /// overlaping of samples for the concurrent code so we group the samples by Threads
+ /// this method also sorts the samples by relative time (ascending)
+ ///
+ internal static IReadOnlyDictionary> GetSortedSamplesPerThread(StackSource stackSource)
+ {
+ var samplesPerThread = new Dictionary>();
+
+ stackSource.ForEach(sample =>
+ {
+ var stackIndex = sample.StackIndex;
+
+ while (stackIndex != StackSourceCallStackIndex.Invalid)
+ {
+ var frameName = stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false);
+
+ // we walk the stack up until we find the Thread name
+ if (!frameName.StartsWith("Thread ("))
+ {
+ stackIndex = stackSource.GetCallerIndex(stackIndex);
+ continue;
+ }
+
+ // we assume that the next caller is always process
+ var processStackIndex = stackSource.GetCallerIndex(stackIndex);
+ var processFrameName = processStackIndex == StackSourceCallStackIndex.Invalid
+ ? "Unknown"
+ : stackSource.GetFrameName(stackSource.GetFrameIndex(processStackIndex), false);
+
+ var threadInfo = new ThreadInfo(frameName, processFrameName);
+
+ if (!samplesPerThread.TryGetValue(threadInfo, out var samples))
+ samplesPerThread[threadInfo] = samples = new List();
+
+ samples.Add(new Sample(sample.StackIndex, -1, sample.TimeRelativeMSec, sample.Metric, -1));
+
+ return;
+ }
+
+ // Sample with no Thread assigned - it's most probably a "Process" sample, we just ignore it
+ });
+
+ foreach (var samples in samplesPerThread.Values)
+ {
+ // all samples in the StackSource should be sorted, but we want to ensure it
+ samples.Sort(CompareSamples);
+ }
+
+ return samplesPerThread;
+ }
+
+ ///
+ /// all the samples that we have are leafs (last sample in the call stack)
+ /// this method expands those samples to full information
+ /// it walks the stack up to the begining and adds a sample for every method on the stack
+ /// it's required to build full information
+ ///
+ internal static IReadOnlyDictionary> WalkTheStackAndExpandSamples(StackSource stackSource, IEnumerable leafs,
+ Dictionary exportedFrameNameToExportedFrameId, Dictionary exportedFrameIdToExportedNameAndCallerId)
+ {
+ var frameIdToSamples = new Dictionary>();
+
+ // we use stack here bacause we want a certain order: from the root to the leaf
+ var stackIndexesToHandle = new Stack();
+
+ foreach (var leafSample in leafs)
+ {
+ // walk the stack first
+ var stackIndex = leafSample.StackIndex;
+ while (stackIndex != StackSourceCallStackIndex.Invalid)
+ {
+ stackIndexesToHandle.Push(stackIndex);
+
+ stackIndex = stackSource.GetCallerIndex(stackIndex);
+ }
+
+ // add sample for every method on the stack
+ int depth = -1;
+ int callerFrameId = -1;
+ while (stackIndexesToHandle.Count > 0)
+ {
+ stackIndex = stackIndexesToHandle.Pop();
+ depth++;
+
+ var frameIndex = stackSource.GetFrameIndex(stackIndex);
+ if (frameIndex == StackSourceFrameIndex.Broken || frameIndex == StackSourceFrameIndex.Invalid)
+ continue;
+
+ var frameName = stackSource.GetFrameName(frameIndex, false);
+ if (string.IsNullOrEmpty(frameName))
+ continue;
+
+ if (!exportedFrameNameToExportedFrameId.TryGetValue(frameName, out int exportedFrameId))
+ exportedFrameNameToExportedFrameId.Add(frameName, exportedFrameId = exportedFrameNameToExportedFrameId.Count);
+
+ if (!frameIdToSamples.TryGetValue(exportedFrameId, out var samples))
+ frameIdToSamples.Add(exportedFrameId, samples = new List());
+
+ // the time and metric are the same as for the leaf sample
+ // the difference is stack index (not really used from here), caller frame id and depth (used for sorting the exported data)
+ samples.Add(new Sample(stackIndex, callerFrameId, leafSample.RelativeTime, leafSample.Metric, depth));
+
+ if (!exportedFrameIdToExportedNameAndCallerId.ContainsKey(exportedFrameId))
+ {
+ // in the future we could identify the categories in a more advance way
+ // and split JIT, GC, Runtime, Libraries and ASP.NET Code into separate categories
+ int index = frameName.IndexOf('!');
+ string category = index > 0 ? frameName.Substring(0, index) : string.Empty;
+ string shortName = index > 0 ? frameName.Substring(index + 1) : frameName;
+ exportedFrameIdToExportedNameAndCallerId.Add(exportedFrameId, new FrameInfo(callerFrameId, shortName, category));
+ }
+
+ callerFrameId = exportedFrameId;
+ }
+ }
+
+ return frameIdToSamples;
+ }
+
+ ///
+ /// this method aggregates all the singular samples to continuous events
+ /// example: samples for Main taken at time 0.1 0.2 0.3 0.4 0.5
+ /// are gonna be translated to Main start at 0.1 stop at 0.5
+ ///
+ internal static IReadOnlyList GetAggregatedOrderedProfileEvents(IReadOnlyDictionary> frameIdToSamples)
+ {
+ List profileEvents = new List();
+
+ foreach (var samplesInfo in frameIdToSamples)
+ {
+ var frameId = samplesInfo.Key;
+ var samples = samplesInfo.Value;
+
+ // this should not be required, but I prefer to be sure that the data is sorted
+ samples.Sort(CompareSamples);
+
+ Sample openSample = samples[0]; // samples are never empty
+ for (int i = 1; i < samples.Count; i++)
+ {
+ if (AreNotContinuous(samples[i - 1], samples[i]))
+ {
+ AddEvents(profileEvents, openSample, samples[i - 1], frameId);
+
+ openSample = samples[i];
+ }
+ }
+
+ // we need to handle the last (or the only one) profile event
+ AddEvents(profileEvents, openSample, samples[samples.Count - 1], frameId);
+ }
+
+ // MUST HAVE!!! the tool expects the profile events in certain order!!
+ return OrderForExport(profileEvents).ToArray();
+ }
+
+ ///
+ /// this method checks if both samples do NOT belong to the same profile event
+ ///
+ private static bool AreNotContinuous(Sample left, Sample right)
+ {
+ if (left.Depth != right.Depth)
+ return true;
+ if (left.CallerFrameId != right.CallerFrameId)
+ return true;
+
+ // 1.2 is a magic number based on some experiments ;)
+ return left.RelativeTime + (left.Metric * 1.2) < right.RelativeTime;
+ }
+
+ ///
+ /// this method adds a new profile event for provided samples
+ /// it also make sure that a profile event does not open and close at the same time (would be ignored by SpeedScope)
+ ///
+ private static void AddEvents(List profileEvents, Sample openSample, Sample closeSample, int frameId)
+ {
+ if (openSample.Depth != closeSample.Depth)
+ throw new ArgumentException("Invalid arguments, both samples must be of the same depth");
+ if (openSample.RelativeTime == closeSample.RelativeTime + closeSample.Metric)
+ throw new ArgumentException("Invalid samples, two samples can not happen at the same time.");
+
+ profileEvents.Add(new ProfileEvent(ProfileEventType.Open, frameId, openSample.RelativeTime, openSample.Depth));
+ profileEvents.Add(new ProfileEvent(ProfileEventType.Close, frameId, closeSample.RelativeTime + closeSample.Metric, closeSample.Depth));
+ }
+
+ ///
+ /// this method orders the profile events in the order required by SpeedScope
+ /// it's just the order of drawing the time graph
+ ///
+ internal static IEnumerable OrderForExport(IEnumerable profiles)
+ {
+ return profiles
+ .GroupBy(@event => @event.RelativeTime)
+ .OrderBy(group => group.Key)
+ .SelectMany(group =>
+ {
+ // MakeSureSamplesDoNotOverlap guarantees that samples do NOT overlap
+ // AddEvents guarantees us that there is no event which starts and end at the same time
+ // so we don't need to worry about this edge case here
+
+ // first of all, we need to emit close events, descending by depth (tool format requires that)
+ var closingDescendingByDepth = group.Where(@event => @event.Type == ProfileEventType.Close).OrderByDescending(@event => @event.Depth);
+ // then we can open new events, ascending by depth (tool format requires that)
+ var openingAscendingByDepth = group.Where(@event => @event.Type == ProfileEventType.Open).OrderBy(@event => @event.Depth);
+
+ return closingDescendingByDepth.Concat(openingAscendingByDepth);
+ });
+ }
+
+ private static int CompareSamples(Sample x, Sample y)
+ {
+ int timeComparison = x.RelativeTime.CompareTo(y.RelativeTime);
+
+ if (timeComparison != 0)
+ return timeComparison;
+
+ // in case both samples start at the same time, the one with smaller metric should be the first one
+ return x.Metric.CompareTo(y.Metric);
+ }
+
+ internal readonly struct ThreadInfo : IEquatable
+ {
+ private static readonly Regex IdExpression = new Regex(@"\((\d+)\)", RegexOptions.Compiled);
+
+ internal ThreadInfo(string threadFrameName, string processFrameName)
+ {
+ var threadIdMatch = IdExpression.Match(threadFrameName);
+ var processIdMatch = IdExpression.Match(processFrameName);
+
+ Name = threadFrameName;
+ Id = threadIdMatch.Success ? int.Parse(threadIdMatch.Groups[1].Value) : 0;
+ ProcessId = processIdMatch.Success ? int.Parse(processIdMatch.Groups[1].Value) : 0;
+ }
+
+ internal ThreadInfo(string name, int id, int processId)
+ {
+ Name = name;
+ Id = id;
+ ProcessId = processId;
+ }
+
+ public override string ToString() => Name;
+
+ public bool Equals(ThreadInfo other) => Name == other.Name && Id == other.Id && ProcessId == other.ProcessId;
+
+ public override bool Equals(object obj) => obj is ThreadInfo other && Equals(other);
+
+ public override int GetHashCode() => Name.GetHashCode() ^ Id ^ ProcessId;
+
+ #region private
+ internal string Name { get; }
+ internal int Id { get; }
+ internal int ProcessId { get; }
+ #endregion private
+ }
+
+ internal readonly struct Sample
+ {
+ internal Sample(StackSourceCallStackIndex stackIndex, int callerFrameId, double relativeTime, double metric, int depth)
+ {
+ StackIndex = stackIndex;
+ CallerFrameId = callerFrameId;
+ RelativeTime = relativeTime;
+ Metric = metric;
+ Depth = depth;
+ }
+
+ public override string ToString() => RelativeTime.ToString(CultureInfo.InvariantCulture);
+
+ #region private
+ internal StackSourceCallStackIndex StackIndex { get; }
+ internal int CallerFrameId { get; }
+ internal double RelativeTime { get; }
+ internal double Metric { get; }
+ internal int Depth { get; }
+ #endregion private
+ }
+
+ internal enum ProfileEventType : byte
+ {
+ Open = 0, Close = 1
+ }
+
+ internal readonly struct ProfileEvent
+ {
+ public ProfileEvent(ProfileEventType type, int frameId, double relativeTime, int depth)
+ {
+ Type = type;
+ FrameId = frameId;
+ RelativeTime = relativeTime;
+ Depth = depth;
+ }
+
+ public override string ToString() => $"{RelativeTime.ToString(CultureInfo.InvariantCulture)} {Type} {FrameId}";
+
+ #region private
+ internal ProfileEventType Type { get; }
+ internal int FrameId { get; }
+ internal double RelativeTime { get; }
+ internal int Depth { get; }
+ #endregion private
+ }
+
+ internal readonly struct FrameInfo
+ {
+ public FrameInfo(int parentId, string frameName, string category)
+ {
+ ParentId = parentId;
+ Name = frameName;
+ Category = category;
+ }
+
+ #region private
+ internal int ParentId { get; }
+ internal string Name { get; }
+ internal string Category { get; }
+ #endregion private
+ }
+ }
+}
diff --git a/src/TraceEvent/TraceEvent.Tests/SpeedScopeExporterTests.cs b/src/TraceEvent/TraceEvent.Tests/SpeedScopeExporterTests.cs
index b8bcfa703..53d9a8cdc 100644
--- a/src/TraceEvent/TraceEvent.Tests/SpeedScopeExporterTests.cs
+++ b/src/TraceEvent/TraceEvent.Tests/SpeedScopeExporterTests.cs
@@ -1,48 +1,58 @@
using Microsoft.Diagnostics.Tracing.Stacks;
-using Microsoft.Diagnostics.Tracing.Stacks.Formats;
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
+using static Microsoft.Diagnostics.Tracing.Stacks.StackSourceWriterHelper;
+
namespace TraceEventTests
{
public class SpeedScopeStackSourceWriterTests
{
- [Fact]
- public void GetSortedSamplesReturnsSamplesSortedByRelativeTimeAndGrouppedByThread()
+ [Theory]
+ [InlineData("Process (321)", 321)]
+ [InlineData("Unknown", 0)]
+ public void GetSortedSamplesReturnsSamplesSortedByRelativeTimeAndGrouppedByThreadWithProcessInfo(string processName, int expectedProcessId)
{
const string ThreadName = "Thread (123)";
- var thread_1 = new FakeStackSourceSample(
+
+ var process = new FakeStackSourceSample(
relativeTime: 0.1,
- name: ThreadName,
+ name: processName,
frameIndex: (StackSourceFrameIndex)5, // 5 is first non-taken enum value
stackIndex: (StackSourceCallStackIndex)1, // 1 is first non-taken enum value
callerIndex: StackSourceCallStackIndex.Invalid);
- var a_1 = new FakeStackSourceSample(
+ var thread_1 = new FakeStackSourceSample(
relativeTime: 0.1,
- name: "A",
+ name: ThreadName,
frameIndex: (StackSourceFrameIndex)6,
stackIndex: (StackSourceCallStackIndex)2,
+ callerIndex: process.StackIndex);
+ var a_1 = new FakeStackSourceSample(
+ relativeTime: 0.1,
+ name: "A",
+ frameIndex: (StackSourceFrameIndex)7,
+ stackIndex: (StackSourceCallStackIndex)3,
callerIndex: thread_1.StackIndex);
var thread_2 = new FakeStackSourceSample(
relativeTime: 0.2,
name: ThreadName,
- frameIndex: (StackSourceFrameIndex)5, // 5 is first non-taken enum value
- stackIndex: (StackSourceCallStackIndex)3, // 1 is first non-taken enum value
- callerIndex: StackSourceCallStackIndex.Invalid);
+ frameIndex: (StackSourceFrameIndex)6,
+ stackIndex: (StackSourceCallStackIndex)4,
+ callerIndex: process.StackIndex);
var a_2 = new FakeStackSourceSample(
relativeTime: 0.2,
name: "A",
- frameIndex: (StackSourceFrameIndex)6,
- stackIndex: (StackSourceCallStackIndex)4,
+ frameIndex: (StackSourceFrameIndex)7,
+ stackIndex: (StackSourceCallStackIndex)5,
callerIndex: thread_2.StackIndex);
- var sourceSamples = new[] { thread_1, thread_2, a_2, a_1 };
+ var sourceSamples = new[] { process, thread_1, thread_2, a_2, a_1 };
var stackSource = new StackSourceStub(sourceSamples);
- var result = SpeedScopeStackSourceWriter.GetSortedSamplesPerThread(stackSource)[ThreadName];
+ var result = GetSortedSamplesPerThread(stackSource)[new ThreadInfo(ThreadName, 123, expectedProcessId)];
Assert.Equal(0.1, result[0].RelativeTime);
Assert.Equal(0.1, result[1].RelativeTime);
@@ -75,11 +85,11 @@ public void WalkTheStackAndExpandSamplesProducesFullInformation()
callerIndex: a.StackIndex);
var allSamples = new[] { main, a, b };
- var leafs = new[] { new SpeedScopeStackSourceWriter.Sample(b.StackIndex, -1, b.RelativeTime, b.Metric, -1) };
+ var leafs = new[] { new Sample(b.StackIndex, -1, b.RelativeTime, b.Metric, -1) };
var stackSource = new StackSourceStub(allSamples);
var frameNameToId = new Dictionary();
- var frameIdToSamples = SpeedScopeStackSourceWriter.WalkTheStackAndExpandSamples(stackSource, leafs, frameNameToId);
+ var frameIdToSamples = WalkTheStackAndExpandSamples(stackSource, leafs, frameNameToId, new Dictionary());
Assert.Equal(0, frameNameToId[main.Name]);
Assert.Equal(1, frameNameToId[a.Name]);
@@ -112,11 +122,11 @@ public void WalkTheStackAndExpandSamplesHandlesBrokenStacks(StackSourceFrameInde
callerIndex: main.StackIndex);
var allSamples = new[] { main, wrong };
- var leafs = new[] { new SpeedScopeStackSourceWriter.Sample(wrong.StackIndex, -1, wrong.RelativeTime, wrong.Metric, -1) };
+ var leafs = new[] { new Sample(wrong.StackIndex, -1, wrong.RelativeTime, wrong.Metric, -1) };
var stackSource = new StackSourceStub(allSamples);
var frameNameToId = new Dictionary();
- var frameIdToSamples = SpeedScopeStackSourceWriter.WalkTheStackAndExpandSamples(stackSource, leafs, frameNameToId);
+ var frameIdToSamples = WalkTheStackAndExpandSamples(stackSource, leafs, frameNameToId, new Dictionary());
Assert.Equal(0, frameNameToId[main.Name]);
Assert.False(frameNameToId.ContainsKey(wrong.Name));
@@ -133,37 +143,37 @@ public void GetAggregatedOrderedProfileEventsConvertsContinuousSamplesWithPauses
var samples = new[]
{
- new SpeedScopeStackSourceWriter.Sample((StackSourceCallStackIndex)1, callerFrameId: 0, metric: metric, depth: 0, relativeTime: 0.1),
- new SpeedScopeStackSourceWriter.Sample((StackSourceCallStackIndex)1, callerFrameId: 0, metric: metric, depth: 0, relativeTime: 0.2),
+ new Sample((StackSourceCallStackIndex)1, callerFrameId: 0, metric: metric, depth: 0, relativeTime: 0.1),
+ new Sample((StackSourceCallStackIndex)1, callerFrameId: 0, metric: metric, depth: 0, relativeTime: 0.2),
- new SpeedScopeStackSourceWriter.Sample((StackSourceCallStackIndex)1, callerFrameId: 0, metric: metric, depth: 0, relativeTime: 0.7),
+ new Sample((StackSourceCallStackIndex)1, callerFrameId: 0, metric: metric, depth: 0, relativeTime: 0.7),
- new SpeedScopeStackSourceWriter.Sample((StackSourceCallStackIndex)1, callerFrameId: 0, metric: metric, depth: 0, relativeTime: 1.1),
- new SpeedScopeStackSourceWriter.Sample((StackSourceCallStackIndex)1, callerFrameId: 0, metric: metric, depth: 0, relativeTime: 1.2),
- new SpeedScopeStackSourceWriter.Sample((StackSourceCallStackIndex)1, callerFrameId: 0, metric: metric, depth: 0, relativeTime: 1.3),
+ new Sample((StackSourceCallStackIndex)1, callerFrameId: 0, metric: metric, depth: 0, relativeTime: 1.1),
+ new Sample((StackSourceCallStackIndex)1, callerFrameId: 0, metric: metric, depth: 0, relativeTime: 1.2),
+ new Sample((StackSourceCallStackIndex)1, callerFrameId: 0, metric: metric, depth: 0, relativeTime: 1.3),
};
- var input = new Dictionary>() { { 0, samples.ToList() } };
+ var input = new Dictionary>() { { 0, samples.ToList() } };
- var aggregatedEvents = SpeedScopeStackSourceWriter.GetAggregatedOrderedProfileEvents(input);
+ var aggregatedEvents = GetAggregatedOrderedProfileEvents(input);
// we should have <0.1, 0.3> and <0.7, 0.8> (the tool would ignore <0.7, 0.7>) and <1.1, 1.4>
Assert.Equal(6, aggregatedEvents.Count);
Assert.Equal(0.1, aggregatedEvents[0].RelativeTime);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Open, aggregatedEvents[0].Type);
+ Assert.Equal(ProfileEventType.Open, aggregatedEvents[0].Type);
Assert.Equal(0.2 + metric, aggregatedEvents[1].RelativeTime);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Close, aggregatedEvents[1].Type);
+ Assert.Equal(ProfileEventType.Close, aggregatedEvents[1].Type);
Assert.Equal(0.7, aggregatedEvents[2].RelativeTime);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Open, aggregatedEvents[2].Type);
+ Assert.Equal(ProfileEventType.Open, aggregatedEvents[2].Type);
Assert.Equal(0.7 + metric, aggregatedEvents[3].RelativeTime);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Close, aggregatedEvents[3].Type);
+ Assert.Equal(ProfileEventType.Close, aggregatedEvents[3].Type);
Assert.Equal(1.1, aggregatedEvents[4].RelativeTime);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Open, aggregatedEvents[4].Type);
+ Assert.Equal(ProfileEventType.Open, aggregatedEvents[4].Type);
Assert.Equal(1.3 + metric, aggregatedEvents[5].RelativeTime);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Close, aggregatedEvents[5].Type);
+ Assert.Equal(ProfileEventType.Close, aggregatedEvents[5].Type);
}
[Fact]
@@ -173,32 +183,32 @@ public void GetAggregatedOrderedProfileEventsConvertsContinuousSamplesWithDiffer
var samples = new[]
{
- new SpeedScopeStackSourceWriter.Sample((StackSourceCallStackIndex)1, callerFrameId: 0, metric: metric, relativeTime: 0.1, depth: 0),
- new SpeedScopeStackSourceWriter.Sample((StackSourceCallStackIndex)1, callerFrameId: 0, metric: metric, relativeTime: 0.2, depth: 1), // depth change!
+ new Sample((StackSourceCallStackIndex)1, callerFrameId: 0, metric: metric, relativeTime: 0.1, depth: 0),
+ new Sample((StackSourceCallStackIndex)1, callerFrameId: 0, metric: metric, relativeTime: 0.2, depth: 1), // depth change!
};
- var input = new Dictionary>() { { 0, samples.ToList() } };
+ var input = new Dictionary>() { { 0, samples.ToList() } };
- var aggregatedEvents = SpeedScopeStackSourceWriter.GetAggregatedOrderedProfileEvents(input);
+ var aggregatedEvents = GetAggregatedOrderedProfileEvents(input);
// we should have:
// Open at 0.1 depth 0 and Close 0.2
// Open at 0.2 depth 1 and Close 0.3
Assert.Equal(4, aggregatedEvents.Count);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Open, aggregatedEvents[0].Type);
+ Assert.Equal(ProfileEventType.Open, aggregatedEvents[0].Type);
Assert.Equal(0.1, aggregatedEvents[0].RelativeTime);
Assert.Equal(0, aggregatedEvents[0].Depth);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Close, aggregatedEvents[1].Type);
+ Assert.Equal(ProfileEventType.Close, aggregatedEvents[1].Type);
Assert.Equal(0.1 + metric, aggregatedEvents[1].RelativeTime);
Assert.Equal(0, aggregatedEvents[0].Depth);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Open, aggregatedEvents[2].Type);
+ Assert.Equal(ProfileEventType.Open, aggregatedEvents[2].Type);
Assert.Equal(0.2, aggregatedEvents[2].RelativeTime);
Assert.Equal(1, aggregatedEvents[2].Depth);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Close, aggregatedEvents[3].Type);
+ Assert.Equal(ProfileEventType.Close, aggregatedEvents[3].Type);
Assert.Equal(0.2 + metric, aggregatedEvents[3].RelativeTime);
Assert.Equal(1, aggregatedEvents[3].Depth);
}
@@ -210,32 +220,32 @@ public void GetAggregatedOrderedProfileEventsConvertsContinuousSamplesWithDiffer
var samples = new[]
{
- new SpeedScopeStackSourceWriter.Sample((StackSourceCallStackIndex)1, metric: metric, relativeTime: 0.1, depth: 0, callerFrameId: 0),
- new SpeedScopeStackSourceWriter.Sample((StackSourceCallStackIndex)1, metric: metric, relativeTime: 0.2, depth: 0, callerFrameId: 1), // callerFrameId change!
+ new Sample((StackSourceCallStackIndex)1, metric: metric, relativeTime: 0.1, depth: 0, callerFrameId: 0),
+ new Sample((StackSourceCallStackIndex)1, metric: metric, relativeTime: 0.2, depth: 0, callerFrameId: 1), // callerFrameId change!
};
- var input = new Dictionary>() { { 0, samples.ToList() } };
+ var input = new Dictionary>() { { 0, samples.ToList() } };
- var aggregatedEvents = SpeedScopeStackSourceWriter.GetAggregatedOrderedProfileEvents(input);
+ var aggregatedEvents = GetAggregatedOrderedProfileEvents(input);
// we should have:
// Open at 0.1 depth 0 and Close 0.2
// Open at 0.2 depth 0 and Close 0.3
Assert.Equal(4, aggregatedEvents.Count);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Open, aggregatedEvents[0].Type);
+ Assert.Equal(ProfileEventType.Open, aggregatedEvents[0].Type);
Assert.Equal(0.1, aggregatedEvents[0].RelativeTime);
Assert.Equal(0, aggregatedEvents[0].Depth);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Close, aggregatedEvents[1].Type);
+ Assert.Equal(ProfileEventType.Close, aggregatedEvents[1].Type);
Assert.Equal(0.1 + metric, aggregatedEvents[1].RelativeTime);
Assert.Equal(0, aggregatedEvents[0].Depth);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Open, aggregatedEvents[2].Type);
+ Assert.Equal(ProfileEventType.Open, aggregatedEvents[2].Type);
Assert.Equal(0.2, aggregatedEvents[2].RelativeTime);
Assert.Equal(0, aggregatedEvents[2].Depth);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Close, aggregatedEvents[3].Type);
+ Assert.Equal(ProfileEventType.Close, aggregatedEvents[3].Type);
Assert.Equal(0.2 + metric, aggregatedEvents[3].RelativeTime);
Assert.Equal(0, aggregatedEvents[3].Depth);
}
@@ -247,24 +257,24 @@ public void CloseMetricCanBeZeroIfItDoesNotCreateAProfileEventThatStartsAndEndsA
var samples = new[]
{
- new SpeedScopeStackSourceWriter.Sample((StackSourceCallStackIndex)1, metric: metric, relativeTime: 0.1, depth: 0, callerFrameId: 0),
- new SpeedScopeStackSourceWriter.Sample((StackSourceCallStackIndex)1, metric: metric, relativeTime: 0.2, depth: 0, callerFrameId: 0),
- new SpeedScopeStackSourceWriter.Sample((StackSourceCallStackIndex)1, metric: 0.0, relativeTime: 0.3, depth: 0, callerFrameId: 0), // 0.0 metric
+ new Sample((StackSourceCallStackIndex)1, metric: metric, relativeTime: 0.1, depth: 0, callerFrameId: 0),
+ new Sample((StackSourceCallStackIndex)1, metric: metric, relativeTime: 0.2, depth: 0, callerFrameId: 0),
+ new Sample((StackSourceCallStackIndex)1, metric: 0.0, relativeTime: 0.3, depth: 0, callerFrameId: 0), // 0.0 metric
};
- var input = new Dictionary>() { { 0, samples.ToList() } };
+ var input = new Dictionary>() { { 0, samples.ToList() } };
- var aggregatedEvents = SpeedScopeStackSourceWriter.GetAggregatedOrderedProfileEvents(input);
+ var aggregatedEvents = GetAggregatedOrderedProfileEvents(input);
// we should have:
// Open at 0.1 depth 0 and Close 0.3
Assert.Equal(2, aggregatedEvents.Count);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Open, aggregatedEvents[0].Type);
+ Assert.Equal(ProfileEventType.Open, aggregatedEvents[0].Type);
Assert.Equal(0.1, aggregatedEvents[0].RelativeTime);
Assert.Equal(0, aggregatedEvents[0].Depth);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Close, aggregatedEvents[1].Type);
+ Assert.Equal(ProfileEventType.Close, aggregatedEvents[1].Type);
Assert.Equal(0.3, aggregatedEvents[1].RelativeTime);
Assert.Equal(0, aggregatedEvents[0].Depth);
}
@@ -277,58 +287,58 @@ public void TwoSamplesCanNotHappenAtTheSameTime()
var samples = new[]
{
- new SpeedScopeStackSourceWriter.Sample((StackSourceCallStackIndex)1, metric: zeroMetric, relativeTime: relativeTime, depth: 0, callerFrameId: 0), // 0.0 metric
- new SpeedScopeStackSourceWriter.Sample((StackSourceCallStackIndex)1, metric: zeroMetric, relativeTime: relativeTime, depth: 0, callerFrameId: 0), // 0.0 metric and same relative time
+ new Sample((StackSourceCallStackIndex)1, metric: zeroMetric, relativeTime: relativeTime, depth: 0, callerFrameId: 0), // 0.0 metric
+ new Sample((StackSourceCallStackIndex)1, metric: zeroMetric, relativeTime: relativeTime, depth: 0, callerFrameId: 0), // 0.0 metric and same relative time
};
- var input = new Dictionary>() { { 0, samples.ToList() } };
+ var input = new Dictionary>() { { 0, samples.ToList() } };
- Assert.Throws(() => SpeedScopeStackSourceWriter.GetAggregatedOrderedProfileEvents(input));
+ Assert.Throws(() => GetAggregatedOrderedProfileEvents(input));
}
[Fact]
public void OrderForExportOrdersTheProfileEventsAsExpectedByTheSpeedScope()
{
- var profileEvents = new List()
+ var profileEvents = new List()
{
- new SpeedScopeStackSourceWriter.ProfileEvent(SpeedScopeStackSourceWriter.ProfileEventType.Open, frameId: 0, depth: 0, relativeTime: 0.1),
- new SpeedScopeStackSourceWriter.ProfileEvent(SpeedScopeStackSourceWriter.ProfileEventType.Open, frameId: 1, depth: 1, relativeTime: 0.1),
- new SpeedScopeStackSourceWriter.ProfileEvent(SpeedScopeStackSourceWriter.ProfileEventType.Close, frameId: 1, depth: 1, relativeTime: 0.3),
- new SpeedScopeStackSourceWriter.ProfileEvent(SpeedScopeStackSourceWriter.ProfileEventType.Close, frameId: 0, depth: 0, relativeTime: 0.3),
- new SpeedScopeStackSourceWriter.ProfileEvent(SpeedScopeStackSourceWriter.ProfileEventType.Open, frameId: 2, depth: 0, relativeTime: 0.3),
- new SpeedScopeStackSourceWriter.ProfileEvent(SpeedScopeStackSourceWriter.ProfileEventType.Close, frameId: 2, depth: 0, relativeTime: 0.4),
+ new ProfileEvent(ProfileEventType.Open, frameId: 0, depth: 0, relativeTime: 0.1),
+ new ProfileEvent(ProfileEventType.Open, frameId: 1, depth: 1, relativeTime: 0.1),
+ new ProfileEvent(ProfileEventType.Close, frameId: 1, depth: 1, relativeTime: 0.3),
+ new ProfileEvent(ProfileEventType.Close, frameId: 0, depth: 0, relativeTime: 0.3),
+ new ProfileEvent(ProfileEventType.Open, frameId: 2, depth: 0, relativeTime: 0.3),
+ new ProfileEvent(ProfileEventType.Close, frameId: 2, depth: 0, relativeTime: 0.4),
};
profileEvents.Reverse(); // reverse to make sure that it does sort the elements in right way
- var ordered = SpeedScopeStackSourceWriter.OrderForExport(profileEvents).ToArray();
+ var ordered = OrderForExport(profileEvents).ToArray();
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Open, ordered[0].Type);
+ Assert.Equal(ProfileEventType.Open, ordered[0].Type);
Assert.Equal(0.1, ordered[0].RelativeTime);
Assert.Equal(0, ordered[0].Depth);
Assert.Equal(0, ordered[0].FrameId);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Open, ordered[1].Type);
+ Assert.Equal(ProfileEventType.Open, ordered[1].Type);
Assert.Equal(0.1, ordered[1].RelativeTime);
Assert.Equal(1, ordered[1].Depth);
Assert.Equal(1, ordered[1].FrameId);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Close, ordered[2].Type);
+ Assert.Equal(ProfileEventType.Close, ordered[2].Type);
Assert.Equal(0.3, ordered[2].RelativeTime);
Assert.Equal(1, ordered[2].Depth);
Assert.Equal(1, ordered[2].FrameId);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Close, ordered[3].Type);
+ Assert.Equal(ProfileEventType.Close, ordered[3].Type);
Assert.Equal(0.3, ordered[3].RelativeTime);
Assert.Equal(0, ordered[3].Depth);
Assert.Equal(0, ordered[3].FrameId);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Open, ordered[4].Type);
+ Assert.Equal(ProfileEventType.Open, ordered[4].Type);
Assert.Equal(0.3, ordered[4].RelativeTime);
Assert.Equal(0, ordered[4].Depth);
Assert.Equal(2, ordered[4].FrameId);
- Assert.Equal(SpeedScopeStackSourceWriter.ProfileEventType.Close, ordered[5].Type);
+ Assert.Equal(ProfileEventType.Close, ordered[5].Type);
Assert.Equal(0.4, ordered[5].RelativeTime);
Assert.Equal(0, ordered[5].Depth);
Assert.Equal(2, ordered[5].FrameId);