Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2cffd5b
feat(call-tree): add aggregated and bottom-up views
lukecotter Apr 28, 2026
8d14f8a
refactor: calltreeNameFormatter switch to dataTree?.index instead of …
lukecotter Apr 28, 2026
fcb1057
refactor: remove persistence option from createTimeOrderTable function
lukecotter Apr 28, 2026
b99865a
refactor: remove Avg Self column from AggregatedTable
lukecotter Apr 28, 2026
85040f8
refactor: simplify progress calculation
lukecotter Apr 28, 2026
1cfdf86
feat(call-tree): and new metrics to AggregatedTable
lukecotter Apr 28, 2026
a93ad00
refactor(TimeOrderTable): change bottomCalc from 'max' to 'sum' for i…
lukecotter Apr 28, 2026
dcdf3c2
refactor: update styles and accessibility for dropdowns in AnalysisVi…
lukecotter Apr 28, 2026
9bf0e3f
refactor(CalltreeView, AggregatedTable, BottomUpTable): update table …
lukecotter Apr 28, 2026
d0874e2
docs: update changelog
lukecotter Apr 29, 2026
9fe4406
docs: update readme with calltree changes
lukecotter Apr 29, 2026
0f32d03
docs: enhance analysis and calltree documentation with bottom-up grou…
lukecotter Apr 29, 2026
c577c4b
refactor(BottomUpTable): enhance options for clipboard and download f…
lukecotter Apr 29, 2026
dfa616a
refactor(CalltreeView): enhance filtering and view switching logic fo…
lukecotter Apr 29, 2026
86e7df0
refactor(MiddleRowFocus): improve row scrolling logic by adding optio…
lukecotter Apr 29, 2026
b354538
refactor(CalltreeNameFormatter): optimize indentation logic by condit…
lukecotter Apr 29, 2026
900de9c
refactor(DataGrid): remove unnecessary CSS properties for improved pe…
lukecotter Apr 29, 2026
359fe70
refactor(AnalysisView): use same table logic as bottom up
lukecotter Apr 29, 2026
2f8c4e7
refactor(CalltreeView): persist bottom-up grouping filter value
lukecotter Apr 29, 2026
File filter

Filter by extension

Filter by extension

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

<!-- Unreleased -->

[#333]: https://github.com/certinia/debug-log-analyzer/issues/333
[#627]: https://github.com/certinia/debug-log-analyzer/issues/627
[#685]: https://github.com/certinia/debug-log-analyzer/issues/685
[#98]: https://github.com/certinia/debug-log-analyzer/issues/98
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Also: Frame Selection & Navigation, Dynamic Frame Labels, Adaptive Frame Detail,
Explore nested method calls with performance metrics:

- **Metrics**: Self Time, Total Time, SOQL/DML/Thrown Counts, SOQL/DML/Rows
- **Views**: Use Time Order for sequence, Aggregated for repeated hot paths, Bottom-Up for caller attribution
- **Filter by Namespace, Type or Duration**
- **Toggle Debug-Only + Detail Events**
- **Keyboard Navigation**
Expand Down
7 changes: 5 additions & 2 deletions lana-docs/docs/docs/features/analysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ hide_title: true

## 🧠 Analysis

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.
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.

![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)

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

### Group

The rows can be grouped by Type or Namespace
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).

In this model, roots are callees and parent/child relationships represent callers expanded beneath each callee.
`Total Time` is the full attributed time for that callee path, while `Self Time` is the exclusive attributed time for the callee itself.

1. Namespace: Shows the rows aggregated by their namespace e.g `default`, `MyNamespace`
1. Type: Shows the rows aggregated by namespace event type e.g `METHOD_ENTRY`, `DML_ENTRY`
Expand Down
17 changes: 17 additions & 0 deletions lana-docs/docs/docs/features/calltree.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,21 @@ The Call Tree helps efficently visualize and navigate the call stack in Salesfro

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

### View Modes

The Call Tree supports three separate views, toggled via the toolbar:

1. **Time Order** (default)
Displays the call stack in chronological execution order.
Best when you want to follow the exact sequence of events from top to bottom.
1. **Aggregated**
Groups repeated method paths so repeated executions are combined.
Best when you want a condensed hotspot view without reviewing every individual frame occurrence.
1. **Bottom-Up**
Starts with each callee as the root and expands into callers.
In this view, roots are callees and expanded rows are caller context.
Best when you want to find which methods are hogging the most time and see the different caller paths that led to them.

### Go to Code

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

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).

### Keyboard Navigation

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.
2 changes: 1 addition & 1 deletion lana-docs/docs/docs/features/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ hide_title: true

import DocCardList from '@theme/DocCardList';

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.
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.

<DocCardList />
51 changes: 51 additions & 0 deletions log-viewer/src/core/utility/Multiset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2026 Certinia Inc. All rights reserved.
*/

/**
* A slimmed down multiset (bag) data structure that allows duplicate elements and tracks their count.
* Provides O(1) add, remove, and has operations.
* Note: This could easily be extended to a full multiset implementation and count() function etc if needed in the future.
*/
export class Multiset<T> {
private map: Map<T, number> = new Map();

/**
* Adds an element to the multiset.
* @param element - The element to add
* @returns The new count of this element
*/
add(element: T): number {
const count = (this.map.get(element) ?? 0) + 1;
this.map.set(element, count);
return count;
}

/**
* Removes one occurrence of an element from the multiset.
* @param element - The element to remove
* @returns True if an element was removed, false if element was not in the multiset
*/
remove(element: T): boolean {
const count = this.map.get(element);
if (count === undefined) {
return false;
}

if (count === 1) {
this.map.delete(element);
} else {
this.map.set(element, count - 1);
}
return true;
}

/**
* Checks if the multiset contains at least one occurrence of an element.
* @param element - The element to check
* @returns True if the element is in the multiset
*/
has(element: T): boolean {
return this.map.has(element);
}
}
Loading
Loading