@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.
Files changed (210) hide show
  1. package/.github/copilot-instructions.md +11 -0
  2. package/.github/skills/trace-analyzer-extension/SKILL.md +3 -3
  3. package/.github/skills/trace-analyzer-extension/assets/extension-checklist.md +1 -1
  4. package/.github/skills/trace-analyzer-extension/references/analyzer-extension-workflow.md +1 -1
  5. package/.github/skills/trace-audit-reporting/SKILL.md +3 -3
  6. package/.github/skills/trace-audit-reporting/references/trace-analysis-workflow.md +1 -1
  7. package/package.json +19 -13
  8. package/plans/Flappy_Bird_Folder_Documentation_Pass.md +4 -4
  9. package/plans/README.md +24 -0
  10. package/plans/Roadmap.md +62 -40
  11. package/plans/analyze-trace-solid-split.plans.md +66 -0
  12. package/plans/architecture-solid-split.plans.md +9 -15
  13. package/plans/asciiMaze-typescript-repair.plans.md +1 -1
  14. package/plans/generate-docs-solid-split.plans.md +87 -0
  15. package/plans/methods-docs.plans.md +25 -1
  16. package/plans/methods-solid-split.plans.md +14 -14
  17. package/plans/neat-docs.plans.md +9 -1
  18. package/plans/neat-test-surface-repair.plans.md +1 -1
  19. package/plans/render-docs-html-solid-split.plans.md +68 -0
  20. package/plans/src-no-explicit-any-cleanup.plans.md +1 -1
  21. package/plans/utils-docs.plans.md +6 -1
  22. package/scripts/analyze-trace/analyze-trace.analysis.ts +479 -0
  23. package/scripts/analyze-trace/analyze-trace.constants.ts +35 -0
  24. package/scripts/analyze-trace/analyze-trace.io.ts +69 -0
  25. package/scripts/analyze-trace/analyze-trace.report.ts +100 -0
  26. package/scripts/analyze-trace/analyze-trace.shared.ts +116 -0
  27. package/scripts/analyze-trace/analyze-trace.ts +45 -0
  28. package/scripts/analyze-trace/analyze-trace.types.ts +72 -0
  29. package/scripts/assets/theme.css +80 -23
  30. package/scripts/copy-examples.ts +239 -0
  31. package/scripts/export-onnx.ts +223 -0
  32. package/scripts/generate-bench-tables.ts +378 -37
  33. package/scripts/generate-docs/generate-docs.constants.ts +107 -0
  34. package/scripts/generate-docs/generate-docs.order.ts +355 -0
  35. package/scripts/generate-docs/generate-docs.state.ts +31 -0
  36. package/scripts/generate-docs/generate-docs.targets.ts +165 -0
  37. package/scripts/generate-docs/generate-docs.ts +63 -0
  38. package/scripts/generate-docs/generate-docs.types.ts +112 -0
  39. package/scripts/generate-docs/output/generate-docs.output.folder-index.utils.ts +167 -0
  40. package/scripts/generate-docs/output/generate-docs.output.ordering.utils.ts +353 -0
  41. package/scripts/generate-docs/output/generate-docs.output.readme.utils.ts +420 -0
  42. package/scripts/generate-docs/output/generate-docs.output.ts +123 -0
  43. package/scripts/generate-docs/output/generate-docs.output.warnings.utils.ts +219 -0
  44. package/scripts/generate-docs/symbols/generate-docs.symbols.collection.utils.ts +365 -0
  45. package/scripts/generate-docs/symbols/generate-docs.symbols.jsdoc.utils.ts +373 -0
  46. package/scripts/generate-docs/symbols/generate-docs.symbols.normalize.utils.ts +155 -0
  47. package/scripts/generate-docs/symbols/generate-docs.symbols.render.utils.ts +149 -0
  48. package/scripts/generate-docs/symbols/generate-docs.symbols.signature.utils.ts +289 -0
  49. package/scripts/generate-docs/symbols/generate-docs.symbols.ts +11 -0
  50. package/scripts/mermaid-cli.mjs +102 -22
  51. package/scripts/mermaid-cli.ts +736 -0
  52. package/scripts/render-docs-html/render-docs-html.assets.ts +54 -0
  53. package/scripts/render-docs-html/render-docs-html.mermaid.ts +245 -0
  54. package/scripts/{render-docs-html.sidebar.ts → render-docs-html/render-docs-html.navigation.ts} +141 -144
  55. package/scripts/render-docs-html/render-docs-html.pages.ts +333 -0
  56. package/scripts/render-docs-html/render-docs-html.shared.ts +333 -0
  57. package/scripts/render-docs-html/render-docs-html.types.ts +42 -0
  58. package/scripts/render-docs-html.ts +23 -587
  59. package/scripts/run-docs.ts +238 -0
  60. package/scripts/write-dist-docs-pkg.ts +40 -0
  61. package/src/README.md +75 -75
  62. package/src/architecture/connection/README.md +5 -5
  63. package/src/architecture/layer/README.md +508 -508
  64. package/src/architecture/network/README.md +1458 -1458
  65. package/src/architecture/network/activate/README.md +694 -694
  66. package/src/architecture/network/bootstrap/README.md +77 -77
  67. package/src/architecture/network/connect/README.md +74 -74
  68. package/src/architecture/network/deterministic/README.md +135 -135
  69. package/src/architecture/network/evolve/README.md +364 -364
  70. package/src/architecture/network/gating/README.md +130 -130
  71. package/src/architecture/network/genetic/README.md +399 -399
  72. package/src/architecture/network/mutate/README.md +897 -897
  73. package/src/architecture/network/onnx/README.md +720 -720
  74. package/src/architecture/network/onnx/export/README.md +728 -728
  75. package/src/architecture/network/onnx/export/layers/README.md +450 -450
  76. package/src/architecture/network/onnx/import/README.md +618 -618
  77. package/src/architecture/network/onnx/schema/README.md +32 -32
  78. package/src/architecture/network/prune/README.md +245 -245
  79. package/src/architecture/network/remove/README.md +135 -135
  80. package/src/architecture/network/runtime/README.md +106 -106
  81. package/src/architecture/network/serialize/README.md +542 -542
  82. package/src/architecture/network/slab/README.md +608 -608
  83. package/src/architecture/network/standalone/README.md +212 -212
  84. package/src/architecture/network/stats/README.md +84 -84
  85. package/src/architecture/network/topology/README.md +465 -465
  86. package/src/architecture/network/training/README.md +200 -200
  87. package/src/architecture/node/README.md +5 -5
  88. package/src/architecture/nodePool/README.md +14 -14
  89. package/src/methods/README.md +99 -99
  90. package/src/methods/activation/README.md +189 -189
  91. package/src/methods/cost/README.md +131 -131
  92. package/src/methods/rate/README.md +86 -86
  93. package/src/multithreading/README.md +77 -77
  94. package/src/multithreading/workers/browser/README.md +8 -8
  95. package/src/multithreading/workers/node/README.md +8 -8
  96. package/src/neat/README.md +148 -148
  97. package/src/neat/adaptive/README.md +120 -120
  98. package/src/neat/adaptive/acceptance/README.md +40 -40
  99. package/src/neat/adaptive/complexity/README.md +137 -137
  100. package/src/neat/adaptive/core/README.md +197 -197
  101. package/src/neat/adaptive/lineage/README.md +90 -90
  102. package/src/neat/adaptive/mutation/README.md +284 -284
  103. package/src/neat/compat/README.md +43 -43
  104. package/src/neat/compat/core/README.md +90 -90
  105. package/src/neat/diversity/README.md +35 -35
  106. package/src/neat/diversity/core/README.md +88 -88
  107. package/src/neat/evaluate/README.md +85 -85
  108. package/src/neat/evaluate/auto-distance/README.md +75 -75
  109. package/src/neat/evaluate/entropy-compat/README.md +37 -37
  110. package/src/neat/evaluate/entropy-sharing/README.md +43 -43
  111. package/src/neat/evaluate/fitness/README.md +23 -23
  112. package/src/neat/evaluate/novelty/README.md +120 -120
  113. package/src/neat/evaluate/objectives/README.md +17 -17
  114. package/src/neat/evaluate/shared/README.md +94 -94
  115. package/src/neat/evolve/README.md +96 -96
  116. package/src/neat/evolve/adaptive/README.md +60 -60
  117. package/src/neat/evolve/objectives/README.md +63 -63
  118. package/src/neat/evolve/offspring/README.md +56 -56
  119. package/src/neat/evolve/population/README.md +171 -171
  120. package/src/neat/evolve/runtime/README.md +79 -79
  121. package/src/neat/evolve/speciation/README.md +74 -74
  122. package/src/neat/evolve/warnings/README.md +10 -10
  123. package/src/neat/export/README.md +114 -114
  124. package/src/neat/helpers/README.md +50 -50
  125. package/src/neat/init/README.md +9 -9
  126. package/src/neat/lineage/core/README.md +101 -101
  127. package/src/neat/multiobjective/category/README.md +74 -74
  128. package/src/neat/multiobjective/crowding/README.md +272 -272
  129. package/src/neat/multiobjective/dominance/README.md +171 -171
  130. package/src/neat/multiobjective/fronts/README.md +68 -68
  131. package/src/neat/multiobjective/metrics/README.md +43 -43
  132. package/src/neat/multiobjective/objectives/README.md +31 -31
  133. package/src/neat/multiobjective/shared/README.md +27 -27
  134. package/src/neat/mutation/README.md +97 -97
  135. package/src/neat/mutation/add-conn/README.md +115 -115
  136. package/src/neat/mutation/add-node/README.md +126 -126
  137. package/src/neat/mutation/flow/README.md +149 -149
  138. package/src/neat/mutation/repair/README.md +185 -185
  139. package/src/neat/mutation/select/README.md +117 -117
  140. package/src/neat/mutation/shared/README.md +32 -32
  141. package/src/neat/objectives/README.md +25 -25
  142. package/src/neat/objectives/core/README.md +67 -67
  143. package/src/neat/pruning/README.md +40 -40
  144. package/src/neat/pruning/core/README.md +171 -171
  145. package/src/neat/pruning/facade/README.md +32 -32
  146. package/src/neat/rng/README.md +104 -104
  147. package/src/neat/rng/core/README.md +137 -137
  148. package/src/neat/rng/facade/README.md +50 -50
  149. package/src/neat/selection/README.md +111 -111
  150. package/src/neat/selection/core/README.md +227 -227
  151. package/src/neat/selection/facade/README.md +61 -61
  152. package/src/neat/shared/README.md +163 -163
  153. package/src/neat/speciation/README.md +31 -31
  154. package/src/neat/speciation/threshold/README.md +35 -35
  155. package/src/neat/species/README.md +25 -25
  156. package/src/neat/species/core/README.md +20 -20
  157. package/src/neat/species/core/shared/README.md +18 -18
  158. package/src/neat/species/history/context/README.md +22 -22
  159. package/src/neat/telemetry/accessors/README.md +58 -58
  160. package/src/neat/telemetry/exports/README.md +233 -233
  161. package/src/neat/telemetry/facade/README.md +252 -252
  162. package/src/neat/telemetry/facade/archive/README.md +57 -57
  163. package/src/neat/telemetry/facade/buffer/README.md +43 -43
  164. package/src/neat/telemetry/facade/lineage/README.md +12 -12
  165. package/src/neat/telemetry/facade/objectives/README.md +44 -44
  166. package/src/neat/telemetry/facade/runtime/README.md +26 -26
  167. package/src/neat/telemetry/facade/species/README.md +27 -27
  168. package/src/neat/telemetry/metrics/README.md +696 -696
  169. package/src/neat/telemetry/recorder/README.md +57 -57
  170. package/src/neat/telemetry/types/README.md +32 -32
  171. package/src/neat/topology-intent/README.md +75 -75
  172. package/src/utils/README.md +193 -193
  173. package/test/examples/asciiMaze/browser-entry/README.md +92 -92
  174. package/test/examples/asciiMaze/dashboardManager/README.md +109 -109
  175. package/test/examples/asciiMaze/dashboardManager/telemetry/README.md +28 -28
  176. package/test/examples/asciiMaze/evolutionEngine/README.md +1527 -1527
  177. package/test/examples/asciiMaze/mazeMovement/README.md +105 -105
  178. package/test/examples/asciiMaze/mazeMovement/finalization/README.md +16 -16
  179. package/test/examples/asciiMaze/mazeMovement/policy/README.md +57 -57
  180. package/test/examples/asciiMaze/mazeMovement/runtime/README.md +52 -52
  181. package/test/examples/asciiMaze/mazeMovement/shaping/README.md +46 -46
  182. package/test/examples/flappy_bird/browser-entry/README.md +508 -508
  183. package/test/examples/flappy_bird/browser-entry/host/README.md +101 -101
  184. package/test/examples/flappy_bird/browser-entry/host/resize/README.md +144 -144
  185. package/test/examples/flappy_bird/browser-entry/network-view/README.md +194 -194
  186. package/test/examples/flappy_bird/browser-entry/playback/README.md +278 -278
  187. package/test/examples/flappy_bird/browser-entry/playback/background/README.md +129 -129
  188. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/README.md +502 -502
  189. package/test/examples/flappy_bird/browser-entry/playback/frame-render/README.md +139 -139
  190. package/test/examples/flappy_bird/browser-entry/playback/snapshot/README.md +10 -10
  191. package/test/examples/flappy_bird/browser-entry/playback/trail/README.md +43 -43
  192. package/test/examples/flappy_bird/browser-entry/playback/worker-channel/README.md +30 -30
  193. package/test/examples/flappy_bird/browser-entry/runtime/README.md +59 -59
  194. package/test/examples/flappy_bird/browser-entry/visualization/README.md +276 -276
  195. package/test/examples/flappy_bird/browser-entry/worker-channel/README.md +16 -16
  196. package/test/examples/flappy_bird/constants/README.md +1070 -1070
  197. package/test/examples/flappy_bird/environment/README.md +22 -22
  198. package/test/examples/flappy_bird/evaluation/README.md +32 -32
  199. package/test/examples/flappy_bird/evaluation/rollout/README.md +141 -141
  200. package/test/examples/flappy_bird/flappy-evolution-worker/README.md +425 -425
  201. package/test/examples/flappy_bird/simulation-shared/README.md +170 -170
  202. package/test/examples/flappy_bird/simulation-shared/observation/README.md +109 -109
  203. package/test/examples/flappy_bird/trainer/README.md +325 -325
  204. package/test/examples/flappy_bird/trainer/evaluation/README.md +74 -74
  205. package/scripts/analyze-trace.ts +0 -590
  206. package/scripts/copy-examples.mjs +0 -114
  207. package/scripts/export-onnx.mjs +0 -86
  208. package/scripts/generate-bench-tables.mjs +0 -182
  209. package/scripts/generate-docs.ts +0 -2900
  210. 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
+ }