diff --git a/DotnetEventViewer/CallTree/CallTreeNode.cs b/DotnetEventViewer/CallTree/CallTreeNode.cs index 71bd07f..4cf8acb 100644 --- a/DotnetEventViewer/CallTree/CallTreeNode.cs +++ b/DotnetEventViewer/CallTree/CallTreeNode.cs @@ -7,7 +7,8 @@ public class CallTreeNode { public static CallTreeNode Create( IEnumerable events, - ICallTreeCountAggregatorProcessor processor) + ICallTreeCountAggregatorProcessor processor, + bool bottomUp) { CallTreeNode root = new(0, new MethodDescription("root", "")); @@ -19,29 +20,40 @@ public static CallTreeNode Create( var currentNode = root; - for (int i = 0; i < evt.StackTrace.Frames.Length; i += 1) + if (bottomUp) { - var frame = evt.StackTrace.Frames[i]; - currentNode.Children ??= new Dictionary(); - if (!currentNode.Children.TryGetValue(frame.Address, out var childNode)) + for (int i = 0; i < evt.StackTrace.Frames.Length; i += 1) { - childNode = new CallTreeNode(id, frame); - currentNode.Children[frame.Address] = childNode; - id += 1; + AddCallToNode(evt.StackTrace.Frames[i], ref currentNode, ref id); } - - currentNode = childNode; - - if (i == evt.StackTrace.Frames.Length - 1) + } + else + { + for (int i = evt.StackTrace.Frames.Length - 1; i >= 0; i -= 1) { - processor.UpdateLeafNode(ref currentNode._count, evt); + AddCallToNode(evt.StackTrace.Frames[i], ref currentNode, ref id); } } + + processor.UpdateLeafNode(ref currentNode._count, evt); } root.PropagateCount(); return root; + + static void AddCallToNode(MethodDescription frame, ref CallTreeNode node, ref int id) + { + node.Children ??= new Dictionary(); + if (!node.Children.TryGetValue(frame.Address, out var childNode)) + { + childNode = new CallTreeNode(id, frame); + node.Children[frame.Address] = childNode; + id += 1; + } + + node = childNode; + } } private long _count; diff --git a/DotnetEventViewer/Components/QueryBuilder.razor b/DotnetEventViewer/Components/QueryBuilder.razor index f0a9820..668c068 100644 --- a/DotnetEventViewer/Components/QueryBuilder.razor +++ b/DotnetEventViewer/Components/QueryBuilder.razor @@ -73,15 +73,26 @@ OptionText="@(d => d.Name)" OptionDisabled="IsAggregatorDisabled" @bind-SelectedOption="Query.SelectedAggregator"/> - - - Tries to filter out events that occured on a non-threadpool thread. Useful to investigate thread - pool starvation. This option is not 100% accurate. - + + + + + Bottom-up Tree + + + Thread Pool Stacks Only + + } ? SelectedColumnFields { get; set; } public ICallTreeCountAggregator? SelectedAggregator { get; set; } + public bool BottomUpTree { get; set; } = true; public bool ThreadPoolStacksOnly { get; set; } } diff --git a/DotnetEventViewer/Querying/QueryResult.cs b/DotnetEventViewer/Querying/QueryResult.cs index 0ddf13a..1722c33 100644 --- a/DotnetEventViewer/Querying/QueryResult.cs +++ b/DotnetEventViewer/Querying/QueryResult.cs @@ -3,7 +3,11 @@ namespace DotnetEventViewer.Querying; -public class QueryResult(IReadOnlyList filteredEvents, IEnumerable? columnFields, ICallTreeCountAggregator? callTreeAggregator) +public class QueryResult( + IReadOnlyList filteredEvents, + IEnumerable? columnFields, + ICallTreeCountAggregator? callTreeAggregator, + bool bottomUpTree) { public IReadOnlyList FilteredEvents { get; } = filteredEvents; @@ -12,4 +16,6 @@ public class QueryResult(IReadOnlyList filteredEvents, IEnumerable /// Non-null for . public ICallTreeCountAggregator? CallTreeAggregator { get; } = callTreeAggregator; + + public bool BottomUpTree { get; } = bottomUpTree; } \ No newline at end of file