@reicek/neataptic-ts 0.1.25 → 0.1.26
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/.github/copilot-instructions.md +11 -0
- package/.github/skills/trace-analyzer-extension/SKILL.md +3 -3
- package/.github/skills/trace-analyzer-extension/assets/extension-checklist.md +1 -1
- package/.github/skills/trace-analyzer-extension/references/analyzer-extension-workflow.md +1 -1
- package/.github/skills/trace-audit-reporting/SKILL.md +3 -3
- package/.github/skills/trace-audit-reporting/references/trace-analysis-workflow.md +1 -1
- package/package.json +19 -13
- package/plans/Flappy_Bird_Folder_Documentation_Pass.md +4 -4
- package/plans/README.md +24 -0
- package/plans/Roadmap.md +62 -40
- package/plans/analyze-trace-solid-split.plans.md +66 -0
- package/plans/architecture-solid-split.plans.md +9 -15
- package/plans/asciiMaze-typescript-repair.plans.md +1 -1
- package/plans/generate-docs-solid-split.plans.md +87 -0
- package/plans/methods-docs.plans.md +25 -1
- package/plans/methods-solid-split.plans.md +14 -14
- package/plans/neat-docs.plans.md +9 -1
- package/plans/neat-test-surface-repair.plans.md +1 -1
- package/plans/render-docs-html-solid-split.plans.md +68 -0
- package/plans/src-no-explicit-any-cleanup.plans.md +1 -1
- package/plans/utils-docs.plans.md +6 -1
- package/scripts/analyze-trace/analyze-trace.analysis.ts +479 -0
- package/scripts/analyze-trace/analyze-trace.constants.ts +35 -0
- package/scripts/analyze-trace/analyze-trace.io.ts +69 -0
- package/scripts/analyze-trace/analyze-trace.report.ts +100 -0
- package/scripts/analyze-trace/analyze-trace.shared.ts +116 -0
- package/scripts/analyze-trace/analyze-trace.ts +45 -0
- package/scripts/analyze-trace/analyze-trace.types.ts +72 -0
- package/scripts/assets/theme.css +80 -23
- package/scripts/copy-examples.ts +239 -0
- package/scripts/export-onnx.ts +223 -0
- package/scripts/generate-bench-tables.ts +378 -37
- package/scripts/generate-docs/generate-docs.constants.ts +107 -0
- package/scripts/generate-docs/generate-docs.order.ts +355 -0
- package/scripts/generate-docs/generate-docs.state.ts +31 -0
- package/scripts/generate-docs/generate-docs.targets.ts +165 -0
- package/scripts/generate-docs/generate-docs.ts +63 -0
- package/scripts/generate-docs/generate-docs.types.ts +112 -0
- package/scripts/generate-docs/output/generate-docs.output.folder-index.utils.ts +167 -0
- package/scripts/generate-docs/output/generate-docs.output.ordering.utils.ts +353 -0
- package/scripts/generate-docs/output/generate-docs.output.readme.utils.ts +420 -0
- package/scripts/generate-docs/output/generate-docs.output.ts +123 -0
- package/scripts/generate-docs/output/generate-docs.output.warnings.utils.ts +219 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.collection.utils.ts +365 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.jsdoc.utils.ts +373 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.normalize.utils.ts +155 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.render.utils.ts +149 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.signature.utils.ts +289 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.ts +11 -0
- package/scripts/mermaid-cli.mjs +102 -22
- package/scripts/mermaid-cli.ts +736 -0
- package/scripts/render-docs-html/render-docs-html.assets.ts +54 -0
- package/scripts/render-docs-html/render-docs-html.mermaid.ts +245 -0
- package/scripts/{render-docs-html.sidebar.ts → render-docs-html/render-docs-html.navigation.ts} +141 -144
- package/scripts/render-docs-html/render-docs-html.pages.ts +333 -0
- package/scripts/render-docs-html/render-docs-html.shared.ts +333 -0
- package/scripts/render-docs-html/render-docs-html.types.ts +42 -0
- package/scripts/render-docs-html.ts +23 -587
- package/scripts/run-docs.ts +238 -0
- package/scripts/write-dist-docs-pkg.ts +40 -0
- package/src/README.md +75 -75
- package/src/architecture/connection/README.md +5 -5
- package/src/architecture/layer/README.md +508 -508
- package/src/architecture/network/README.md +1458 -1458
- package/src/architecture/network/activate/README.md +694 -694
- package/src/architecture/network/bootstrap/README.md +77 -77
- package/src/architecture/network/connect/README.md +74 -74
- package/src/architecture/network/deterministic/README.md +135 -135
- package/src/architecture/network/evolve/README.md +364 -364
- package/src/architecture/network/gating/README.md +130 -130
- package/src/architecture/network/genetic/README.md +399 -399
- package/src/architecture/network/mutate/README.md +897 -897
- package/src/architecture/network/onnx/README.md +720 -720
- package/src/architecture/network/onnx/export/README.md +728 -728
- package/src/architecture/network/onnx/export/layers/README.md +450 -450
- package/src/architecture/network/onnx/import/README.md +618 -618
- package/src/architecture/network/onnx/schema/README.md +32 -32
- package/src/architecture/network/prune/README.md +245 -245
- package/src/architecture/network/remove/README.md +135 -135
- package/src/architecture/network/runtime/README.md +106 -106
- package/src/architecture/network/serialize/README.md +542 -542
- package/src/architecture/network/slab/README.md +608 -608
- package/src/architecture/network/standalone/README.md +212 -212
- package/src/architecture/network/stats/README.md +84 -84
- package/src/architecture/network/topology/README.md +465 -465
- package/src/architecture/network/training/README.md +200 -200
- package/src/architecture/node/README.md +5 -5
- package/src/architecture/nodePool/README.md +14 -14
- package/src/methods/README.md +99 -99
- package/src/methods/activation/README.md +189 -189
- package/src/methods/cost/README.md +131 -131
- package/src/methods/rate/README.md +86 -86
- package/src/multithreading/README.md +77 -77
- package/src/multithreading/workers/browser/README.md +8 -8
- package/src/multithreading/workers/node/README.md +8 -8
- package/src/neat/README.md +148 -148
- package/src/neat/adaptive/README.md +120 -120
- package/src/neat/adaptive/acceptance/README.md +40 -40
- package/src/neat/adaptive/complexity/README.md +137 -137
- package/src/neat/adaptive/core/README.md +197 -197
- package/src/neat/adaptive/lineage/README.md +90 -90
- package/src/neat/adaptive/mutation/README.md +284 -284
- package/src/neat/compat/README.md +43 -43
- package/src/neat/compat/core/README.md +90 -90
- package/src/neat/diversity/README.md +35 -35
- package/src/neat/diversity/core/README.md +88 -88
- package/src/neat/evaluate/README.md +85 -85
- package/src/neat/evaluate/auto-distance/README.md +75 -75
- package/src/neat/evaluate/entropy-compat/README.md +37 -37
- package/src/neat/evaluate/entropy-sharing/README.md +43 -43
- package/src/neat/evaluate/fitness/README.md +23 -23
- package/src/neat/evaluate/novelty/README.md +120 -120
- package/src/neat/evaluate/objectives/README.md +17 -17
- package/src/neat/evaluate/shared/README.md +94 -94
- package/src/neat/evolve/README.md +96 -96
- package/src/neat/evolve/adaptive/README.md +60 -60
- package/src/neat/evolve/objectives/README.md +63 -63
- package/src/neat/evolve/offspring/README.md +56 -56
- package/src/neat/evolve/population/README.md +171 -171
- package/src/neat/evolve/runtime/README.md +79 -79
- package/src/neat/evolve/speciation/README.md +74 -74
- package/src/neat/evolve/warnings/README.md +10 -10
- package/src/neat/export/README.md +114 -114
- package/src/neat/helpers/README.md +50 -50
- package/src/neat/init/README.md +9 -9
- package/src/neat/lineage/core/README.md +101 -101
- package/src/neat/multiobjective/category/README.md +74 -74
- package/src/neat/multiobjective/crowding/README.md +272 -272
- package/src/neat/multiobjective/dominance/README.md +171 -171
- package/src/neat/multiobjective/fronts/README.md +68 -68
- package/src/neat/multiobjective/metrics/README.md +43 -43
- package/src/neat/multiobjective/objectives/README.md +31 -31
- package/src/neat/multiobjective/shared/README.md +27 -27
- package/src/neat/mutation/README.md +97 -97
- package/src/neat/mutation/add-conn/README.md +115 -115
- package/src/neat/mutation/add-node/README.md +126 -126
- package/src/neat/mutation/flow/README.md +149 -149
- package/src/neat/mutation/repair/README.md +185 -185
- package/src/neat/mutation/select/README.md +117 -117
- package/src/neat/mutation/shared/README.md +32 -32
- package/src/neat/objectives/README.md +25 -25
- package/src/neat/objectives/core/README.md +67 -67
- package/src/neat/pruning/README.md +40 -40
- package/src/neat/pruning/core/README.md +171 -171
- package/src/neat/pruning/facade/README.md +32 -32
- package/src/neat/rng/README.md +104 -104
- package/src/neat/rng/core/README.md +137 -137
- package/src/neat/rng/facade/README.md +50 -50
- package/src/neat/selection/README.md +111 -111
- package/src/neat/selection/core/README.md +227 -227
- package/src/neat/selection/facade/README.md +61 -61
- package/src/neat/shared/README.md +163 -163
- package/src/neat/speciation/README.md +31 -31
- package/src/neat/speciation/threshold/README.md +35 -35
- package/src/neat/species/README.md +25 -25
- package/src/neat/species/core/README.md +20 -20
- package/src/neat/species/core/shared/README.md +18 -18
- package/src/neat/species/history/context/README.md +22 -22
- package/src/neat/telemetry/accessors/README.md +58 -58
- package/src/neat/telemetry/exports/README.md +233 -233
- package/src/neat/telemetry/facade/README.md +252 -252
- package/src/neat/telemetry/facade/archive/README.md +57 -57
- package/src/neat/telemetry/facade/buffer/README.md +43 -43
- package/src/neat/telemetry/facade/lineage/README.md +12 -12
- package/src/neat/telemetry/facade/objectives/README.md +44 -44
- package/src/neat/telemetry/facade/runtime/README.md +26 -26
- package/src/neat/telemetry/facade/species/README.md +27 -27
- package/src/neat/telemetry/metrics/README.md +696 -696
- package/src/neat/telemetry/recorder/README.md +57 -57
- package/src/neat/telemetry/types/README.md +32 -32
- package/src/neat/topology-intent/README.md +75 -75
- package/src/utils/README.md +193 -193
- package/test/examples/asciiMaze/browser-entry/README.md +92 -92
- package/test/examples/asciiMaze/dashboardManager/README.md +109 -109
- package/test/examples/asciiMaze/dashboardManager/telemetry/README.md +28 -28
- package/test/examples/asciiMaze/evolutionEngine/README.md +1527 -1527
- package/test/examples/asciiMaze/mazeMovement/README.md +105 -105
- package/test/examples/asciiMaze/mazeMovement/finalization/README.md +16 -16
- package/test/examples/asciiMaze/mazeMovement/policy/README.md +57 -57
- package/test/examples/asciiMaze/mazeMovement/runtime/README.md +52 -52
- package/test/examples/asciiMaze/mazeMovement/shaping/README.md +46 -46
- package/test/examples/flappy_bird/browser-entry/README.md +508 -508
- package/test/examples/flappy_bird/browser-entry/host/README.md +101 -101
- package/test/examples/flappy_bird/browser-entry/host/resize/README.md +144 -144
- package/test/examples/flappy_bird/browser-entry/network-view/README.md +194 -194
- package/test/examples/flappy_bird/browser-entry/playback/README.md +278 -278
- package/test/examples/flappy_bird/browser-entry/playback/background/README.md +129 -129
- package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/README.md +502 -502
- package/test/examples/flappy_bird/browser-entry/playback/frame-render/README.md +139 -139
- package/test/examples/flappy_bird/browser-entry/playback/snapshot/README.md +10 -10
- package/test/examples/flappy_bird/browser-entry/playback/trail/README.md +43 -43
- package/test/examples/flappy_bird/browser-entry/playback/worker-channel/README.md +30 -30
- package/test/examples/flappy_bird/browser-entry/runtime/README.md +59 -59
- package/test/examples/flappy_bird/browser-entry/visualization/README.md +276 -276
- package/test/examples/flappy_bird/browser-entry/worker-channel/README.md +16 -16
- package/test/examples/flappy_bird/constants/README.md +1070 -1070
- package/test/examples/flappy_bird/environment/README.md +22 -22
- package/test/examples/flappy_bird/evaluation/README.md +32 -32
- package/test/examples/flappy_bird/evaluation/rollout/README.md +141 -141
- package/test/examples/flappy_bird/flappy-evolution-worker/README.md +425 -425
- package/test/examples/flappy_bird/simulation-shared/README.md +170 -170
- package/test/examples/flappy_bird/simulation-shared/observation/README.md +109 -109
- package/test/examples/flappy_bird/trainer/README.md +325 -325
- package/test/examples/flappy_bird/trainer/evaluation/README.md +74 -74
- package/scripts/analyze-trace.ts +0 -590
- package/scripts/copy-examples.mjs +0 -114
- package/scripts/export-onnx.mjs +0 -86
- package/scripts/generate-bench-tables.mjs +0 -182
- package/scripts/generate-docs.ts +0 -2900
- package/scripts/write-dist-docs-pkg.mjs +0 -16
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BEGIN_FRAME_EVENT_NAME,
|
|
3
|
+
DROPPED_FRAME_EVENT_NAME,
|
|
4
|
+
FIRE_ANIMATION_FRAME_EVENT_NAME,
|
|
5
|
+
FUNCTION_CALL_EVENT_NAME,
|
|
6
|
+
LONG_TASK_THRESHOLD_MS,
|
|
7
|
+
MICROSECONDS_PER_MILLISECOND,
|
|
8
|
+
RUN_TASK_EVENT_NAME,
|
|
9
|
+
UNKNOWN_PROCESS_LABEL,
|
|
10
|
+
UNKNOWN_SCRIPT_LABEL,
|
|
11
|
+
UNKNOWN_THREAD_LABEL,
|
|
12
|
+
VERY_LONG_TASK_THRESHOLD_MS,
|
|
13
|
+
} from './analyze-trace.constants.js';
|
|
14
|
+
import {
|
|
15
|
+
createProcessKey,
|
|
16
|
+
createThreadKey,
|
|
17
|
+
readNestedPrimitive,
|
|
18
|
+
} from './analyze-trace.shared.js';
|
|
19
|
+
import type {
|
|
20
|
+
AggregatedDuration,
|
|
21
|
+
LongEventSummary,
|
|
22
|
+
ThreadSummary,
|
|
23
|
+
TraceAnalysis,
|
|
24
|
+
TraceEvent,
|
|
25
|
+
TraceTimeRange,
|
|
26
|
+
} from './analyze-trace.types.js';
|
|
27
|
+
|
|
28
|
+
type TraceMetadataName = 'process_name' | 'thread_name';
|
|
29
|
+
|
|
30
|
+
interface TraceAnalysisInput {
|
|
31
|
+
traceFilePath: string;
|
|
32
|
+
traceEvents: readonly TraceEvent[];
|
|
33
|
+
topCount: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface TraceLabelContext {
|
|
37
|
+
processNames: ReadonlyMap<string, string>;
|
|
38
|
+
threadNames: ReadonlyMap<string, string>;
|
|
39
|
+
workerThreadLabels: ReadonlyMap<string, string>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Analyzes one trace event collection into deterministic report data.
|
|
44
|
+
*
|
|
45
|
+
* @param input - Trace analysis input packet.
|
|
46
|
+
* @returns Fully analyzed trace payload used by the reporting layer.
|
|
47
|
+
*/
|
|
48
|
+
export function analyzeTraceEvents(input: TraceAnalysisInput): TraceAnalysis {
|
|
49
|
+
const processNames = collectMetadataNames(input.traceEvents, 'process_name');
|
|
50
|
+
const threadNames = collectMetadataNames(input.traceEvents, 'thread_name');
|
|
51
|
+
const workerThreadLabels = collectWorkerThreadLabels(input.traceEvents);
|
|
52
|
+
const labelContext: TraceLabelContext = {
|
|
53
|
+
processNames,
|
|
54
|
+
threadNames,
|
|
55
|
+
workerThreadLabels,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
traceFilePath: input.traceFilePath,
|
|
60
|
+
traceEventCount: input.traceEvents.length,
|
|
61
|
+
timeRange: resolveTimeRange(input.traceEvents),
|
|
62
|
+
droppedFrameCount: countNamedEvents(
|
|
63
|
+
input.traceEvents,
|
|
64
|
+
DROPPED_FRAME_EVENT_NAME,
|
|
65
|
+
),
|
|
66
|
+
beginFrameCount: countNamedEvents(
|
|
67
|
+
input.traceEvents,
|
|
68
|
+
BEGIN_FRAME_EVENT_NAME,
|
|
69
|
+
),
|
|
70
|
+
threadSummaries: buildThreadSummaries(input.traceEvents, labelContext),
|
|
71
|
+
hottestEvents: aggregateDurationsByName(input.traceEvents, input.topCount),
|
|
72
|
+
hottestFunctionCalls: aggregateFunctionCalls(
|
|
73
|
+
input.traceEvents,
|
|
74
|
+
input.topCount,
|
|
75
|
+
),
|
|
76
|
+
longestEvents: collectLongestEvents(
|
|
77
|
+
input.traceEvents,
|
|
78
|
+
labelContext,
|
|
79
|
+
input.topCount,
|
|
80
|
+
),
|
|
81
|
+
fireAnimationFrameDurations: collectEventDurationsMs(
|
|
82
|
+
input.traceEvents,
|
|
83
|
+
FIRE_ANIMATION_FRAME_EVENT_NAME,
|
|
84
|
+
),
|
|
85
|
+
functionCallDurations: collectEventDurationsMs(
|
|
86
|
+
input.traceEvents,
|
|
87
|
+
FUNCTION_CALL_EVENT_NAME,
|
|
88
|
+
),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Collects metadata names keyed by process or thread id.
|
|
94
|
+
*
|
|
95
|
+
* @param traceEvents - Trace events to scan.
|
|
96
|
+
* @param metadataName - Metadata event name to collect.
|
|
97
|
+
* @returns Metadata map keyed by stable process or thread keys.
|
|
98
|
+
*/
|
|
99
|
+
function collectMetadataNames(
|
|
100
|
+
traceEvents: readonly TraceEvent[],
|
|
101
|
+
metadataName: TraceMetadataName,
|
|
102
|
+
): Map<string, string> {
|
|
103
|
+
const metadataNames = new Map<string, string>();
|
|
104
|
+
|
|
105
|
+
for (const traceEvent of traceEvents) {
|
|
106
|
+
if (traceEvent.ph !== 'M' || traceEvent.name !== metadataName) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const resolvedName = readNestedPrimitive(traceEvent.args, ['name']);
|
|
111
|
+
if (typeof resolvedName !== 'string') {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const metadataKey =
|
|
116
|
+
metadataName === 'process_name'
|
|
117
|
+
? createProcessKey(traceEvent.pid)
|
|
118
|
+
: createThreadKey(traceEvent.pid, traceEvent.tid);
|
|
119
|
+
if (!metadataKey) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
metadataNames.set(metadataKey, resolvedName);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return metadataNames;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Collects worker-thread labels from worker attachment events.
|
|
131
|
+
*
|
|
132
|
+
* @param traceEvents - Trace events to scan.
|
|
133
|
+
* @returns Worker thread label map keyed by stable thread keys.
|
|
134
|
+
*/
|
|
135
|
+
function collectWorkerThreadLabels(
|
|
136
|
+
traceEvents: readonly TraceEvent[],
|
|
137
|
+
): Map<string, string> {
|
|
138
|
+
const workerThreadLabels = new Map<string, string>();
|
|
139
|
+
|
|
140
|
+
for (const traceEvent of traceEvents) {
|
|
141
|
+
if (traceEvent.name !== 'TracingSessionIdForWorker') {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const workerThreadId = readNestedPrimitive(traceEvent.args, [
|
|
146
|
+
'data',
|
|
147
|
+
'workerThreadId',
|
|
148
|
+
]);
|
|
149
|
+
const workerUrl = readNestedPrimitive(traceEvent.args, ['data', 'url']);
|
|
150
|
+
if (typeof workerThreadId !== 'number' || typeof workerUrl !== 'string') {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const threadKey = createThreadKey(traceEvent.pid, workerThreadId);
|
|
155
|
+
if (!threadKey) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
workerThreadLabels.set(threadKey, workerUrl);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return workerThreadLabels;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Computes the visible time window spanned by the trace.
|
|
167
|
+
*
|
|
168
|
+
* @param traceEvents - Trace events to scan.
|
|
169
|
+
* @returns Visible trace window metrics.
|
|
170
|
+
*/
|
|
171
|
+
function resolveTimeRange(traceEvents: readonly TraceEvent[]): TraceTimeRange {
|
|
172
|
+
let minimumTimestamp = Number.POSITIVE_INFINITY;
|
|
173
|
+
let maximumTimestamp = 0;
|
|
174
|
+
|
|
175
|
+
for (const traceEvent of traceEvents) {
|
|
176
|
+
const eventTimestamp =
|
|
177
|
+
typeof traceEvent.ts === 'number' ? traceEvent.ts : 0;
|
|
178
|
+
const eventDuration =
|
|
179
|
+
typeof traceEvent.dur === 'number' ? traceEvent.dur : 0;
|
|
180
|
+
|
|
181
|
+
if (eventTimestamp > 0) {
|
|
182
|
+
minimumTimestamp = Math.min(minimumTimestamp, eventTimestamp);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
maximumTimestamp = Math.max(
|
|
186
|
+
maximumTimestamp,
|
|
187
|
+
eventTimestamp + eventDuration,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!Number.isFinite(minimumTimestamp)) {
|
|
192
|
+
minimumTimestamp = 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
windowMs:
|
|
197
|
+
(maximumTimestamp - minimumTimestamp) / MICROSECONDS_PER_MILLISECOND,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Builds per-thread duration and long-task summaries.
|
|
203
|
+
*
|
|
204
|
+
* @param traceEvents - Trace events to scan.
|
|
205
|
+
* @param labelContext - Thread label context.
|
|
206
|
+
* @returns Thread summaries sorted by total duration descending.
|
|
207
|
+
*/
|
|
208
|
+
function buildThreadSummaries(
|
|
209
|
+
traceEvents: readonly TraceEvent[],
|
|
210
|
+
labelContext: TraceLabelContext,
|
|
211
|
+
): ThreadSummary[] {
|
|
212
|
+
const durationByThreadLabel = new Map<string, ThreadSummary>();
|
|
213
|
+
|
|
214
|
+
for (const traceEvent of traceEvents) {
|
|
215
|
+
if (traceEvent.ph !== 'X' || typeof traceEvent.dur !== 'number') {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const threadKey = createThreadKey(traceEvent.pid, traceEvent.tid);
|
|
220
|
+
if (!threadKey) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const threadLabel = resolveThreadLabel(traceEvent, labelContext);
|
|
225
|
+
const threadSummary = durationByThreadLabel.get(threadLabel) ?? {
|
|
226
|
+
label: threadLabel,
|
|
227
|
+
totalDurationMs: 0,
|
|
228
|
+
runTaskDurationMs: 0,
|
|
229
|
+
longTaskCount16ms: 0,
|
|
230
|
+
longTaskCount50ms: 0,
|
|
231
|
+
maxTaskMs: 0,
|
|
232
|
+
};
|
|
233
|
+
const eventDurationMs = traceEvent.dur / MICROSECONDS_PER_MILLISECOND;
|
|
234
|
+
|
|
235
|
+
threadSummary.totalDurationMs += eventDurationMs;
|
|
236
|
+
if (traceEvent.name === RUN_TASK_EVENT_NAME) {
|
|
237
|
+
threadSummary.runTaskDurationMs += eventDurationMs;
|
|
238
|
+
if (eventDurationMs >= LONG_TASK_THRESHOLD_MS) {
|
|
239
|
+
threadSummary.longTaskCount16ms += 1;
|
|
240
|
+
}
|
|
241
|
+
if (eventDurationMs >= VERY_LONG_TASK_THRESHOLD_MS) {
|
|
242
|
+
threadSummary.longTaskCount50ms += 1;
|
|
243
|
+
}
|
|
244
|
+
threadSummary.maxTaskMs = Math.max(
|
|
245
|
+
threadSummary.maxTaskMs,
|
|
246
|
+
eventDurationMs,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
durationByThreadLabel.set(threadLabel, threadSummary);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return Array.from(durationByThreadLabel.values()).toSorted(
|
|
254
|
+
(leftSummary, rightSummary) =>
|
|
255
|
+
rightSummary.totalDurationMs - leftSummary.totalDurationMs ||
|
|
256
|
+
leftSummary.label.localeCompare(rightSummary.label),
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Resolves the longest complete events in the trace.
|
|
262
|
+
*
|
|
263
|
+
* @param traceEvents - Trace events to scan.
|
|
264
|
+
* @param labelContext - Thread label context.
|
|
265
|
+
* @param topCount - Maximum number of events to return.
|
|
266
|
+
* @returns Longest complete events sorted by duration.
|
|
267
|
+
*/
|
|
268
|
+
function collectLongestEvents(
|
|
269
|
+
traceEvents: readonly TraceEvent[],
|
|
270
|
+
labelContext: TraceLabelContext,
|
|
271
|
+
topCount: number,
|
|
272
|
+
): LongEventSummary[] {
|
|
273
|
+
const longEvents: LongEventSummary[] = [];
|
|
274
|
+
|
|
275
|
+
for (const traceEvent of traceEvents) {
|
|
276
|
+
if (traceEvent.ph !== 'X' || typeof traceEvent.dur !== 'number') {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const eventName = traceEvent.name;
|
|
281
|
+
if (typeof eventName !== 'string') {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const scriptUrlValue = readNestedPrimitive(traceEvent.args, [
|
|
286
|
+
'data',
|
|
287
|
+
'url',
|
|
288
|
+
]);
|
|
289
|
+
longEvents.push({
|
|
290
|
+
durationMs: traceEvent.dur / MICROSECONDS_PER_MILLISECOND,
|
|
291
|
+
threadLabel: resolveThreadLabel(traceEvent, labelContext),
|
|
292
|
+
eventName,
|
|
293
|
+
scriptUrl:
|
|
294
|
+
typeof scriptUrlValue === 'string' ? scriptUrlValue : undefined,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return longEvents
|
|
299
|
+
.toSorted(
|
|
300
|
+
(leftEvent, rightEvent) =>
|
|
301
|
+
rightEvent.durationMs - leftEvent.durationMs ||
|
|
302
|
+
leftEvent.eventName.localeCompare(rightEvent.eventName) ||
|
|
303
|
+
leftEvent.threadLabel.localeCompare(rightEvent.threadLabel),
|
|
304
|
+
)
|
|
305
|
+
.slice(0, topCount);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Aggregates complete-event durations by event name.
|
|
310
|
+
*
|
|
311
|
+
* @param traceEvents - Trace events to scan.
|
|
312
|
+
* @param topCount - Maximum number of entries to return.
|
|
313
|
+
* @returns Event duration rollup.
|
|
314
|
+
*/
|
|
315
|
+
function aggregateDurationsByName(
|
|
316
|
+
traceEvents: readonly TraceEvent[],
|
|
317
|
+
topCount: number,
|
|
318
|
+
): AggregatedDuration[] {
|
|
319
|
+
const durationsByName = new Map<string, AggregatedDuration>();
|
|
320
|
+
|
|
321
|
+
for (const traceEvent of traceEvents) {
|
|
322
|
+
if (traceEvent.ph !== 'X' || typeof traceEvent.dur !== 'number') {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const eventName = traceEvent.name;
|
|
327
|
+
if (!eventName) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const eventDurationMs = traceEvent.dur / MICROSECONDS_PER_MILLISECOND;
|
|
332
|
+
const eventSummary = durationsByName.get(eventName) ?? {
|
|
333
|
+
name: eventName,
|
|
334
|
+
count: 0,
|
|
335
|
+
totalMs: 0,
|
|
336
|
+
maxMs: 0,
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
eventSummary.count += 1;
|
|
340
|
+
eventSummary.totalMs += eventDurationMs;
|
|
341
|
+
eventSummary.maxMs = Math.max(eventSummary.maxMs, eventDurationMs);
|
|
342
|
+
durationsByName.set(eventName, eventSummary);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return Array.from(durationsByName.values())
|
|
346
|
+
.toSorted(
|
|
347
|
+
(leftSummary, rightSummary) =>
|
|
348
|
+
rightSummary.totalMs - leftSummary.totalMs ||
|
|
349
|
+
leftSummary.name.localeCompare(rightSummary.name),
|
|
350
|
+
)
|
|
351
|
+
.slice(0, topCount);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Aggregates FunctionCall durations by script URL.
|
|
356
|
+
*
|
|
357
|
+
* @param traceEvents - Trace events to scan.
|
|
358
|
+
* @param topCount - Maximum number of entries to return.
|
|
359
|
+
* @returns Function-call duration rollup.
|
|
360
|
+
*/
|
|
361
|
+
function aggregateFunctionCalls(
|
|
362
|
+
traceEvents: readonly TraceEvent[],
|
|
363
|
+
topCount: number,
|
|
364
|
+
): AggregatedDuration[] {
|
|
365
|
+
const durationsByFunctionCall = new Map<string, AggregatedDuration>();
|
|
366
|
+
|
|
367
|
+
for (const traceEvent of traceEvents) {
|
|
368
|
+
if (
|
|
369
|
+
traceEvent.ph !== 'X' ||
|
|
370
|
+
traceEvent.name !== FUNCTION_CALL_EVENT_NAME ||
|
|
371
|
+
typeof traceEvent.dur !== 'number'
|
|
372
|
+
) {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const functionCallNameValue = readNestedPrimitive(traceEvent.args, [
|
|
377
|
+
'data',
|
|
378
|
+
'url',
|
|
379
|
+
]);
|
|
380
|
+
const functionCallName =
|
|
381
|
+
typeof functionCallNameValue === 'string'
|
|
382
|
+
? functionCallNameValue
|
|
383
|
+
: UNKNOWN_SCRIPT_LABEL;
|
|
384
|
+
const functionCallSummary = durationsByFunctionCall.get(
|
|
385
|
+
functionCallName,
|
|
386
|
+
) ?? {
|
|
387
|
+
name: functionCallName,
|
|
388
|
+
count: 0,
|
|
389
|
+
totalMs: 0,
|
|
390
|
+
maxMs: 0,
|
|
391
|
+
};
|
|
392
|
+
const eventDurationMs = traceEvent.dur / MICROSECONDS_PER_MILLISECOND;
|
|
393
|
+
|
|
394
|
+
functionCallSummary.count += 1;
|
|
395
|
+
functionCallSummary.totalMs += eventDurationMs;
|
|
396
|
+
functionCallSummary.maxMs = Math.max(
|
|
397
|
+
functionCallSummary.maxMs,
|
|
398
|
+
eventDurationMs,
|
|
399
|
+
);
|
|
400
|
+
durationsByFunctionCall.set(functionCallName, functionCallSummary);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return Array.from(durationsByFunctionCall.values())
|
|
404
|
+
.toSorted(
|
|
405
|
+
(leftSummary, rightSummary) =>
|
|
406
|
+
rightSummary.totalMs - leftSummary.totalMs ||
|
|
407
|
+
leftSummary.name.localeCompare(rightSummary.name),
|
|
408
|
+
)
|
|
409
|
+
.slice(0, topCount);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Collects all durations for one named complete event.
|
|
414
|
+
*
|
|
415
|
+
* @param traceEvents - Trace events to scan.
|
|
416
|
+
* @param eventName - Event name to collect.
|
|
417
|
+
* @returns Sorted duration samples in milliseconds.
|
|
418
|
+
*/
|
|
419
|
+
function collectEventDurationsMs(
|
|
420
|
+
traceEvents: readonly TraceEvent[],
|
|
421
|
+
eventName: string,
|
|
422
|
+
): number[] {
|
|
423
|
+
const durationsMs: number[] = [];
|
|
424
|
+
|
|
425
|
+
for (const traceEvent of traceEvents) {
|
|
426
|
+
if (
|
|
427
|
+
traceEvent.ph !== 'X' ||
|
|
428
|
+
traceEvent.name !== eventName ||
|
|
429
|
+
typeof traceEvent.dur !== 'number'
|
|
430
|
+
) {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
durationsMs.push(traceEvent.dur / MICROSECONDS_PER_MILLISECOND);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return durationsMs.toSorted(
|
|
438
|
+
(leftDuration, rightDuration) => leftDuration - rightDuration,
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Counts complete or metadata events by exact name.
|
|
444
|
+
*
|
|
445
|
+
* @param traceEvents - Trace events to scan.
|
|
446
|
+
* @param eventName - Event name to count.
|
|
447
|
+
* @returns Matching event count.
|
|
448
|
+
*/
|
|
449
|
+
function countNamedEvents(
|
|
450
|
+
traceEvents: readonly TraceEvent[],
|
|
451
|
+
eventName: string,
|
|
452
|
+
): number {
|
|
453
|
+
return traceEvents.filter((traceEvent) => traceEvent.name === eventName)
|
|
454
|
+
.length;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Resolves a readable process and thread label for one trace event.
|
|
459
|
+
*
|
|
460
|
+
* @param traceEvent - Trace event needing a label.
|
|
461
|
+
* @param labelContext - Thread label context.
|
|
462
|
+
* @returns Human-readable thread label.
|
|
463
|
+
*/
|
|
464
|
+
function resolveThreadLabel(
|
|
465
|
+
traceEvent: TraceEvent,
|
|
466
|
+
labelContext: TraceLabelContext,
|
|
467
|
+
): string {
|
|
468
|
+
const processKey = createProcessKey(traceEvent.pid) ?? '';
|
|
469
|
+
const threadKey = createThreadKey(traceEvent.pid, traceEvent.tid) ?? '';
|
|
470
|
+
const processLabel =
|
|
471
|
+
labelContext.processNames.get(processKey) ?? UNKNOWN_PROCESS_LABEL;
|
|
472
|
+
const threadLabel =
|
|
473
|
+
labelContext.threadNames.get(threadKey) ?? UNKNOWN_THREAD_LABEL;
|
|
474
|
+
const workerLabel = labelContext.workerThreadLabels.get(threadKey);
|
|
475
|
+
|
|
476
|
+
return workerLabel
|
|
477
|
+
? `${processLabel} / ${threadLabel} / ${workerLabel}`
|
|
478
|
+
: `${processLabel} / ${threadLabel}`;
|
|
479
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Number of trace microseconds contained in one millisecond. */
|
|
2
|
+
export const MICROSECONDS_PER_MILLISECOND = 1_000;
|
|
3
|
+
|
|
4
|
+
/** Default item count used by the top-N rollups. */
|
|
5
|
+
export const DEFAULT_TOP_COUNT = 12;
|
|
6
|
+
|
|
7
|
+
/** RunTask duration threshold used for frame-budget pressure reporting. */
|
|
8
|
+
export const LONG_TASK_THRESHOLD_MS = 16.7;
|
|
9
|
+
|
|
10
|
+
/** Severe RunTask duration threshold used for very long task reporting. */
|
|
11
|
+
export const VERY_LONG_TASK_THRESHOLD_MS = 50;
|
|
12
|
+
|
|
13
|
+
/** Trace event name used by Chrome when one frame is dropped. */
|
|
14
|
+
export const DROPPED_FRAME_EVENT_NAME = 'DroppedFrame';
|
|
15
|
+
|
|
16
|
+
/** Trace event name used by Chrome for frame production. */
|
|
17
|
+
export const BEGIN_FRAME_EVENT_NAME = 'BeginFrame';
|
|
18
|
+
|
|
19
|
+
/** Trace event name used for browser task execution slices. */
|
|
20
|
+
export const RUN_TASK_EVENT_NAME = 'RunTask';
|
|
21
|
+
|
|
22
|
+
/** Trace event name used for script execution attribution slices. */
|
|
23
|
+
export const FUNCTION_CALL_EVENT_NAME = 'FunctionCall';
|
|
24
|
+
|
|
25
|
+
/** Trace event name used for animation-frame callback execution. */
|
|
26
|
+
export const FIRE_ANIMATION_FRAME_EVENT_NAME = 'FireAnimationFrame';
|
|
27
|
+
|
|
28
|
+
/** Fallback process label when metadata is missing. */
|
|
29
|
+
export const UNKNOWN_PROCESS_LABEL = 'unknown-process';
|
|
30
|
+
|
|
31
|
+
/** Fallback thread label when metadata is missing. */
|
|
32
|
+
export const UNKNOWN_THREAD_LABEL = 'unknown-thread';
|
|
33
|
+
|
|
34
|
+
/** Fallback function-call label when a script URL is unavailable. */
|
|
35
|
+
export const UNKNOWN_SCRIPT_LABEL = '(unknown-script)';
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
|
|
3
|
+
import { DEFAULT_TOP_COUNT } from './analyze-trace.constants.js';
|
|
4
|
+
import type { CliOptions, TraceFile } from './analyze-trace.types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolves CLI flags for one trace-analyzer invocation.
|
|
8
|
+
*
|
|
9
|
+
* Supported inputs:
|
|
10
|
+
* - positional trace path
|
|
11
|
+
* - `--top=NUMBER`
|
|
12
|
+
* - `--top NUMBER`
|
|
13
|
+
*
|
|
14
|
+
* @param argumentValues - CLI arguments after the script path.
|
|
15
|
+
* @returns Parsed CLI options.
|
|
16
|
+
*/
|
|
17
|
+
export function resolveCliOptions(
|
|
18
|
+
argumentValues: readonly string[],
|
|
19
|
+
): CliOptions {
|
|
20
|
+
const pathArgument = argumentValues.find(
|
|
21
|
+
(argument) => !argument.startsWith('--'),
|
|
22
|
+
);
|
|
23
|
+
if (!pathArgument) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
'Missing trace path. Example: npm run trace:analyze -- test/examples/flappy_bird/Trace.json',
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
tracePath: pathArgument,
|
|
31
|
+
topCount: resolveRequestedTopCount(argumentValues),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Reads and parses one trace JSON file.
|
|
37
|
+
*
|
|
38
|
+
* @param traceFilePath - Absolute or relative trace file path.
|
|
39
|
+
* @returns Parsed trace file payload.
|
|
40
|
+
*/
|
|
41
|
+
export function loadTrace(traceFilePath: string): TraceFile {
|
|
42
|
+
const traceFileContents = fs.readFileSync(traceFilePath, 'utf8');
|
|
43
|
+
return JSON.parse(traceFileContents) as TraceFile;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Resolves the requested top-count override from CLI flags.
|
|
48
|
+
*
|
|
49
|
+
* @param argumentValues - CLI arguments after the script path.
|
|
50
|
+
* @returns Positive top-count value or the repo default.
|
|
51
|
+
*/
|
|
52
|
+
function resolveRequestedTopCount(argumentValues: readonly string[]): number {
|
|
53
|
+
const inlineTopArgument = argumentValues.find((argument) =>
|
|
54
|
+
argument.startsWith('--top='),
|
|
55
|
+
);
|
|
56
|
+
const splitTopArgumentIndex = argumentValues.findIndex(
|
|
57
|
+
(argument) => argument === '--top',
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const requestedTopCount = inlineTopArgument
|
|
61
|
+
? Number.parseInt(inlineTopArgument.slice('--top='.length), 10)
|
|
62
|
+
: splitTopArgumentIndex >= 0
|
|
63
|
+
? Number.parseInt(argumentValues[splitTopArgumentIndex + 1] ?? '', 10)
|
|
64
|
+
: DEFAULT_TOP_COUNT;
|
|
65
|
+
|
|
66
|
+
return Number.isFinite(requestedTopCount) && requestedTopCount > 0
|
|
67
|
+
? requestedTopCount
|
|
68
|
+
: DEFAULT_TOP_COUNT;
|
|
69
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { formatDistribution, formatMs } from './analyze-trace.shared.js';
|
|
2
|
+
import type { TraceAnalysis } from './analyze-trace.types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds the textual report lines for one analyzed trace.
|
|
6
|
+
*
|
|
7
|
+
* @param traceAnalysis - Fully analyzed trace payload.
|
|
8
|
+
* @returns Report lines in display order.
|
|
9
|
+
*/
|
|
10
|
+
export function buildTraceReportLines(traceAnalysis: TraceAnalysis): string[] {
|
|
11
|
+
const reportLines: string[] = [];
|
|
12
|
+
|
|
13
|
+
appendSection(reportLines, 'Trace', [
|
|
14
|
+
`File: ${traceAnalysis.traceFilePath}`,
|
|
15
|
+
`Window: ${formatMs(traceAnalysis.timeRange.windowMs)} across ${traceAnalysis.traceEventCount.toLocaleString()} events`,
|
|
16
|
+
`Frames: ${traceAnalysis.beginFrameCount.toLocaleString()} begin, ${traceAnalysis.droppedFrameCount.toLocaleString()} dropped`,
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
appendSection(
|
|
20
|
+
reportLines,
|
|
21
|
+
'Thread Summary',
|
|
22
|
+
traceAnalysis.threadSummaries.map(
|
|
23
|
+
(threadSummary) =>
|
|
24
|
+
`${threadSummary.label}: total=${formatMs(threadSummary.totalDurationMs)}, ` +
|
|
25
|
+
`RunTask=${formatMs(threadSummary.runTaskDurationMs)}, ` +
|
|
26
|
+
`>16.7ms=${threadSummary.longTaskCount16ms}, ` +
|
|
27
|
+
`>50ms=${threadSummary.longTaskCount50ms}, ` +
|
|
28
|
+
`max=${formatMs(threadSummary.maxTaskMs)}`,
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
appendSection(reportLines, 'Animation Frames', [
|
|
33
|
+
`FireAnimationFrame: ${formatDistribution(traceAnalysis.fireAnimationFrameDurations)}`,
|
|
34
|
+
`FunctionCall: ${formatDistribution(traceAnalysis.functionCallDurations)}`,
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
appendSection(
|
|
38
|
+
reportLines,
|
|
39
|
+
'Longest Events',
|
|
40
|
+
traceAnalysis.longestEvents.map(
|
|
41
|
+
(longEvent) =>
|
|
42
|
+
`${formatMs(longEvent.durationMs)} | ${longEvent.threadLabel} | ${longEvent.eventName}${
|
|
43
|
+
longEvent.scriptUrl ? ` | ${longEvent.scriptUrl}` : ''
|
|
44
|
+
}`,
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
appendSection(
|
|
49
|
+
reportLines,
|
|
50
|
+
'Top Events',
|
|
51
|
+
traceAnalysis.hottestEvents.map(
|
|
52
|
+
(eventSummary) =>
|
|
53
|
+
`${eventSummary.name}: count=${eventSummary.count.toLocaleString()}, total=${formatMs(eventSummary.totalMs)}, max=${formatMs(eventSummary.maxMs)}`,
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
appendSection(
|
|
58
|
+
reportLines,
|
|
59
|
+
'Top Function Calls',
|
|
60
|
+
traceAnalysis.hottestFunctionCalls.map(
|
|
61
|
+
(functionCallSummary) =>
|
|
62
|
+
`${functionCallSummary.name}: count=${functionCallSummary.count.toLocaleString()}, total=${formatMs(functionCallSummary.totalMs)}, max=${formatMs(functionCallSummary.maxMs)}`,
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return reportLines;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Prints one analyzed trace report to stdout.
|
|
71
|
+
*
|
|
72
|
+
* @param traceAnalysis - Fully analyzed trace payload.
|
|
73
|
+
* @returns Nothing.
|
|
74
|
+
*/
|
|
75
|
+
export function printTraceReport(traceAnalysis: TraceAnalysis): void {
|
|
76
|
+
for (const reportLine of buildTraceReportLines(traceAnalysis)) {
|
|
77
|
+
console.log(reportLine);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Appends one report section to the output line buffer.
|
|
83
|
+
*
|
|
84
|
+
* @param reportLines - Mutable output line buffer.
|
|
85
|
+
* @param title - Section title.
|
|
86
|
+
* @param contentLines - Section body lines.
|
|
87
|
+
* @returns Nothing.
|
|
88
|
+
*/
|
|
89
|
+
function appendSection(
|
|
90
|
+
reportLines: string[],
|
|
91
|
+
title: string,
|
|
92
|
+
contentLines: readonly string[],
|
|
93
|
+
): void {
|
|
94
|
+
if (reportLines.length > 0) {
|
|
95
|
+
reportLines.push('');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
reportLines.push(`[${title}]`);
|
|
99
|
+
reportLines.push(...contentLines);
|
|
100
|
+
}
|