@zenfg/inspector 0.1.0-beta.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GUIDE.md +182 -0
- package/README.md +61 -89
- package/THEMING.md +124 -0
- package/dist/FrameGraphInspector.d.ts +23 -4
- package/dist/FrameGraphInspector.d.ts.map +1 -1
- package/dist/FrameGraphInspector.js +137 -29
- package/dist/FrameGraphInspector.js.map +1 -1
- package/dist/capture.d.ts +5 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +2 -0
- package/dist/capture.js.map +1 -0
- package/dist/debugCaptureModel.d.ts +19 -0
- package/dist/debugCaptureModel.d.ts.map +1 -1
- package/dist/debugCaptureModel.js +36 -2
- package/dist/debugCaptureModel.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/panelAliasAnalysis.d.ts +1 -0
- package/dist/panelAliasAnalysis.d.ts.map +1 -1
- package/dist/panelAliasAnalysis.js +23 -54
- package/dist/panelAliasAnalysis.js.map +1 -1
- package/dist/panelCytoscapeGraphRenderer.d.ts +15 -2
- package/dist/panelCytoscapeGraphRenderer.d.ts.map +1 -1
- package/dist/panelCytoscapeGraphRenderer.js +145 -23
- package/dist/panelCytoscapeGraphRenderer.js.map +1 -1
- package/dist/panelDetailLayout.d.ts +27 -0
- package/dist/panelDetailLayout.d.ts.map +1 -0
- package/dist/panelDetailLayout.js +174 -0
- package/dist/panelDetailLayout.js.map +1 -0
- package/dist/panelDetailStyles.d.ts +2 -0
- package/dist/panelDetailStyles.d.ts.map +1 -0
- package/dist/panelDetailStyles.js +57 -0
- package/dist/panelDetailStyles.js.map +1 -0
- package/dist/panelDiagnosticsView.d.ts +10 -1
- package/dist/panelDiagnosticsView.d.ts.map +1 -1
- package/dist/panelDiagnosticsView.js +166 -57
- package/dist/panelDiagnosticsView.js.map +1 -1
- package/dist/panelDomHelpers.d.ts.map +1 -1
- package/dist/panelDomHelpers.js +6 -5
- package/dist/panelDomHelpers.js.map +1 -1
- package/dist/panelGraphLayout.d.ts +3 -2
- package/dist/panelGraphLayout.d.ts.map +1 -1
- package/dist/panelGraphLayout.js +6 -5
- package/dist/panelGraphLayout.js.map +1 -1
- package/dist/panelGraphRenderer.d.ts +10 -0
- package/dist/panelGraphRenderer.d.ts.map +1 -1
- package/dist/panelGraphRenderer.js.map +1 -1
- package/dist/panelGraphScene.d.ts +1 -0
- package/dist/panelGraphScene.d.ts.map +1 -1
- package/dist/panelGraphScene.js +69 -32
- package/dist/panelGraphScene.js.map +1 -1
- package/dist/panelGraphSearch.d.ts +16 -0
- package/dist/panelGraphSearch.d.ts.map +1 -0
- package/dist/panelGraphSearch.js +93 -0
- package/dist/panelGraphSearch.js.map +1 -0
- package/dist/panelGraphView.d.ts +82 -0
- package/dist/panelGraphView.d.ts.map +1 -1
- package/dist/panelGraphView.js +15 -4
- package/dist/panelGraphView.js.map +1 -1
- package/dist/panelGraphVisuals.d.ts +14 -6
- package/dist/panelGraphVisuals.d.ts.map +1 -1
- package/dist/panelGraphVisuals.js +126 -51
- package/dist/panelGraphVisuals.js.map +1 -1
- package/dist/panelInspectorView.d.ts +8 -0
- package/dist/panelInspectorView.d.ts.map +1 -1
- package/dist/panelInspectorView.js +242 -48
- package/dist/panelInspectorView.js.map +1 -1
- package/dist/panelListStyles.d.ts +3 -0
- package/dist/panelListStyles.d.ts.map +1 -0
- package/dist/panelListStyles.js +155 -0
- package/dist/panelListStyles.js.map +1 -0
- package/dist/panelMemoryStyles.d.ts +3 -0
- package/dist/panelMemoryStyles.d.ts.map +1 -0
- package/dist/panelMemoryStyles.js +42 -0
- package/dist/panelMemoryStyles.js.map +1 -0
- package/dist/panelMemoryView.d.ts +13 -1
- package/dist/panelMemoryView.d.ts.map +1 -1
- package/dist/panelMemoryView.js +161 -41
- package/dist/panelMemoryView.js.map +1 -1
- package/dist/panelPassesView.d.ts +15 -0
- package/dist/panelPassesView.d.ts.map +1 -1
- package/dist/panelPassesView.js +202 -72
- package/dist/panelPassesView.js.map +1 -1
- package/dist/panelRawView.d.ts +15 -0
- package/dist/panelRawView.d.ts.map +1 -0
- package/dist/panelRawView.js +105 -0
- package/dist/panelRawView.js.map +1 -0
- package/dist/panelResourcesView.d.ts +8 -0
- package/dist/panelResourcesView.d.ts.map +1 -1
- package/dist/panelResourcesView.js +62 -31
- package/dist/panelResourcesView.js.map +1 -1
- package/dist/panelSelection.d.ts +9 -1
- package/dist/panelSelection.d.ts.map +1 -1
- package/dist/panelSelection.js +34 -4
- package/dist/panelSelection.js.map +1 -1
- package/dist/panelTheme.d.ts +13 -0
- package/dist/panelTheme.d.ts.map +1 -0
- package/dist/panelTheme.js +104 -0
- package/dist/panelTheme.js.map +1 -0
- package/dist/panelTypes.d.ts +10 -1
- package/dist/panelTypes.d.ts.map +1 -1
- package/dist/panelTypes.js.map +1 -1
- package/dist/panelVisualTheme.d.ts +147 -50
- package/dist/panelVisualTheme.d.ts.map +1 -1
- package/dist/panelVisualTheme.js +34 -44
- package/dist/panelVisualTheme.js.map +1 -1
- package/dist/panelWorkbenchHelpers.d.ts +12 -1
- package/dist/panelWorkbenchHelpers.d.ts.map +1 -1
- package/dist/panelWorkbenchHelpers.js +62 -1
- package/dist/panelWorkbenchHelpers.js.map +1 -1
- package/dist/panelWorkbenchStyles.d.ts +2 -0
- package/dist/panelWorkbenchStyles.d.ts.map +1 -0
- package/dist/panelWorkbenchStyles.js +101 -0
- package/dist/panelWorkbenchStyles.js.map +1 -0
- package/dist/panelWorkbenchView.d.ts +16 -0
- package/dist/panelWorkbenchView.d.ts.map +1 -1
- package/dist/panelWorkbenchView.js +228 -50
- package/dist/panelWorkbenchView.js.map +1 -1
- package/dist/query.js.map +1 -1
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +147 -195
- package/dist/styles.js.map +1 -1
- package/dist/theme.d.ts +22 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +10 -0
- package/dist/theme.js.map +1 -0
- package/dist/themeDefinitions.d.ts +251 -0
- package/dist/themeDefinitions.d.ts.map +1 -0
- package/dist/themeDefinitions.js +96 -0
- package/dist/themeDefinitions.js.map +1 -0
- package/dist/themePalette.d.ts +66 -0
- package/dist/themePalette.d.ts.map +1 -0
- package/dist/themePalette.js +37 -0
- package/dist/themePalette.js.map +1 -0
- package/dist/themes.css +165 -0
- package/package.json +44 -10
- package/src/FrameGraphInspector.ts +130 -31
- package/src/capture.ts +4 -0
- package/src/debugCaptureModel.ts +53 -2
- package/src/index.ts +6 -2
- package/src/panelAliasAnalysis.ts +24 -70
- package/src/panelCytoscapeGraphRenderer.ts +140 -23
- package/src/panelDetailLayout.ts +155 -0
- package/src/panelDetailStyles.ts +56 -0
- package/src/panelDiagnosticsView.ts +166 -85
- package/src/panelDomHelpers.ts +4 -5
- package/src/panelGraphLayout.ts +6 -5
- package/src/panelGraphRenderer.ts +7 -0
- package/src/panelGraphScene.ts +68 -30
- package/src/panelGraphSearch.ts +96 -0
- package/src/panelGraphView.ts +14 -4
- package/src/panelGraphVisuals.ts +122 -52
- package/src/panelInspectorView.ts +227 -48
- package/src/panelListStyles.ts +154 -0
- package/src/panelMemoryStyles.ts +41 -0
- package/src/panelMemoryView.ts +178 -53
- package/src/panelPassesView.ts +190 -137
- package/src/panelRawView.ts +99 -0
- package/src/panelResourcesView.ts +69 -56
- package/src/panelSelection.ts +37 -5
- package/src/panelTheme.ts +85 -0
- package/src/panelTypes.ts +7 -1
- package/src/panelVisualTheme.ts +38 -49
- package/src/panelWorkbenchHelpers.ts +60 -2
- package/src/panelWorkbenchStyles.ts +100 -0
- package/src/panelWorkbenchView.ts +201 -52
- package/src/query.ts +11 -11
- package/src/styles.ts +148 -195
- package/src/theme.ts +24 -0
- package/src/themeDefinitions.ts +103 -0
- package/src/themePalette.ts +44 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Appended after the base styles so the axis and every resource share one grid. */
|
|
2
|
+
export const PANEL_MEMORY_CSS = `
|
|
3
|
+
.zenfg-inspector-memory-view {
|
|
4
|
+
grid-template-rows: auto auto auto minmax(0, 1fr);
|
|
5
|
+
container-type: inline-size;
|
|
6
|
+
container-name: zenfg-memory;
|
|
7
|
+
}
|
|
8
|
+
.zenfg-inspector-memory-summary { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
9
|
+
.zenfg-inspector-memory-summary > div { border: 0; background: transparent; }
|
|
10
|
+
.zenfg-inspector-memory-summary span { color: var(--fgd-muted); font: var(--fgd-font-size-small)/1.4 var(--fgd-font-ui); }
|
|
11
|
+
.zenfg-inspector-memory-summary strong { font-size: var(--fgd-font-size); white-space: normal; overflow-wrap: anywhere; }
|
|
12
|
+
.zenfg-inspector-memory-note { margin: 0; padding: 6px var(--fgd-space-3); font: var(--fgd-font-size-small)/1.45 var(--fgd-font-ui); }
|
|
13
|
+
.zenfg-inspector-memory-timeline { align-content: start; grid-template-columns: minmax(0, 1fr); min-width: 720px; font-size: var(--fgd-font-size-small); }
|
|
14
|
+
.zenfg-inspector-memory-axis,
|
|
15
|
+
.zenfg-inspector-memory-resource {
|
|
16
|
+
grid-template-columns: minmax(165px, 0.9fr) 86px minmax(300px, 2.4fr) 82px;
|
|
17
|
+
column-gap: 10px;
|
|
18
|
+
padding-left: var(--fgd-space-2);
|
|
19
|
+
padding-right: var(--fgd-space-2);
|
|
20
|
+
}
|
|
21
|
+
.zenfg-inspector-memory-axis { min-height: var(--fgd-row-height); z-index: 4; font-size: var(--fgd-font-size-small); }
|
|
22
|
+
.zenfg-inspector-memory-axis > :first-child,
|
|
23
|
+
.zenfg-inspector-memory-name { position: sticky; left: 0; z-index: 2; background: var(--fgd-canvas); }
|
|
24
|
+
.zenfg-inspector-memory-axis-track,
|
|
25
|
+
.zenfg-inspector-memory-track { grid-column: 3; }
|
|
26
|
+
.zenfg-inspector-memory-axis-track > span { transform: translateX(-50%); border-left: 0; font-size: var(--fgd-font-size-small); }
|
|
27
|
+
.zenfg-inspector-memory-gridline { position: absolute; height: 100%; top: 0; border-left: 1px dotted var(--fgd-border-strong); }
|
|
28
|
+
.zenfg-inspector-memory-resource { min-height: var(--fgd-row-height); }
|
|
29
|
+
.zenfg-inspector-memory-name { display: flex; align-items: center; min-height: var(--fgd-row-height); gap: 6px; min-width: 0; }
|
|
30
|
+
.zenfg-inspector-memory-name > .zenfg-inspector-relation-button { min-width: 0; overflow: hidden; text-overflow: ellipsis; font-family: var(--fgd-font-ui); }
|
|
31
|
+
.zenfg-inspector-memory-reveal { flex: 0 0 auto; padding: 2px var(--fgd-space-1); border: 1px solid var(--fgd-border); border-radius: var(--fgd-radius-sm); color: var(--fgd-text-secondary); background: var(--fgd-panel); font: var(--fgd-font-size-small)/1.4 var(--fgd-font-ui); cursor: pointer; }
|
|
32
|
+
.zenfg-inspector-memory-reveal:focus-visible { outline: 2px solid var(--fgd-accent); outline-offset: 1px; }
|
|
33
|
+
.zenfg-inspector-memory-resource.selected .zenfg-inspector-memory-name { background: var(--fgd-panel); box-shadow: inset 2px 0 var(--fgd-accent); }
|
|
34
|
+
.zenfg-inspector-memory-allocation { min-width: 0; align-items: flex-start; font-family: var(--fgd-font-ui); }
|
|
35
|
+
.zenfg-inspector-memory-allocation > button { min-width: 0; overflow: hidden; }
|
|
36
|
+
.zenfg-inspector-memory-allocation > span { min-width: 0; overflow-wrap: anywhere; font-size: var(--fgd-font-size-small); }
|
|
37
|
+
.zenfg-inspector-memory-bar { min-width: 0; }
|
|
38
|
+
@container zenfg-memory (max-width: 660px) {
|
|
39
|
+
.zenfg-inspector-memory-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
40
|
+
}
|
|
41
|
+
`;
|
package/src/panelMemoryView.ts
CHANGED
|
@@ -1,38 +1,73 @@
|
|
|
1
1
|
import type { FrameGraphDebugResource, FrameGraphDebugViewModel } from './debugCaptureModel.ts';
|
|
2
|
-
import { analyzeSnapshotAliases } from './panelAliasAnalysis.ts';
|
|
2
|
+
import { analyzeSnapshotAliases, type AliasAnalysisAllocation } from './panelAliasAnalysis.ts';
|
|
3
3
|
import { createMutedText, labelResource } from './panelDomHelpers.ts';
|
|
4
4
|
import type { Selection } from './panelTypes.ts';
|
|
5
5
|
import {
|
|
6
|
-
createRelationButton,
|
|
7
|
-
formatEstimatedBytes,
|
|
8
|
-
|
|
9
|
-
type WorkbenchCallbacks,
|
|
10
|
-
updateSelectedRows,
|
|
6
|
+
createFilterSelect, createRelationButton, createSearchInput, createViewToolbar,
|
|
7
|
+
formatEstimatedBytes, formatEstimateCoverage, groupPath, registerSelectable,
|
|
8
|
+
selectionKey, type WorkbenchCallbacks, updateSelectedRows,
|
|
11
9
|
} from './panelWorkbenchHelpers.ts';
|
|
12
10
|
|
|
11
|
+
type MemoryFilter = 'all' | 'aliased' | 'single' | 'unallocated';
|
|
12
|
+
type MemorySort = 'allocation' | 'size';
|
|
13
|
+
|
|
13
14
|
export class MemoryView {
|
|
14
15
|
readonly root = document.createElement('section');
|
|
15
16
|
private readonly metrics = document.createElement('div');
|
|
16
17
|
private readonly scroller = document.createElement('div');
|
|
17
18
|
private readonly timeline = document.createElement('div');
|
|
19
|
+
private readonly count = document.createElement('span');
|
|
20
|
+
private readonly search: HTMLInputElement;
|
|
21
|
+
private readonly filterSelect: HTMLSelectElement;
|
|
18
22
|
private readonly rows = new Map<string, HTMLElement[]>();
|
|
19
23
|
private snapshot: FrameGraphDebugViewModel | undefined;
|
|
20
24
|
private selected: Selection | undefined;
|
|
25
|
+
private query = '';
|
|
26
|
+
private filter: MemoryFilter = 'all';
|
|
27
|
+
private sort: MemorySort = 'allocation';
|
|
21
28
|
|
|
22
29
|
constructor(private readonly callbacks: WorkbenchCallbacks, idPrefix: string) {
|
|
23
30
|
this.root.className = 'zenfg-inspector-view zenfg-inspector-memory-view';
|
|
24
31
|
this.root.id = `${idPrefix}-view-memory`;
|
|
25
32
|
this.root.setAttribute('role', 'tabpanel');
|
|
26
33
|
this.metrics.className = 'zenfg-inspector-memory-summary';
|
|
34
|
+
const toolbar = createViewToolbar('Memory filters');
|
|
35
|
+
this.search = createSearchInput('Search memory resources or allocations', '', (value) => {
|
|
36
|
+
this.query = value;
|
|
37
|
+
this.renderTimeline();
|
|
38
|
+
});
|
|
39
|
+
this.filterSelect = createFilterSelect('Memory allocation status', this.filter, [
|
|
40
|
+
['all', 'All'], ['aliased', 'Aliased'], ['single', 'Single'], ['unallocated', 'Unallocated'],
|
|
41
|
+
], (value) => {
|
|
42
|
+
this.filter = value as MemoryFilter;
|
|
43
|
+
this.renderTimeline();
|
|
44
|
+
});
|
|
45
|
+
const sort = createFilterSelect('Memory sort', this.sort, [
|
|
46
|
+
['allocation', 'Allocation order'], ['size', 'Size: largest first'],
|
|
47
|
+
], (value) => {
|
|
48
|
+
this.sort = value as MemorySort;
|
|
49
|
+
this.renderTimeline();
|
|
50
|
+
});
|
|
51
|
+
const clear = document.createElement('button');
|
|
52
|
+
clear.type = 'button';
|
|
53
|
+
clear.textContent = 'Clear filters';
|
|
54
|
+
clear.addEventListener('click', () => this.clearFilters());
|
|
55
|
+
this.count.className = 'zenfg-inspector-result-count';
|
|
56
|
+
this.count.setAttribute('role', 'status');
|
|
57
|
+
toolbar.append(this.search, this.filterSelect, sort, clear, this.count);
|
|
58
|
+
const note = createMutedText('Whole-snapshot estimates, not total GPU memory or a measured peak. Bars include the first and last execution slots; missing lifetimes have no bar.');
|
|
59
|
+
note.classList.add('zenfg-inspector-memory-note');
|
|
27
60
|
this.scroller.className = 'zenfg-inspector-memory-scroller';
|
|
61
|
+
this.scroller.setAttribute('aria-label', 'Resource lifetimes by execution slot');
|
|
28
62
|
this.timeline.className = 'zenfg-inspector-memory-timeline';
|
|
29
63
|
this.scroller.appendChild(this.timeline);
|
|
30
|
-
this.root.append(this.metrics, this.scroller);
|
|
64
|
+
this.root.append(this.metrics, toolbar, note, this.scroller);
|
|
31
65
|
}
|
|
32
66
|
|
|
33
67
|
setSnapshot(snapshot: FrameGraphDebugViewModel): void {
|
|
34
68
|
this.snapshot = snapshot;
|
|
35
|
-
this.
|
|
69
|
+
this.renderMetrics();
|
|
70
|
+
this.renderTimeline();
|
|
36
71
|
}
|
|
37
72
|
|
|
38
73
|
setSelection(selected: Selection | undefined): void {
|
|
@@ -40,58 +75,116 @@ export class MemoryView {
|
|
|
40
75
|
updateSelectedRows(this.rows, selected);
|
|
41
76
|
}
|
|
42
77
|
|
|
43
|
-
|
|
78
|
+
/** Explicit navigation can remove filters; ordinary selection never does. */
|
|
79
|
+
reveal(selection: Selection): void {
|
|
80
|
+
this.clearFilters();
|
|
81
|
+
this.setSelection(selection);
|
|
82
|
+
const row = this.rows.get(selectionKey(selection))?.[0];
|
|
83
|
+
row?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });
|
|
84
|
+
row?.querySelector<HTMLButtonElement>('.zenfg-inspector-relation-button')?.focus({ preventScroll: true });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private clearFilters(): void {
|
|
88
|
+
this.query = '';
|
|
89
|
+
this.filter = 'all';
|
|
90
|
+
this.search.value = '';
|
|
91
|
+
this.filterSelect.value = 'all';
|
|
92
|
+
this.renderTimeline();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private renderMetrics(): void {
|
|
44
96
|
const snapshot = this.snapshot;
|
|
45
97
|
if (!snapshot) return;
|
|
46
98
|
const metrics = snapshot.metrics;
|
|
99
|
+
const coverage = metrics.estimatedCoverage;
|
|
100
|
+
const allocationAvailable = snapshot.protocol.memory.allocationReport.status === 'available';
|
|
101
|
+
const pool = snapshot.resourcePool;
|
|
47
102
|
this.metrics.replaceChildren(
|
|
48
|
-
this.createMetric('
|
|
49
|
-
|
|
50
|
-
this.createMetric('
|
|
51
|
-
|
|
103
|
+
this.createMetric('Resource estimate', formatEstimateCoverage(metrics.transientEstimatedByteSize, coverage.transient),
|
|
104
|
+
'The declared estimated sizes of all transient logical resources.'),
|
|
105
|
+
this.createMetric('Logical capacity', allocationAvailable ? formatEstimateCoverage(metrics.logicalCapacityBytes, coverage.logical) : 'Not collected',
|
|
106
|
+
'Assigned allocation capacity counted once per logical transient resource.'),
|
|
107
|
+
this.createMetric('Physical estimate', allocationAvailable ? formatEstimateCoverage(metrics.physicalEstimatedBytes, coverage.physical) : 'Not collected',
|
|
108
|
+
'Estimated allocation sizes, counting each physical allocation once.'),
|
|
109
|
+
this.createMetric('Alias reuse', allocationAvailable ? formatEstimatedBytes(metrics.aliasReuseBytes) : 'Not collected',
|
|
110
|
+
'Logical allocation capacity minus physical allocation estimate; not a measured saving.'),
|
|
111
|
+
this.createMetric('Pool retained', pool.status === 'available' ? formatEstimatedBytes(pool.estimatedRetainedBytes) : 'Not collected',
|
|
112
|
+
'Estimated bytes retained by the resource pool, including allocations outside this frame.'),
|
|
113
|
+
this.createMetric('Allocations', allocationAvailable ? `${snapshot.physicalAllocations.length} · ${metrics.aliasedAllocationCount} aliased` : 'Not collected',
|
|
114
|
+
'Physical allocation count for the entire snapshot.'),
|
|
52
115
|
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private renderTimeline(): void {
|
|
119
|
+
const snapshot = this.snapshot;
|
|
120
|
+
if (!snapshot) return;
|
|
53
121
|
this.rows.clear();
|
|
54
122
|
this.timeline.replaceChildren();
|
|
55
123
|
const analysis = analyzeSnapshotAliases(snapshot);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
124
|
+
const ticks = analysis.hasLifetimes ? this.executionTicks(analysis.minUse, analysis.maxUse) : [];
|
|
125
|
+
this.timeline.appendChild(this.createAxis(analysis.minUse, analysis.maxUse, ticks));
|
|
126
|
+
const query = this.query.trim().toLocaleLowerCase();
|
|
127
|
+
const matches = (resource: FrameGraphDebugResource): boolean => !query || [
|
|
128
|
+
resource.id, resource.label, groupPath(snapshot, resource.debugGroupId),
|
|
129
|
+
].some((value) => value?.toLocaleLowerCase().includes(query));
|
|
130
|
+
const groups = analysis.allocations.flatMap((group) => {
|
|
131
|
+
if (this.filter === 'unallocated' || (this.filter === 'aliased' && !group.aliases) || (this.filter === 'single' && group.aliases)) return [];
|
|
132
|
+
const allocationMatch = !query || [group.allocation.id, group.allocation.compatibilityClassId]
|
|
133
|
+
.some((value) => value.toLocaleLowerCase().includes(query));
|
|
134
|
+
const resources = allocationMatch ? group.resources : group.resources.filter(matches);
|
|
135
|
+
return resources.length > 0 || allocationMatch ? [{ ...group, resources }] : [];
|
|
136
|
+
});
|
|
137
|
+
if (this.sort === 'size') groups.sort((a, b) => compareSize(a.allocation.estimatedByteSize, b.allocation.estimatedByteSize));
|
|
138
|
+
let shown = 0;
|
|
139
|
+
for (const group of groups) {
|
|
140
|
+
this.timeline.appendChild(this.createAllocationHeader(group));
|
|
69
141
|
for (const resource of group.resources) {
|
|
70
|
-
this.timeline.appendChild(this.createResourceRow(resource, analysis.minUse, analysis.maxUse));
|
|
142
|
+
this.timeline.appendChild(this.createResourceRow(resource, analysis.minUse, analysis.maxUse, ticks));
|
|
143
|
+
shown++;
|
|
71
144
|
}
|
|
72
145
|
}
|
|
73
146
|
|
|
74
|
-
const
|
|
75
|
-
resource.
|
|
76
|
-
)
|
|
147
|
+
const allUnallocated = snapshot.resources.filter((resource) => resource.origin === 'transient'
|
|
148
|
+
&& (resource.physicalResourceId === undefined || !snapshot.allocationById.has(resource.physicalResourceId)));
|
|
149
|
+
const unallocated = this.filter === 'all' || this.filter === 'unallocated' ? allUnallocated.filter(matches) : [];
|
|
150
|
+
if (this.sort === 'size') unallocated.sort((a, b) => compareSize(a.estimatedByteSize, b.estimatedByteSize));
|
|
77
151
|
if (unallocated.length > 0) {
|
|
78
152
|
const header = document.createElement('div');
|
|
79
153
|
header.className = 'zenfg-inspector-memory-allocation muted';
|
|
80
154
|
header.dataset.kind = 'unallocated';
|
|
81
|
-
header.textContent =
|
|
155
|
+
header.textContent = snapshot.protocol.memory.allocationReport.status === 'available'
|
|
156
|
+
? 'Unallocated transient resources' : 'Transient resources — allocation report unavailable';
|
|
82
157
|
this.timeline.appendChild(header);
|
|
83
158
|
for (const resource of unallocated) {
|
|
84
|
-
this.timeline.appendChild(this.createResourceRow(resource, analysis.minUse, analysis.maxUse));
|
|
159
|
+
this.timeline.appendChild(this.createResourceRow(resource, analysis.minUse, analysis.maxUse, ticks));
|
|
160
|
+
shown++;
|
|
85
161
|
}
|
|
86
162
|
}
|
|
87
|
-
|
|
88
|
-
|
|
163
|
+
const total = analysis.allocations.reduce((sum, group) => sum + group.resources.length, 0) + allUnallocated.length;
|
|
164
|
+
this.count.textContent = `${shown} / ${total} resources`;
|
|
165
|
+
if (groups.length === 0 && unallocated.length === 0) {
|
|
166
|
+
this.timeline.appendChild(createMutedText(total > 0 ? 'No resources match these filters.' : 'No transient resource lifetimes.'));
|
|
89
167
|
}
|
|
90
168
|
updateSelectedRows(this.rows, this.selected);
|
|
91
169
|
}
|
|
92
170
|
|
|
93
|
-
private
|
|
171
|
+
private createAllocationHeader(group: AliasAnalysisAllocation): HTMLElement {
|
|
172
|
+
const selection: Selection = { kind: 'allocation', id: group.allocation.id };
|
|
173
|
+
const header = document.createElement('div');
|
|
174
|
+
header.className = 'zenfg-inspector-memory-allocation';
|
|
175
|
+
header.dataset.kind = group.allocation.kind;
|
|
176
|
+
registerSelectable(this.rows, header, selection, this.callbacks);
|
|
177
|
+
const name = createRelationButton(`Allocation ${group.allocation.id.replace(/^allocation:/, '')}`, selection, this.callbacks.onSelect);
|
|
178
|
+
name.title = group.allocation.id;
|
|
179
|
+
const meta = document.createElement('span');
|
|
180
|
+
meta.textContent = `${group.allocation.kind} · class ${group.allocation.compatibilityClassId} · ${formatEstimatedBytes(group.allocation.estimatedByteSize)} · ${group.aliases ? `aliased ×${group.allocation.resourceIds.length}` : 'single'}`;
|
|
181
|
+
header.append(name, meta);
|
|
182
|
+
return header;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private createMetric(label: string, value: string, explanation: string): HTMLElement {
|
|
94
186
|
const item = document.createElement('div');
|
|
187
|
+
item.title = explanation;
|
|
95
188
|
const term = document.createElement('span');
|
|
96
189
|
term.textContent = label;
|
|
97
190
|
const amount = document.createElement('strong');
|
|
@@ -100,48 +193,74 @@ export class MemoryView {
|
|
|
100
193
|
return item;
|
|
101
194
|
}
|
|
102
195
|
|
|
103
|
-
private
|
|
196
|
+
private executionTicks(minUse: number, maxUse: number): number[] {
|
|
197
|
+
const count = Math.min(6, maxUse - minUse + 1);
|
|
198
|
+
return Array.from({ length: count }, (_, index) => count === 1 ? minUse : minUse + Math.round(index * (maxUse - minUse) / (count - 1)));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private createAxis(minUse: number, maxUse: number, ticks: readonly number[]): HTMLElement {
|
|
104
202
|
const axis = document.createElement('div');
|
|
105
203
|
axis.className = 'zenfg-inspector-memory-axis';
|
|
106
204
|
const label = document.createElement('span');
|
|
107
|
-
label.textContent = '
|
|
205
|
+
label.textContent = 'Resource';
|
|
206
|
+
const lifetime = document.createElement('span');
|
|
207
|
+
lifetime.textContent = 'First–last slot';
|
|
108
208
|
const track = document.createElement('div');
|
|
109
209
|
track.className = 'zenfg-inspector-memory-axis-track';
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
210
|
+
track.setAttribute('aria-label', 'Execution slots');
|
|
211
|
+
if (ticks.length === 0) track.textContent = 'No execution lifetimes';
|
|
212
|
+
const slots = maxUse - minUse + 1;
|
|
213
|
+
for (const value of ticks) {
|
|
113
214
|
const tick = document.createElement('span');
|
|
114
|
-
tick.style.left = `${((value - minUse) /
|
|
215
|
+
tick.style.left = `${((value - minUse + 0.5) / slots) * 100}%`;
|
|
115
216
|
tick.textContent = String(value);
|
|
116
217
|
track.appendChild(tick);
|
|
117
218
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
tick.textContent = String(maxUse);
|
|
122
|
-
track.appendChild(tick);
|
|
123
|
-
}
|
|
124
|
-
axis.append(label, track);
|
|
219
|
+
const size = document.createElement('span');
|
|
220
|
+
size.textContent = 'Estimate';
|
|
221
|
+
axis.append(label, lifetime, track, size);
|
|
125
222
|
return axis;
|
|
126
223
|
}
|
|
127
224
|
|
|
128
|
-
private createResourceRow(resource: FrameGraphDebugResource, minUse: number, maxUse: number): HTMLElement {
|
|
225
|
+
private createResourceRow(resource: FrameGraphDebugResource, minUse: number, maxUse: number, ticks: readonly number[]): HTMLElement {
|
|
129
226
|
const selection: Selection = { kind: 'resource', id: resource.id };
|
|
130
227
|
const row = document.createElement('div');
|
|
131
228
|
row.className = 'zenfg-inspector-memory-resource';
|
|
132
|
-
row.dataset.kind = resource.kind;
|
|
229
|
+
row.dataset.kind = resource.kind;
|
|
133
230
|
registerSelectable(this.rows, row, selection, this.callbacks);
|
|
134
|
-
const name =
|
|
231
|
+
const name = document.createElement('div');
|
|
232
|
+
name.className = 'zenfg-inspector-memory-name';
|
|
233
|
+
const button = createRelationButton(labelResource(resource), selection, this.callbacks.onSelect);
|
|
234
|
+
button.title = resource.id;
|
|
235
|
+
name.appendChild(button);
|
|
236
|
+
if (this.callbacks.onReveal) {
|
|
237
|
+
const reveal = document.createElement('button');
|
|
238
|
+
reveal.type = 'button';
|
|
239
|
+
reveal.className = 'zenfg-inspector-memory-reveal';
|
|
240
|
+
reveal.textContent = 'Locate';
|
|
241
|
+
reveal.title = 'Locate in Resources';
|
|
242
|
+
reveal.setAttribute('aria-label', `Locate ${labelResource(resource)} in Resources`);
|
|
243
|
+
reveal.addEventListener('click', () => this.callbacks.onReveal?.(selection, 'resources'));
|
|
244
|
+
name.appendChild(reveal);
|
|
245
|
+
}
|
|
135
246
|
const range = document.createElement('span');
|
|
136
|
-
range.textContent = resource.lifetime ? `${resource.lifetime.firstUse}–${resource.lifetime.lastUse}` : '
|
|
247
|
+
range.textContent = resource.lifetime ? `${resource.lifetime.firstUse}–${resource.lifetime.lastUse}` : 'No lifetime';
|
|
137
248
|
const track = document.createElement('div');
|
|
138
249
|
track.className = 'zenfg-inspector-memory-track';
|
|
250
|
+
const slots = maxUse - minUse + 1;
|
|
251
|
+
for (const value of ticks) {
|
|
252
|
+
const grid = document.createElement('span');
|
|
253
|
+
grid.className = 'zenfg-inspector-memory-gridline';
|
|
254
|
+
grid.style.left = `${((value - minUse + 0.5) / slots) * 100}%`;
|
|
255
|
+
grid.setAttribute('aria-hidden', 'true');
|
|
256
|
+
track.appendChild(grid);
|
|
257
|
+
}
|
|
139
258
|
const bar = document.createElement('span');
|
|
140
259
|
bar.className = `zenfg-inspector-memory-bar ${resource.kind}`;
|
|
141
260
|
if (resource.lifetime) {
|
|
142
|
-
const slots = Math.max(1, maxUse - minUse + 1);
|
|
143
261
|
bar.style.left = `${((resource.lifetime.firstUse - minUse) / slots) * 100}%`;
|
|
144
262
|
bar.style.width = `${((resource.lifetime.lastUse - resource.lifetime.firstUse + 1) / slots) * 100}%`;
|
|
263
|
+
bar.title = `Execution slots ${resource.lifetime.firstUse}–${resource.lifetime.lastUse} (inclusive)`;
|
|
145
264
|
} else {
|
|
146
265
|
bar.classList.add('empty');
|
|
147
266
|
}
|
|
@@ -152,3 +271,9 @@ export class MemoryView {
|
|
|
152
271
|
return row;
|
|
153
272
|
}
|
|
154
273
|
}
|
|
274
|
+
|
|
275
|
+
function compareSize(a: number | undefined, b: number | undefined): number {
|
|
276
|
+
if (a === undefined) return b === undefined ? 0 : 1;
|
|
277
|
+
if (b === undefined) return -1;
|
|
278
|
+
return b - a;
|
|
279
|
+
}
|