Skip to content

Commit 0ff08cf

Browse files
Merge pull request #755 from lukecotter/feat-calltree-bottom-up-and-others
feat: add aggregated and bottom-up call tree views
2 parents ec5c1f1 + 2f8c4e7 commit 0ff08cf

29 files changed

Lines changed: 3710 additions & 788 deletions

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6868
- Markers for errors, skipped lines, and truncation
6969
- Click to navigate to Call Tree (now on key press of `J` or `Cmd/Ctrl+Click`)
7070
- **Legacy Support**: Toggle the legacy timeline anytime via **Settings -> Apex Log Analyzer -> Timeline -> Legacy**.
71+
- 🌲 **Call Tree Views**: Detailed **Time Order**, **Aggregated**, and **Bottom-Up** views. ([#333])
72+
- **Time Order**: Chronological call stack for frame-by-frame execution flow.
73+
- **Aggregated**: Groups repeated calls to surface the highest-impact methods quickly.
74+
- **Bottom-Up**: Starts from callees and expands to callers, with optional grouping by Namespace or Type.
75+
- **Go to Source**: Click method names to open source from **Time Order**, **Aggregated**, and **Bottom-Up** when symbols are available.
76+
- **Analysis Alignment**: Analysis now uses the same bottom-up table model for consistent caller attribution.
7177
- 📄 **Raw Log Navigation**: Seamless navigation between raw log files and the log analysis. ([#204])
7278
- **Show in Raw Log**: Right-click timeline or call tree frames → "Show in Log File" to jump to the corresponding line.
7379
- **Show in Log Analysis**: Click the hover link on raw log lines to navigate back to the log analysis.
@@ -477,6 +483,7 @@ Skipped due to adopting odd numbering for pre releases and even number for relea
477483

478484
<!-- Unreleased -->
479485

486+
[#333]: https://github.com/certinia/debug-log-analyzer/issues/333
480487
[#627]: https://github.com/certinia/debug-log-analyzer/issues/627
481488
[#685]: https://github.com/certinia/debug-log-analyzer/issues/685
482489
[#98]: https://github.com/certinia/debug-log-analyzer/issues/98

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ Also: Frame Selection & Navigation, Dynamic Frame Labels, Adaptive Frame Detail,
103103
Explore nested method calls with performance metrics:
104104

105105
- **Metrics**: Self Time, Total Time, SOQL/DML/Thrown Counts, SOQL/DML/Rows
106+
- **Views**: Use Time Order for sequence, Aggregated for repeated hot paths, Bottom-Up for caller attribution
106107
- **Filter by Namespace, Type or Duration**
107108
- **Toggle Debug-Only + Detail Events**
108109
- **Keyboard Navigation**

lana-docs/docs/docs/features/analysis.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ hide_title: true
1818

1919
## 🧠 Analysis
2020

21-
Analyze Salesforce debug logs with detailed metrics on method calls, including Self Time, Total Time, Count, Name, and Type. Easily sort, filter, and group log events by namespace or type, and export or copy results for efficient troubleshooting and performance optimization.
21+
Analyze Salesforce debug logs with detailed metrics on method calls, including Self Time, Total Time, Count, Name, and Type. Easily sort, filter, and group log events by namespace or type, and export or copy results for efficient troubleshooting and performance optimization. The Analysis table uses a bottom-up caller grouping model, where each method is shown as a root with its direct callers as children.
2222

2323
![Analysis view screenshot showing method call metrics such as Self Time, Total Time, Count, Name, and Type](https://raw.githubusercontent.com/certinia/debug-log-analyzer/main/lana/assets/v1.18/lana-analysis.png)
2424

@@ -33,7 +33,10 @@ Each column can be sorted by clicking the column header, this will sort the rows
3333

3434
### Group
3535

36-
The rows can be grouped by Type or Namespace
36+
The rows can be grouped by Type or Namespace using a bottom-up caller grouping model, which complements the three Call Tree views: [Time Order, Aggregated, and Bottom-Up](calltree.md).
37+
38+
In this model, roots are callees and parent/child relationships represent callers expanded beneath each callee.
39+
`Total Time` is the full attributed time for that callee path, while `Self Time` is the exclusive attributed time for the callee itself.
3740

3841
1. Namespace: Shows the rows aggregated by their namespace e.g `default`, `MyNamespace`
3942
1. Type: Shows the rows aggregated by namespace event type e.g `METHOD_ENTRY`, `DML_ENTRY`

lana-docs/docs/docs/features/calltree.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,21 @@ The Call Tree helps efficently visualize and navigate the call stack in Salesfro
2525

2626
Each row shows event type, details such as method signature, self and total time as well as aggregated DML, SOQL, Throws and Row counts.
2727

28+
### View Modes
29+
30+
The Call Tree supports three separate views, toggled via the toolbar:
31+
32+
1. **Time Order** (default)
33+
Displays the call stack in chronological execution order.
34+
Best when you want to follow the exact sequence of events from top to bottom.
35+
1. **Aggregated**
36+
Groups repeated method paths so repeated executions are combined.
37+
Best when you want a condensed hotspot view without reviewing every individual frame occurrence.
38+
1. **Bottom-Up**
39+
Starts with each callee as the root and expands into callers.
40+
In this view, roots are callees and expanded rows are caller context.
41+
Best when you want to find which methods are hogging the most time and see the different caller paths that led to them.
42+
2843
### Go to Code
2944

3045
Clicking the link in the event column will open the corresponding file and line, if that file exists in the current workspace.
@@ -40,6 +55,8 @@ Each column can be sorted by clicking the column header, this will sort the rows
4055
1. Show Log events for specific namespaces using the namespace column filter
4156
1. Min and Max filtering can be done on the _Total Time_ and _Self Time_ columns.
4257

58+
Details, Debug Only, and Type filtering are available in **Time Order** and **Aggregated**. The **Bottom-Up** view has its own grouping controls (None, Namespace, Type).
59+
4360
### Keyboard Navigation
4461

4562
The Call Tree can be navigated with the keyboard. The up and down keys will move between rows, the left and right keys will expand and collapse a parent within the tree.

lana-docs/docs/docs/features/features.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ hide_title: true
1919

2020
import DocCardList from '@theme/DocCardList';
2121

22-
Explore the Apex Log Analyzer for Salesforce debug logs features including interactive timelines, detailed call trees, in-depth analysis, database insights, and powerful search capabilities to streamline your debugging and performance tuning.
22+
Explore the Apex Log Analyzer for Salesforce debug logs features including interactive timelines, detailed Time Order, Aggregated, and Bottom-Up call trees, in-depth analysis, database insights, and powerful search capabilities to streamline your debugging and performance tuning.
2323

2424
<DocCardList />
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (c) 2026 Certinia Inc. All rights reserved.
3+
*/
4+
5+
/**
6+
* A slimmed down multiset (bag) data structure that allows duplicate elements and tracks their count.
7+
* Provides O(1) add, remove, and has operations.
8+
* Note: This could easily be extended to a full multiset implementation and count() function etc if needed in the future.
9+
*/
10+
export class Multiset<T> {
11+
private map: Map<T, number> = new Map();
12+
13+
/**
14+
* Adds an element to the multiset.
15+
* @param element - The element to add
16+
* @returns The new count of this element
17+
*/
18+
add(element: T): number {
19+
const count = (this.map.get(element) ?? 0) + 1;
20+
this.map.set(element, count);
21+
return count;
22+
}
23+
24+
/**
25+
* Removes one occurrence of an element from the multiset.
26+
* @param element - The element to remove
27+
* @returns True if an element was removed, false if element was not in the multiset
28+
*/
29+
remove(element: T): boolean {
30+
const count = this.map.get(element);
31+
if (count === undefined) {
32+
return false;
33+
}
34+
35+
if (count === 1) {
36+
this.map.delete(element);
37+
} else {
38+
this.map.set(element, count - 1);
39+
}
40+
return true;
41+
}
42+
43+
/**
44+
* Checks if the multiset contains at least one occurrence of an element.
45+
* @param element - The element to check
46+
* @returns True if the element is in the multiset
47+
*/
48+
has(element: T): boolean {
49+
return this.map.has(element);
50+
}
51+
}

0 commit comments

Comments
 (0)