Skip to content

Commit dc8824a

Browse files
Merge pull request certinia#716 from lukecotter/feat-timeline-governorlimits
feat: timeline - governor limits strip
2 parents 575abb1 + fc1b1f9 commit dc8824a

24 files changed

Lines changed: 3804 additions & 88 deletions

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2424
- **Scroll** → zoom (vertical) or pan (horizontal)
2525
- **Double-click** → reset to full timeline view
2626
- **Keyboard shortcuts** (when hovering minimap): Arrow keys pan, `W`/`S` zoom, `Home`/`End` jump, `0` resets.
27+
- **📊 Governor Limits Visualization**: New metric strip showing governor limit usage over time. ([#714])
28+
- **Traffic Light Coloring**: Instant visual feedback on limit consumption (safe/warning/critical/breach zones).
29+
- **Collapsed Mode**: Compact heat-style strip showing max usage across all metrics.
30+
- **Expanded Mode**: Full step chart with individual metric lines (click chevron or `Shift+Click` to toggle).
31+
- **Smart Tooltips**: Hover to see detailed breakdown with top metrics, always-visible core limits (CPU, Heap, SOQL, DML), and usage percentages.
32+
- **Tier Classification**: Metrics auto-classified by usage—Tier 1 (top 3), Tier 2 (>80%), Tier 3 (others aggregated).
33+
- **Synced Navigation**: Zoom, pan, and cursor synchronized with main timeline.
2734
- **Adaptive Frame Bucketing**: Reveals nested frame detail and stack structure as you zoom, automatically adjusting frame granularity to understand complex call hierarchies.
2835
- **Dynamic Frame Labels**: Labels automatically appear on timeline frames as you zoom and pan, making log scanning and navigation effortless without manually hovering to see event details. ([#92])
2936
- **Keyboard and Mouse Navigation**: Comprehensive interaction controls for the timeline. ([#573] [#366] [#296] [#295] [#535])
@@ -459,6 +466,7 @@ Skipped due to adopting odd numbering for pre releases and even number for relea
459466

460467
<!-- Unreleased -->
461468

469+
[#714]: https://github.com/certinia/debug-log-analyzer/issues/714
462470
[#245]: https://github.com/certinia/debug-log-analyzer/issues/245
463471
[#164]: https://github.com/certinia/debug-log-analyzer/issues/164
464472
[#535]: https://github.com/certinia/debug-log-analyzer/issues/535

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ The Timeline view shows a live visualization of your Salesforce Apex log executi
9090
- **Zoom & Pan** – Navigate your logs down to 0.001 ms with precision zoom. `W`/`S` keys or scroll wheel for zoom; `A`/`D` keys or drag for pan.
9191
- **Dynamic Frame Labels** – Instantly see method names on timeline frames for faster scanning.
9292
- **🗺️ Minimap** – Bird's-eye view with skyline overview showing call stack depth, viewport lens for navigation, and instant teleport to any position.
93+
- **📊 Governor Limits Strip** – At-a-glance limit usage with traffic light coloring (safe/warning/critical/breach). Expand for detailed step chart.
9394
- **📏 Measure Range**`Shift+Drag` to measure the duration between any two points. Resize edges, double-click to zoom.
9495
- **🔍 Area Zoom**`Alt/Option+Drag` to select a region and instantly zoom to fit.
9596
- **Tooltips** – Hover for duration, event name, SOQL/DML/Exception counts, SOQL/DML rows, and more.

lana-docs-site/docs/docs/features/timeline.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,72 @@ When hovering over the viewport lens or dragging, a label appears showing:
8686
- The **duration** of the selected time range (e.g., "1.23s")
8787
- The **time range** start and end (e.g., "0.5s - 1.73s")
8888

89+
## Governor Limits Strip
90+
91+
The Governor Limits strip displays Salesforce governor limit usage over time, positioned below the main timeline. It provides instant visual feedback on limit consumption to help identify potential limit breaches.
92+
93+
### Display Modes
94+
95+
#### Collapsed Mode (Default)
96+
97+
A compact heat-style visualization showing the maximum limit usage across all metrics at each point in time.
98+
99+
**Traffic Light Colors:**
100+
101+
| Usage Range | Color | Meaning |
102+
| ----------- | ------------ | -------- |
103+
| 0-50% | Transparent | Safe |
104+
| 50-80% | Amber/Orange | Warning |
105+
| 80-100% | Red | Critical |
106+
| >100% | Purple | Breach |
107+
108+
#### Expanded Mode
109+
110+
Click the chevron icon (◀/▼) in the top-left corner or `Shift+Click` anywhere to expand to a full step chart showing:
111+
112+
- **Tier 1 Metrics**: Top 3 metrics by usage (solid colored lines)
113+
- **Tier 2 Metrics**: Any metric exceeding 80% (solid colored lines)
114+
- **Tier 3 Metrics**: Remaining metrics (aggregated as grey dashed line)
115+
- **Danger Zone**: Semi-transparent band from 80-100%
116+
- **100% Limit Line**: Red dashed line at the limit threshold
117+
- **Breach Areas**: Purple shading for values above 100%
118+
119+
### Mouse Interactions
120+
121+
| Action | Mouse | Result |
122+
| --------------- | ------------------------------ | --------------------------------- |
123+
| Toggle View | Click chevron or `Shift+Click` | Switch between collapsed/expanded |
124+
| Show Tooltip | Hover | Display metric breakdown |
125+
| Center Timeline | Click on strip | Pan timeline to clicked position |
126+
| Zoom | Scroll (vertical) | Zoom at cursor position |
127+
| Pan | Scroll (horizontal) | Move timeline left/right |
128+
| Reset View | Double-click | Fit entire timeline |
129+
130+
### Keyboard Shortcuts
131+
132+
When your mouse is hovering over the Governor Limits strip, these keyboard shortcuts are available:
133+
134+
| Key | Action |
135+
| -------------- | ------------------------------------------- |
136+
| `Arrow Left` | Pan viewport left (10% of selection width) |
137+
| `Arrow Right` | Pan viewport right (10% of selection width) |
138+
| `W` / `+` | Zoom in |
139+
| `S` / `-` | Zoom out |
140+
| `Home` | Jump to timeline start |
141+
| `End` | Jump to timeline end |
142+
| `0` / `Escape` | Reset zoom (fit entire timeline) |
143+
144+
### Tooltip
145+
146+
Hovering over the metric strip displays a detailed breakdown showing:
147+
148+
- **Always-visible metrics**: CPU Time, Heap Size, SOQL Queries, Query Rows, DML Statements, DML Rows
149+
- **Top 3 by percentage**: Highest usage metrics (if not already shown)
150+
- **Critical metrics**: Any metric at or above 80%
151+
- **Summary**: Count of remaining metrics with max percentage
152+
153+
Each row displays: color swatch, metric name, percentage, and used/limit values.
154+
89155
## Navigation
90156

91157
### Zoom + Pan

log-viewer/src/core/log-parser/ApexLogParser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export class ApexLogParser {
4949
queueableJobsAddedToQueue: { used: 0, limit: 0 },
5050
mobileApexPushCalls: { used: 0, limit: 0 },
5151
byNamespace: new Map<string, Limits>(),
52+
snapshots: [],
5253
};
5354

5455
/**

log-viewer/src/core/log-parser/LogEvents.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ export class ApexLog extends LogEvent {
338338
queueableJobsAddedToQueue: { used: 0, limit: 0 },
339339
mobileApexPushCalls: { used: 0, limit: 0 },
340340
byNamespace: new Map<string, Limits>(),
341+
snapshots: [],
341342
};
342343

343344
/**
@@ -1092,6 +1093,13 @@ export class LimitUsageForNSLine extends LogEvent {
10921093
}
10931094

10941095
parser.governorLimits.byNamespace.set(this.namespace, limits);
1096+
1097+
// Track snapshots for governor limit visualization
1098+
parser.governorLimits.snapshots.push({
1099+
timestamp: this.timestamp,
1100+
namespace: this.namespace,
1101+
limits,
1102+
});
10951103
}
10961104
}
10971105

log-viewer/src/core/log-parser/types.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,22 @@ export interface Limits {
3434
mobileApexPushCalls: { used: number; limit: number };
3535
}
3636

37+
/**
38+
* A single governor limit usage snapshot at a point in time.
39+
*/
40+
export interface GovernorSnapshot {
41+
/** Timestamp in nanoseconds when this limit snapshot was recorded. */
42+
timestamp: number;
43+
/** Namespace the limits apply to (e.g., 'default', 'MyPackage'). */
44+
namespace: string;
45+
/** The limit values at this timestamp. */
46+
limits: Limits;
47+
}
48+
3749
export interface GovernorLimits extends Limits {
3850
byNamespace: Map<string, Limits>;
51+
/** Point-in-time snapshots of governor limit usage, ordered by timestamp ascending. */
52+
snapshots: GovernorSnapshot[];
3953
}
4054

4155
export interface LogIssue {

log-viewer/src/features/timeline/__tests__/keyboard-handler.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ describe('KeyboardHandler', () => {
6161
onMinimapJumpStart: jest.fn(),
6262
onMinimapJumpEnd: jest.fn(),
6363
onMinimapResetZoom: jest.fn(),
64+
// Metric strip keyboard callbacks
65+
isInMetricStripArea: jest.fn().mockReturnValue(false),
66+
onMetricStripPanViewport: jest.fn(),
67+
onMetricStripPanDepth: jest.fn(),
68+
onMetricStripZoom: jest.fn(),
69+
onMetricStripJumpStart: jest.fn(),
70+
onMetricStripJumpEnd: jest.fn(),
71+
onMetricStripResetZoom: jest.fn(),
6472
};
6573

6674
handler = new KeyboardHandler(container, viewport, callbacks);

log-viewer/src/features/timeline/optimised/ApexLogTimeline.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import { ContextMenu, type ContextMenuItem } from '../../../components/ContextMenu.js';
2121
import type { ApexLog, LogEvent } from '../../../core/log-parser/LogEvents.js';
22+
import type { GovernorSnapshot, Limits } from '../../../core/log-parser/types.js';
2223
import { vscodeMessenger } from '../../../core/messaging/VSCodeExtensionMessenger.js';
2324
import { formatDuration } from '../../../core/utility/Util.js';
2425
import { goToRow } from '../../call-tree/components/CalltreeView.js';
@@ -27,6 +28,8 @@ import type {
2728
EventNode,
2829
FindEventDetail,
2930
FindResultsEventDetail,
31+
HeatStripMetric,
32+
HeatStripTimeSeries,
3033
ModifierKeys,
3134
TimelineMarker,
3235
TimelineOptions,
@@ -38,6 +41,39 @@ import { logEventToTreeNode } from '../utils/tree-converter.js';
3841
import { FlameChart } from './FlameChart.js';
3942
import { TimelineTooltipManager } from './TimelineTooltipManager.js';
4043

44+
/**
45+
* Apex-specific metric definitions for heat strip visualization.
46+
* "Big 4" limits (CPU, SOQL, DML, Heap) have priority < 4 and are always shown.
47+
* Other limits have priority >= 4 and are only shown when > 0%.
48+
*/
49+
const APEX_METRICS: Map<keyof Limits, HeatStripMetric> = new Map([
50+
['cpuTime', { id: 'cpuTime', displayName: 'CPU Time', unit: 'ms', priority: 0 }],
51+
['soqlQueries', { id: 'soqlQueries', displayName: 'SOQL Queries', unit: '', priority: 1 }],
52+
['dmlStatements', { id: 'dmlStatements', displayName: 'DML Statements', unit: '', priority: 2 }],
53+
['heapSize', { id: 'heapSize', displayName: 'Heap Size', unit: 'bytes', priority: 3 }],
54+
['queryRows', { id: 'queryRows', displayName: 'Query Rows', unit: '', priority: 4 }],
55+
['soslQueries', { id: 'soslQueries', displayName: 'SOSL Queries', unit: '', priority: 5 }],
56+
['dmlRows', { id: 'dmlRows', displayName: 'DML Rows', unit: '', priority: 6 }],
57+
[
58+
'publishImmediateDml',
59+
{ id: 'publishImmediateDml', displayName: 'Publish Immediate DML', unit: '', priority: 7 },
60+
],
61+
['callouts', { id: 'callouts', displayName: 'Callouts', unit: '', priority: 8 }],
62+
[
63+
'emailInvocations',
64+
{ id: 'emailInvocations', displayName: 'Email Invocations', unit: '', priority: 9 },
65+
],
66+
['futureCalls', { id: 'futureCalls', displayName: 'Future Calls', unit: '', priority: 10 }],
67+
[
68+
'queueableJobsAddedToQueue',
69+
{ id: 'queueableJobsAddedToQueue', displayName: 'Queueable Jobs', unit: '', priority: 11 },
70+
],
71+
[
72+
'mobileApexPushCalls',
73+
{ id: 'mobileApexPushCalls', displayName: 'Mobile Push Calls', unit: '', priority: 12 },
74+
],
75+
]);
76+
4177
interface ApexTimelineOptions extends TimelineOptions {
4278
themeName?: string | null;
4379
}
@@ -157,6 +193,14 @@ export class ApexLogTimeline {
157193

158194
// Wire up search event listeners
159195
this.enableSearch();
196+
197+
// Transform and set heat strip time series data for visualization
198+
if (apexLog.governorLimits.snapshots.length > 0) {
199+
const heatStripSeries = this.transformGovernorToHeatStrip(apexLog.governorLimits.snapshots);
200+
this.flamechart.setHeatStripTimeSeries(heatStripSeries);
201+
} else {
202+
this.flamechart.setHeatStripTimeSeries(null);
203+
}
160204
}
161205

162206
/**
@@ -868,4 +912,42 @@ export class ApexLogTimeline {
868912

869913
document.dispatchEvent(event);
870914
}
915+
916+
// ============================================================================
917+
// APEX-SPECIFIC DATA TRANSFORMATION
918+
// ============================================================================
919+
920+
/**
921+
* Transform Apex-specific governor snapshots to generic HeatStripTimeSeries.
922+
* This converts Apex governor limits data to the generic format expected by
923+
* the heat strip visualization components.
924+
*
925+
* @param snapshots - Apex governor limit snapshots
926+
* @returns Generic heat strip time series
927+
*/
928+
private transformGovernorToHeatStrip(snapshots: GovernorSnapshot[]): HeatStripTimeSeries {
929+
// Convert APEX_METRICS to string-keyed Map for the generic interface
930+
const metrics = new Map<string, HeatStripMetric>();
931+
for (const [key, metric] of APEX_METRICS) {
932+
metrics.set(key, metric);
933+
}
934+
935+
// Transform snapshots to events
936+
const events = snapshots.map((snapshot) => {
937+
const values = new Map<string, { used: number; limit: number }>();
938+
for (const [key, value] of Object.entries(snapshot.limits) as [
939+
keyof Limits,
940+
{ used: number; limit: number },
941+
][]) {
942+
values.set(key, { used: value.used, limit: value.limit });
943+
}
944+
return {
945+
timestamp: snapshot.timestamp,
946+
namespace: snapshot.namespace,
947+
values,
948+
};
949+
});
950+
951+
return { metrics, events };
952+
}
871953
}

log-viewer/src/features/timeline/optimised/CLAUDE.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ import type { LogEvent } from '../../../core/log-parser/LogEvents.js';
2424

2525
The `flamechart.types.ts` file re-exports necessary types from external modules, keeping dependencies contained at the boundary.
2626

27+
### Metric Strip Architecture
28+
29+
The metric strip visualization (governor limits) is rendered below the main timeline and above the minimap:
30+
31+
- `MetricStripOrchestrator` manages the metric strip lifecycle and interactions
32+
- `MetricStripRenderer` renders step charts for governor limit metrics
33+
- `MetricStripManager` processes `HeatStripTimeSeries` data and classifies metrics into tiers
34+
- `ApexLogTimeline` transforms `GovernorSnapshot[]``HeatStripTimeSeries`
35+
- Apex-specific display names, units, and priority order are defined ONLY in ApexLogTimeline
36+
37+
The metric strip supports collapsed (heat-style) and expanded (step chart) views, toggled via a chevron icon.
38+
2739
## Spatial Queries: Use TemporalSegmentTree
2840

2941
**Always use TemporalSegmentTree (via RectangleManager) for frame queries. Never traverse the event tree directly.**
@@ -266,12 +278,18 @@ Tests should focus on the manager's API, not internal implementation.
266278
- Use instanced rendering where possible
267279
- Minimize state changes
268280

269-
4. **Profile before optimizing**
281+
4. **Use Mesh for filled shapes, Graphics for lines**
282+
- Mesh-based rendering (RectangleGeometry + custom shader) for rectangles/filled shapes
283+
- Single draw call for many rectangles, much faster than Graphics
284+
- Keep Graphics for thin lines (step charts, dashed lines) - mesh overhead not justified
285+
- See `MeshRectangleRenderer`, `MeshMetricStripRenderer` for examples
286+
287+
5. **Profile before optimizing**
270288
- Use Chrome DevTools Performance tab
271289
- Measure frame time, not just "feels fast"
272290
- Target 60fps (16.6ms frame budget)
273291

274-
5. **Cache computed values**
292+
6. **Cache computed values**
275293
- Viewport transforms
276294
- Color conversions
277295
- Text measurements

0 commit comments

Comments
 (0)