@deeeed/metamask-harness 0.43.0 → 0.44.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/CHANGELOG.md +25 -0
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +304 -34
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +20 -8
- package/adapters/mobile/bridge-runtime/lib/cdp-broker.cjs +102 -0
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +4 -2
- package/adapters/mobile/reload-app.mjs +99 -1
- package/dist/adapters/mobile/prepare.js +12 -0
- package/dist/adapters.js +22 -3
- package/dist/commands/call.js +40 -2
- package/dist/commands/run-engine.js +247 -139
- package/dist/commands/run-report.js +68 -0
- package/dist/commands/run.js +47 -2
- package/dist/execution-provenance.js +342 -0
- package/dist/run-diagnostics.js +36 -11
- package/dist/runner.js +44 -13
- package/library/actions/mobile/perps/performance-capture.mjs +570 -189
- package/library/actions/mobile/perps/perps.mjs +122 -0
- package/library/actions/mobile/platform/bridge.mjs +40 -8
- package/library/actions/mobile/platform/native-session.mjs +1 -1
- package/library/actions/mobile/wallet/lock.mjs +1 -4
- package/library/actions/mobile/wallet/select_account.mjs +129 -17
- package/library/manifests/mobile.action-manifest.json +28 -3
- package/library/recipes/mobile/perps/performance.recipe.json +73 -47
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, open, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
|
|
4
5
|
const MARKER = "[PerpsPerf]";
|
|
@@ -7,12 +8,39 @@ const DETAILED_MARKERS = [MARKER, LEGACY_MARKER];
|
|
|
7
8
|
const STARTUP_MARKER = "[StartupPerf]";
|
|
8
9
|
const LOAD_PROOF_MARKER = "[PerpsLoadProof]";
|
|
9
10
|
const STATE_FILE = ".homepage-performance-capture.json";
|
|
11
|
+
const CAPTURE_BOUNDARY_BYTES = 4096;
|
|
12
|
+
const BOOTSTRAP_LOOKBACK_BYTES = 512 * 1024;
|
|
10
13
|
const FRESH_SOURCES = new Set([
|
|
11
14
|
"fresh_socket",
|
|
12
15
|
"provider_snapshot",
|
|
13
16
|
"terminal_global_snapshot_v2",
|
|
17
|
+
"terminal_v2",
|
|
14
18
|
"provider",
|
|
15
19
|
]);
|
|
20
|
+
const SURFACE_STAGE_ORDER = [
|
|
21
|
+
"surface_demand",
|
|
22
|
+
"surface_initial_ui_recorded",
|
|
23
|
+
"surface_resolved_recorded",
|
|
24
|
+
"surface_live_recorded",
|
|
25
|
+
];
|
|
26
|
+
const SURFACE_STAGES = new Set(SURFACE_STAGE_ORDER);
|
|
27
|
+
const PERFORMANCE_IDENTITY_FIELDS = [
|
|
28
|
+
"demand_id",
|
|
29
|
+
"perps_session_id",
|
|
30
|
+
"lifecycle",
|
|
31
|
+
"account_generation",
|
|
32
|
+
"context_generation",
|
|
33
|
+
];
|
|
34
|
+
const LOAD_PROOF_BASE_IDENTITY_FIELDS = PERFORMANCE_IDENTITY_FIELDS.slice(1);
|
|
35
|
+
const LOAD_PROOF_IDENTITY_FIELDS = [
|
|
36
|
+
...LOAD_PROOF_BASE_IDENTITY_FIELDS,
|
|
37
|
+
"connection_generation",
|
|
38
|
+
];
|
|
39
|
+
const GENERATION_IDENTITY_FIELDS = new Set([
|
|
40
|
+
"account_generation",
|
|
41
|
+
"context_generation",
|
|
42
|
+
"connection_generation",
|
|
43
|
+
]);
|
|
16
44
|
|
|
17
45
|
function resolveWithin(root, relativePath, label) {
|
|
18
46
|
const absoluteRoot = path.resolve(root);
|
|
@@ -24,15 +52,83 @@ function resolveWithin(root, relativePath, label) {
|
|
|
24
52
|
return absolutePath;
|
|
25
53
|
}
|
|
26
54
|
|
|
27
|
-
async function
|
|
55
|
+
async function fileState(file) {
|
|
28
56
|
try {
|
|
29
|
-
|
|
57
|
+
const value = await stat(file);
|
|
58
|
+
return { size: value.size, device: value.dev, inode: value.ino };
|
|
30
59
|
} catch (error) {
|
|
31
|
-
if (error?.code === "ENOENT") return
|
|
60
|
+
if (error?.code === "ENOENT") return null;
|
|
32
61
|
throw error;
|
|
33
62
|
}
|
|
34
63
|
}
|
|
35
64
|
|
|
65
|
+
async function readFileRange(file, start, length) {
|
|
66
|
+
if (length === 0) return Buffer.alloc(0);
|
|
67
|
+
const handle = await open(file, "r");
|
|
68
|
+
try {
|
|
69
|
+
const buffer = Buffer.alloc(length);
|
|
70
|
+
const { bytesRead } = await handle.read(buffer, 0, length, start);
|
|
71
|
+
return buffer.subarray(0, bytesRead);
|
|
72
|
+
} finally {
|
|
73
|
+
await handle.close();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function captureBoundary(content, offset) {
|
|
78
|
+
const start = Math.max(0, offset - CAPTURE_BOUNDARY_BYTES);
|
|
79
|
+
const bytes = content.subarray(start, offset);
|
|
80
|
+
return {
|
|
81
|
+
start,
|
|
82
|
+
length: bytes.length,
|
|
83
|
+
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function captureBoundaryBytes(start, bytes) {
|
|
88
|
+
return {
|
|
89
|
+
start,
|
|
90
|
+
length: bytes.length,
|
|
91
|
+
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function readCaptureSource(sourceFile, state) {
|
|
96
|
+
const current = await fileState(sourceFile);
|
|
97
|
+
if (!current) {
|
|
98
|
+
if (state.offset === 0 && state.sourceIdentity === null) {
|
|
99
|
+
return Buffer.alloc(0);
|
|
100
|
+
}
|
|
101
|
+
throw new Error("Perps performance capture source disappeared after phase=start.");
|
|
102
|
+
}
|
|
103
|
+
if (state.sourceIdentity === null) {
|
|
104
|
+
state.sourceIdentity = current;
|
|
105
|
+
}
|
|
106
|
+
if (
|
|
107
|
+
state.sourceIdentity &&
|
|
108
|
+
(current.device !== state.sourceIdentity.device ||
|
|
109
|
+
current.inode !== state.sourceIdentity.inode)
|
|
110
|
+
) {
|
|
111
|
+
throw new Error("Perps performance capture source was replaced after phase=start.");
|
|
112
|
+
}
|
|
113
|
+
if (current.size < state.offset) {
|
|
114
|
+
throw new Error("Perps performance capture source shrank after phase=start.");
|
|
115
|
+
}
|
|
116
|
+
const content = await readFile(sourceFile);
|
|
117
|
+
const boundary = captureBoundary(content, state.offset);
|
|
118
|
+
if (state.sourceBoundary === null) {
|
|
119
|
+
state.sourceBoundary = boundary;
|
|
120
|
+
} else if (
|
|
121
|
+
boundary.start !== state.sourceBoundary.start ||
|
|
122
|
+
boundary.length !== state.sourceBoundary.length ||
|
|
123
|
+
boundary.sha256 !== state.sourceBoundary.sha256
|
|
124
|
+
) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
"Perps performance capture source was truncated or rewritten after phase=start.",
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return content;
|
|
130
|
+
}
|
|
131
|
+
|
|
36
132
|
function parseRecord(line, marker) {
|
|
37
133
|
const markerIndex = line.indexOf(marker);
|
|
38
134
|
if (markerIndex < 0) return null;
|
|
@@ -56,83 +152,279 @@ function parseDetailedRecord(line) {
|
|
|
56
152
|
return marker ? parseRecord(line, marker) : null;
|
|
57
153
|
}
|
|
58
154
|
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
.
|
|
65
|
-
|
|
155
|
+
function identityField(record, field) {
|
|
156
|
+
if (field === "perps_session_id") {
|
|
157
|
+
if (Object.hasOwn(record, "perps_session_id")) {
|
|
158
|
+
return { present: true, value: record.perps_session_id };
|
|
159
|
+
}
|
|
160
|
+
if (Object.hasOwn(record, "session_id")) {
|
|
161
|
+
return { present: true, value: record.session_id };
|
|
162
|
+
}
|
|
163
|
+
return { present: false, value: undefined };
|
|
164
|
+
}
|
|
165
|
+
return Object.hasOwn(record, field)
|
|
166
|
+
? { present: true, value: record[field] }
|
|
167
|
+
: { present: false, value: undefined };
|
|
66
168
|
}
|
|
67
169
|
|
|
68
|
-
function
|
|
69
|
-
const
|
|
70
|
-
if (
|
|
170
|
+
function hasValidIdentityField(record, field) {
|
|
171
|
+
const identity = identityField(record, field);
|
|
172
|
+
if (
|
|
173
|
+
!identity.present ||
|
|
174
|
+
identity.value === null ||
|
|
175
|
+
identity.value === undefined
|
|
176
|
+
) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
if (GENERATION_IDENTITY_FIELDS.has(field)) {
|
|
180
|
+
return (
|
|
181
|
+
typeof identity.value === "number" &&
|
|
182
|
+
Number.isInteger(identity.value) &&
|
|
183
|
+
identity.value >= 0
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
return String(identity.value).trim().length > 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function hasCompleteIdentity(record, fields = PERFORMANCE_IDENTITY_FIELDS) {
|
|
190
|
+
return fields.every((field) => hasValidIdentityField(record, field));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function sameIdentity(left, right, fields = PERFORMANCE_IDENTITY_FIELDS) {
|
|
71
194
|
return (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
195
|
+
hasCompleteIdentity(left, fields) &&
|
|
196
|
+
hasCompleteIdentity(right, fields) &&
|
|
197
|
+
fields.every((field) => {
|
|
198
|
+
const leftField = identityField(left, field);
|
|
199
|
+
const rightField = identityField(right, field);
|
|
200
|
+
return String(leftField.value) === String(rightField.value);
|
|
201
|
+
})
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function identityKey(record, fields = PERFORMANCE_IDENTITY_FIELDS) {
|
|
206
|
+
return JSON.stringify(
|
|
207
|
+
fields.map((field) => {
|
|
208
|
+
const value = identityField(record, field);
|
|
209
|
+
return value.present ? [field, String(value.value)] : [field, "<missing>"];
|
|
210
|
+
}),
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function performanceRecordTime(record) {
|
|
215
|
+
const value = Number(
|
|
216
|
+
record.frame_checkpoint_monotonic_ms ?? record.monotonic_ms,
|
|
76
217
|
);
|
|
218
|
+
return Number.isFinite(value) ? value : null;
|
|
77
219
|
}
|
|
78
220
|
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
221
|
+
function isAccountContentVariant(value) {
|
|
222
|
+
return ["positions", "orders", "positions_and_orders"].includes(
|
|
223
|
+
String(value ?? ""),
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function orderedSurfaceSequence(records, demand, requiredStages) {
|
|
228
|
+
const stages = SURFACE_STAGE_ORDER.filter((stage) =>
|
|
229
|
+
requiredStages.includes(stage),
|
|
230
|
+
);
|
|
231
|
+
if (stages.length === 0) return [demand];
|
|
232
|
+
const demandAt = performanceRecordTime(demand);
|
|
233
|
+
if (demandAt === null) return null;
|
|
234
|
+
const sequence = [];
|
|
235
|
+
let previousAt = demandAt;
|
|
236
|
+
for (const stage of stages) {
|
|
237
|
+
if (stage === "surface_demand") {
|
|
238
|
+
sequence.push(demand);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const matches = records
|
|
242
|
+
.filter(
|
|
243
|
+
(record) =>
|
|
244
|
+
record.stage === stage &&
|
|
245
|
+
sameIdentity(record, demand),
|
|
246
|
+
)
|
|
247
|
+
.sort(
|
|
248
|
+
(left, right) =>
|
|
249
|
+
performanceRecordTime(left) - performanceRecordTime(right),
|
|
250
|
+
);
|
|
251
|
+
if (matches.length !== 1) return null;
|
|
252
|
+
const match = matches[0];
|
|
253
|
+
const matchAt = performanceRecordTime(match);
|
|
82
254
|
if (
|
|
83
|
-
|
|
84
|
-
|
|
255
|
+
matchAt === null ||
|
|
256
|
+
matchAt < previousAt ||
|
|
257
|
+
!Number.isFinite(Number(match.duration_ms)) ||
|
|
258
|
+
Number(match.duration_ms) < 0 ||
|
|
259
|
+
(stage === "surface_live_recorded" &&
|
|
260
|
+
(match.fresh_for_lifecycle !== true || !frameIsFresh(match)))
|
|
85
261
|
) {
|
|
86
|
-
|
|
262
|
+
return null;
|
|
87
263
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
264
|
+
sequence.push(match);
|
|
265
|
+
previousAt = matchAt;
|
|
266
|
+
}
|
|
267
|
+
return sequence;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function coherentPerformanceProof(
|
|
271
|
+
records,
|
|
272
|
+
loadProofRecords,
|
|
273
|
+
requiredStages,
|
|
274
|
+
requiredLiveStreams,
|
|
275
|
+
expectedLifecycle = null,
|
|
276
|
+
expectedContentVariant = null,
|
|
277
|
+
) {
|
|
278
|
+
const requiredSurfaceStages = requiredStages.filter((stage) =>
|
|
279
|
+
SURFACE_STAGES.has(stage),
|
|
280
|
+
);
|
|
281
|
+
const required =
|
|
282
|
+
requiredSurfaceStages.length > 0 || requiredLiveStreams.length > 0;
|
|
283
|
+
if (!required) {
|
|
284
|
+
return {
|
|
285
|
+
status: "not_required",
|
|
286
|
+
demand: null,
|
|
287
|
+
identity: null,
|
|
288
|
+
missingLiveStreams: [],
|
|
289
|
+
sequence: [],
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const demands = records.filter((record) => record.stage === "surface_demand");
|
|
293
|
+
let best = null;
|
|
294
|
+
for (const demand of demands) {
|
|
295
|
+
if (!hasCompleteIdentity(demand)) continue;
|
|
296
|
+
if (
|
|
297
|
+
records.filter(
|
|
298
|
+
(record) =>
|
|
299
|
+
record.stage === "surface_demand" && sameIdentity(record, demand),
|
|
300
|
+
).length !== 1
|
|
301
|
+
) {
|
|
302
|
+
continue;
|
|
114
303
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
304
|
+
if (
|
|
305
|
+
expectedLifecycle !== null &&
|
|
306
|
+
String(demand.lifecycle ?? "") !== expectedLifecycle
|
|
307
|
+
) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const sequence = orderedSurfaceSequence(
|
|
311
|
+
records,
|
|
312
|
+
demand,
|
|
313
|
+
requiredSurfaceStages,
|
|
314
|
+
);
|
|
315
|
+
if (!sequence) continue;
|
|
316
|
+
if (
|
|
317
|
+
expectedContentVariant !== null &&
|
|
318
|
+
sequence
|
|
319
|
+
.filter((record) => record.stage !== "surface_demand")
|
|
320
|
+
.some(
|
|
321
|
+
(record) =>
|
|
322
|
+
!contentVariantSatisfies(
|
|
323
|
+
String(record.content_variant ?? ""),
|
|
324
|
+
expectedContentVariant,
|
|
325
|
+
),
|
|
326
|
+
)
|
|
327
|
+
) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const liveRecords = loadProofRecords.filter(
|
|
331
|
+
(record) =>
|
|
332
|
+
record.stage === "values_ready" &&
|
|
333
|
+
record.source === "fresh_socket" &&
|
|
334
|
+
hasCompleteIdentity(record, LOAD_PROOF_IDENTITY_FIELDS) &&
|
|
335
|
+
sameIdentity(record, demand, LOAD_PROOF_BASE_IDENTITY_FIELDS),
|
|
336
|
+
);
|
|
337
|
+
const connectionGenerations = new Set(
|
|
338
|
+
liveRecords.map((record) => record.connection_generation),
|
|
339
|
+
);
|
|
340
|
+
const liveRecordConnectionGeneration =
|
|
341
|
+
connectionGenerations.size === 1
|
|
342
|
+
? [...connectionGenerations][0]
|
|
343
|
+
: null;
|
|
344
|
+
const liveSurface = sequence.find(
|
|
345
|
+
(record) => record.stage === "surface_live_recorded",
|
|
346
|
+
);
|
|
347
|
+
const accountLiveSurface =
|
|
348
|
+
liveSurface && isAccountContentVariant(liveSurface.content_variant)
|
|
349
|
+
? liveSurface
|
|
350
|
+
: null;
|
|
351
|
+
const surfaceConnectionGeneration =
|
|
352
|
+
accountLiveSurface &&
|
|
353
|
+
hasValidIdentityField(accountLiveSurface, "connection_generation")
|
|
354
|
+
? accountLiveSurface.connection_generation
|
|
355
|
+
: null;
|
|
356
|
+
const identityCoherent =
|
|
357
|
+
connectionGenerations.size <= 1 &&
|
|
358
|
+
(!accountLiveSurface ||
|
|
359
|
+
(surfaceConnectionGeneration !== null &&
|
|
360
|
+
(liveRecordConnectionGeneration === null ||
|
|
361
|
+
surfaceConnectionGeneration === liveRecordConnectionGeneration)));
|
|
362
|
+
const coherentConnectionGeneration =
|
|
363
|
+
liveRecordConnectionGeneration ?? surfaceConnectionGeneration;
|
|
364
|
+
const liveStreams = new Set(
|
|
365
|
+
liveRecordConnectionGeneration === null
|
|
366
|
+
? []
|
|
367
|
+
: liveRecords.map((record) => String(record.stream ?? "")),
|
|
368
|
+
);
|
|
369
|
+
const missingLiveStreams = requiredLiveStreams.filter(
|
|
370
|
+
(stream) => !liveStreams.has(stream),
|
|
371
|
+
);
|
|
372
|
+
const candidate = {
|
|
373
|
+
demand,
|
|
374
|
+
sequence,
|
|
375
|
+
missingLiveStreams,
|
|
376
|
+
identityCoherent,
|
|
377
|
+
connectionGeneration: coherentConnectionGeneration,
|
|
378
|
+
};
|
|
379
|
+
if (
|
|
380
|
+
!best ||
|
|
381
|
+
Number(identityCoherent) > Number(best.identityCoherent) ||
|
|
382
|
+
(identityCoherent === best.identityCoherent &&
|
|
383
|
+
missingLiveStreams.length < best.missingLiveStreams.length)
|
|
384
|
+
) {
|
|
385
|
+
best = candidate;
|
|
386
|
+
}
|
|
387
|
+
if (identityCoherent && missingLiveStreams.length === 0) break;
|
|
128
388
|
}
|
|
129
|
-
return
|
|
389
|
+
return {
|
|
390
|
+
status:
|
|
391
|
+
best?.identityCoherent && best.missingLiveStreams.length === 0
|
|
392
|
+
? "pass"
|
|
393
|
+
: "fail",
|
|
394
|
+
demand: best?.demand ?? null,
|
|
395
|
+
identity: best
|
|
396
|
+
? {
|
|
397
|
+
...Object.fromEntries(
|
|
398
|
+
PERFORMANCE_IDENTITY_FIELDS.map((field) => {
|
|
399
|
+
const value = identityField(best.demand, field);
|
|
400
|
+
return [field, value.present ? value.value : null];
|
|
401
|
+
}),
|
|
402
|
+
),
|
|
403
|
+
connection_generation: best.connectionGeneration,
|
|
404
|
+
}
|
|
405
|
+
: null,
|
|
406
|
+
missingLiveStreams: best?.missingLiveStreams ?? requiredLiveStreams,
|
|
407
|
+
sequence: best?.sequence.map((record) => record.stage) ?? [],
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function latestPerpsBootstrapStartRecord(content) {
|
|
412
|
+
return content
|
|
413
|
+
.split(/\r?\n/u)
|
|
414
|
+
.filter((line) => line.includes(LOAD_PROOF_MARKER))
|
|
415
|
+
.map((line) => parseRecord(line, LOAD_PROOF_MARKER))
|
|
416
|
+
.filter((record) => record?.stage === "perps_bootstrap_start")
|
|
417
|
+
.at(-1) ?? null;
|
|
130
418
|
}
|
|
131
419
|
|
|
132
420
|
function mergeLoadProofRecords(records) {
|
|
133
421
|
const byStageStreamSource = new Map();
|
|
134
422
|
for (const record of records) {
|
|
135
|
-
const
|
|
423
|
+
const identityFields =
|
|
424
|
+
record.stage === "values_ready"
|
|
425
|
+
? LOAD_PROOF_IDENTITY_FIELDS
|
|
426
|
+
: LOAD_PROOF_BASE_IDENTITY_FIELDS;
|
|
427
|
+
const key = `${record.stage ?? "unknown"}:${record.stream ?? ""}:${record.source ?? ""}:${identityKey(record, identityFields)}`;
|
|
136
428
|
const current = byStageStreamSource.get(key);
|
|
137
429
|
if (record.stage === "perps_bootstrap_start") {
|
|
138
430
|
if (
|
|
@@ -143,14 +435,9 @@ function mergeLoadProofRecords(records) {
|
|
|
143
435
|
}
|
|
144
436
|
continue;
|
|
145
437
|
}
|
|
146
|
-
const recordIsFallback =
|
|
147
|
-
record.evidence === "existing_sentry_ws_marker";
|
|
148
|
-
const currentIsFallback =
|
|
149
|
-
current?.evidence === "existing_sentry_ws_marker";
|
|
150
438
|
const recordElapsedMs = Number(record.elapsed_ms);
|
|
151
439
|
const currentElapsedMs = Number(current?.elapsed_ms);
|
|
152
|
-
const
|
|
153
|
-
recordIsFallback === currentIsFallback &&
|
|
440
|
+
const earlier =
|
|
154
441
|
record.elapsed_ms !== null &&
|
|
155
442
|
record.elapsed_ms !== undefined &&
|
|
156
443
|
Number.isFinite(recordElapsedMs) &&
|
|
@@ -158,18 +445,14 @@ function mergeLoadProofRecords(records) {
|
|
|
158
445
|
current?.elapsed_ms !== undefined &&
|
|
159
446
|
Number.isFinite(currentElapsedMs) &&
|
|
160
447
|
recordElapsedMs < currentElapsedMs;
|
|
161
|
-
if (
|
|
162
|
-
!current ||
|
|
163
|
-
(currentIsFallback && !recordIsFallback) ||
|
|
164
|
-
sameEvidenceClassEarlier
|
|
165
|
-
) {
|
|
448
|
+
if (!current || earlier) {
|
|
166
449
|
byStageStreamSource.set(key, record);
|
|
167
450
|
}
|
|
168
451
|
}
|
|
169
452
|
return [...byStageStreamSource.values()];
|
|
170
453
|
}
|
|
171
454
|
|
|
172
|
-
async function waitForPerformanceRecord({ node, sourceFile,
|
|
455
|
+
async function waitForPerformanceRecord({ node, sourceFile, state }) {
|
|
173
456
|
const stage = node.wait_for_stage;
|
|
174
457
|
const requiredLiveStreams = Array.isArray(node.required_live_streams)
|
|
175
458
|
? node.required_live_streams.map(String)
|
|
@@ -187,6 +470,9 @@ async function waitForPerformanceRecord({ node, sourceFile, offset }) {
|
|
|
187
470
|
...(node.wait_for_lifecycle !== undefined
|
|
188
471
|
? { lifecycle: String(node.wait_for_lifecycle) }
|
|
189
472
|
: {}),
|
|
473
|
+
...(node.wait_for_content_variant !== undefined
|
|
474
|
+
? { content_variant: String(node.wait_for_content_variant) }
|
|
475
|
+
: {}),
|
|
190
476
|
};
|
|
191
477
|
const timeoutMs = Math.max(1, Number(node.wait_timeout_ms ?? 60_000));
|
|
192
478
|
const pollIntervalMs = Math.max(
|
|
@@ -196,8 +482,8 @@ async function waitForPerformanceRecord({ node, sourceFile, offset }) {
|
|
|
196
482
|
const deadline = Date.now() + timeoutMs;
|
|
197
483
|
|
|
198
484
|
while (true) {
|
|
199
|
-
const content = await
|
|
200
|
-
const start =
|
|
485
|
+
const content = await readCaptureSource(sourceFile, state);
|
|
486
|
+
const start = state.offset;
|
|
201
487
|
const segmentLines = content
|
|
202
488
|
.subarray(start)
|
|
203
489
|
.toString("utf8")
|
|
@@ -206,33 +492,42 @@ async function waitForPerformanceRecord({ node, sourceFile, offset }) {
|
|
|
206
492
|
.filter(detailedMarker)
|
|
207
493
|
.map(parseDetailedRecord)
|
|
208
494
|
.filter(Boolean);
|
|
209
|
-
const stageMatched =
|
|
210
|
-
expected === null ||
|
|
211
|
-
performanceRecords.some((record) =>
|
|
212
|
-
Object.entries(expected).every(
|
|
213
|
-
([key, value]) => String(record[key] ?? "") === value,
|
|
214
|
-
),
|
|
215
|
-
);
|
|
216
495
|
const markerLoadProofRecords = segmentLines
|
|
217
496
|
.filter((line) => line.includes(LOAD_PROOF_MARKER))
|
|
218
497
|
.map((line) => parseRecord(line, LOAD_PROOF_MARKER))
|
|
219
498
|
.filter(Boolean);
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
]
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const liveStreamsMatched = requiredLiveStreams.every((stream) =>
|
|
233
|
-
liveStreams.has(stream),
|
|
499
|
+
const loadProofRecords = mergeLoadProofRecords(markerLoadProofRecords);
|
|
500
|
+
const coherentProof = coherentPerformanceProof(
|
|
501
|
+
performanceRecords,
|
|
502
|
+
loadProofRecords,
|
|
503
|
+
expected && SURFACE_STAGES.has(expected.stage) ? [expected.stage] : [],
|
|
504
|
+
requiredLiveStreams,
|
|
505
|
+
node.wait_for_lifecycle === undefined
|
|
506
|
+
? null
|
|
507
|
+
: String(node.wait_for_lifecycle),
|
|
508
|
+
node.wait_for_content_variant === undefined
|
|
509
|
+
? null
|
|
510
|
+
: String(node.wait_for_content_variant),
|
|
234
511
|
);
|
|
235
|
-
|
|
512
|
+
const stageMatched =
|
|
513
|
+
expected === null ||
|
|
514
|
+
performanceRecords.some(
|
|
515
|
+
(record) =>
|
|
516
|
+
Object.entries(expected).every(
|
|
517
|
+
([key, value]) =>
|
|
518
|
+
key === "content_variant"
|
|
519
|
+
? contentVariantSatisfies(
|
|
520
|
+
String(record.content_variant ?? ""),
|
|
521
|
+
value,
|
|
522
|
+
)
|
|
523
|
+
: String(record[key] ?? "") === value,
|
|
524
|
+
) &&
|
|
525
|
+
(coherentProof.demand === null ||
|
|
526
|
+
sameIdentity(record, coherentProof.demand)),
|
|
527
|
+
);
|
|
528
|
+
const coherenceMatched =
|
|
529
|
+
coherentProof.status === "not_required" || coherentProof.status === "pass";
|
|
530
|
+
if (stageMatched && coherenceMatched) return;
|
|
236
531
|
if (Date.now() >= deadline) {
|
|
237
532
|
throw new Error(
|
|
238
533
|
`Timed out waiting ${timeoutMs}ms for ${MARKER} stage ${JSON.stringify(expected)} and live streams ${JSON.stringify(requiredLiveStreams)}.`,
|
|
@@ -271,13 +566,19 @@ function startupMeasurements(records) {
|
|
|
271
566
|
}
|
|
272
567
|
|
|
273
568
|
function perpsBootstrapBoundary(homepageRecords, loadProofRecords) {
|
|
274
|
-
const
|
|
569
|
+
const demands = homepageRecords
|
|
275
570
|
.filter((record) => record.stage === "surface_demand")
|
|
276
|
-
.
|
|
277
|
-
.
|
|
278
|
-
const
|
|
571
|
+
.filter((record) => Number.isFinite(Number(record.monotonic_ms)))
|
|
572
|
+
.sort((left, right) => Number(left.monotonic_ms) - Number(right.monotonic_ms));
|
|
573
|
+
const demand = demands[0] ?? null;
|
|
574
|
+
const firstDemandAt = demand === null ? null : Number(demand.monotonic_ms);
|
|
279
575
|
const bootstrapTimes = loadProofRecords
|
|
280
|
-
.filter(
|
|
576
|
+
.filter(
|
|
577
|
+
(record) =>
|
|
578
|
+
record.stage === "perps_bootstrap_start" &&
|
|
579
|
+
demand !== null &&
|
|
580
|
+
sameIdentity(record, demand, LOAD_PROOF_BASE_IDENTITY_FIELDS),
|
|
581
|
+
)
|
|
281
582
|
.map((record) => Number(record.monotonic_ms))
|
|
282
583
|
.filter(
|
|
283
584
|
(value) =>
|
|
@@ -395,13 +696,8 @@ function frameIsCached(record) {
|
|
|
395
696
|
}
|
|
396
697
|
|
|
397
698
|
function visibleFrameGroups(records) {
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
.filter((record) => record.stage === "surface_demand")
|
|
401
|
-
.map((record) => [
|
|
402
|
-
String(record.demand_id ?? ""),
|
|
403
|
-
record.lifecycle ?? "unknown",
|
|
404
|
-
]),
|
|
699
|
+
const demands = records.filter(
|
|
700
|
+
(record) => record.stage === "surface_demand",
|
|
405
701
|
);
|
|
406
702
|
const groups = new Map();
|
|
407
703
|
for (const record of records) {
|
|
@@ -409,12 +705,13 @@ function visibleFrameGroups(records) {
|
|
|
409
705
|
const at = frameTime(record);
|
|
410
706
|
if (at === null) continue;
|
|
411
707
|
const demandId = String(record.demand_id ?? "");
|
|
412
|
-
const key = `${
|
|
708
|
+
const key = `${identityKey(record)}:${at}`;
|
|
709
|
+
const demand = demands.find((candidate) => sameIdentity(candidate, record));
|
|
413
710
|
const group = groups.get(key) ?? {
|
|
414
711
|
at,
|
|
415
712
|
demandId,
|
|
416
|
-
|
|
417
|
-
|
|
713
|
+
demand,
|
|
714
|
+
lifecycle: record.lifecycle ?? demand?.lifecycle ?? "unknown",
|
|
418
715
|
records: [],
|
|
419
716
|
};
|
|
420
717
|
group.records.push(record);
|
|
@@ -451,23 +748,29 @@ function frameGroupSatisfies(group, requiredVariants, predicate) {
|
|
|
451
748
|
|
|
452
749
|
function cacheTakeoverMeasurement(records, requiredContentVariants = []) {
|
|
453
750
|
const groups = visibleFrameGroups(records);
|
|
454
|
-
const cached = groups.find(
|
|
455
|
-
|
|
751
|
+
const cached = groups.find(
|
|
752
|
+
(group) =>
|
|
753
|
+
group.demand &&
|
|
754
|
+
frameGroupSatisfies(group, requiredContentVariants, frameIsCached),
|
|
456
755
|
);
|
|
457
756
|
const fresh = cached
|
|
458
757
|
? groups.find(
|
|
459
758
|
(group) =>
|
|
460
759
|
group.at > cached.at &&
|
|
760
|
+
group.demand &&
|
|
761
|
+
sameIdentity(group.demand, cached.demand) &&
|
|
461
762
|
frameGroupSatisfies(group, requiredContentVariants, frameIsFresh),
|
|
462
763
|
)
|
|
463
764
|
: null;
|
|
464
765
|
const cacheHydratedValues = records
|
|
465
|
-
.filter((record) => record.stage === "disk_cache_hydrated")
|
|
466
|
-
.map((record) => Number(record.monotonic_ms))
|
|
467
766
|
.filter(
|
|
468
|
-
(
|
|
469
|
-
|
|
470
|
-
|
|
767
|
+
(record) =>
|
|
768
|
+
record.stage === "disk_cache_hydrated" &&
|
|
769
|
+
cached !== undefined &&
|
|
770
|
+
sameIdentity(record, cached.demand),
|
|
771
|
+
)
|
|
772
|
+
.map((record) => Number(record.monotonic_ms))
|
|
773
|
+
.filter((value) => Number.isFinite(value) && value <= cached.at);
|
|
471
774
|
const cacheHydratedAt =
|
|
472
775
|
cacheHydratedValues.length > 0 ? Math.max(...cacheHydratedValues) : null;
|
|
473
776
|
const failureReason = !cached
|
|
@@ -511,29 +814,42 @@ function visibleMeasurements(records, requiredFreshContentVariants = []) {
|
|
|
511
814
|
return demands.map((demand) => {
|
|
512
815
|
const demandId = String(demand.demand_id ?? "");
|
|
513
816
|
const startedAt = Number(demand.monotonic_ms);
|
|
514
|
-
const belongsToDemand = (record) =>
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
record[key] === undefined ||
|
|
520
|
-
String(record[key]) === String(demand[key]),
|
|
521
|
-
);
|
|
817
|
+
const belongsToDemand = (record) => sameIdentity(record, demand);
|
|
818
|
+
const afterDemand = (record) => {
|
|
819
|
+
const at = performanceRecordTime(record);
|
|
820
|
+
return at !== null && Number.isFinite(startedAt) && at >= startedAt;
|
|
821
|
+
};
|
|
522
822
|
const frames = records.filter(
|
|
523
823
|
(record) =>
|
|
524
|
-
record.stage === "next_frame_checkpoint" &&
|
|
824
|
+
record.stage === "next_frame_checkpoint" &&
|
|
825
|
+
belongsToDemand(record) &&
|
|
826
|
+
afterDemand(record),
|
|
525
827
|
);
|
|
526
828
|
const commits = records.filter(
|
|
527
|
-
(record) => record.stage === "react_commit" && belongsToDemand(record),
|
|
528
|
-
);
|
|
529
|
-
const firstVisibleBoundary = records.find(
|
|
530
829
|
(record) =>
|
|
531
|
-
record.stage === "
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
(record) =>
|
|
535
|
-
record.stage === "surface_live_recorded" && belongsToDemand(record),
|
|
830
|
+
record.stage === "react_commit" &&
|
|
831
|
+
belongsToDemand(record) &&
|
|
832
|
+
afterDemand(record),
|
|
536
833
|
);
|
|
834
|
+
const firstVisibleBoundary = records
|
|
835
|
+
.filter(
|
|
836
|
+
(record) =>
|
|
837
|
+
record.stage === "surface_resolved_recorded" &&
|
|
838
|
+
belongsToDemand(record) &&
|
|
839
|
+
afterDemand(record),
|
|
840
|
+
)
|
|
841
|
+
.sort((left, right) => frameTime(left) - frameTime(right))[0];
|
|
842
|
+
const firstVisibleBoundaryAt = frameTime(firstVisibleBoundary ?? {});
|
|
843
|
+
const freshVisibleBoundary = records
|
|
844
|
+
.filter(
|
|
845
|
+
(record) =>
|
|
846
|
+
record.stage === "surface_live_recorded" &&
|
|
847
|
+
belongsToDemand(record) &&
|
|
848
|
+
afterDemand(record) &&
|
|
849
|
+
(firstVisibleBoundaryAt === null ||
|
|
850
|
+
frameTime(record) >= firstVisibleBoundaryAt),
|
|
851
|
+
)
|
|
852
|
+
.sort((left, right) => frameTime(left) - frameTime(right))[0];
|
|
537
853
|
const frameTimes = frames
|
|
538
854
|
.map((record) =>
|
|
539
855
|
Number(record.frame_checkpoint_monotonic_ms ?? record.monotonic_ms),
|
|
@@ -601,6 +917,11 @@ function visibleMeasurements(records, requiredFreshContentVariants = []) {
|
|
|
601
917
|
const dataAges = frames
|
|
602
918
|
.map((record) => Number(record.data_age_ms))
|
|
603
919
|
.filter(Number.isFinite);
|
|
920
|
+
const deliveryRecords = [
|
|
921
|
+
...frames,
|
|
922
|
+
firstVisibleBoundary,
|
|
923
|
+
freshVisibleBoundary,
|
|
924
|
+
].filter(Boolean);
|
|
604
925
|
return {
|
|
605
926
|
demandId,
|
|
606
927
|
lifecycle: demand.lifecycle ?? "unknown",
|
|
@@ -625,10 +946,15 @@ function visibleMeasurements(records, requiredFreshContentVariants = []) {
|
|
|
625
946
|
? roundMs(freshVisibleAt - startedAt)
|
|
626
947
|
: null,
|
|
627
948
|
deliverySources: [
|
|
628
|
-
...new Set(
|
|
949
|
+
...new Set(
|
|
950
|
+
deliveryRecords
|
|
951
|
+
.map((record) => String(record.source ?? ""))
|
|
952
|
+
.filter(Boolean),
|
|
953
|
+
),
|
|
629
954
|
],
|
|
630
955
|
contentVariant:
|
|
631
|
-
|
|
956
|
+
deliveryRecords.find((record) => record.content_variant)
|
|
957
|
+
?.content_variant ??
|
|
632
958
|
null,
|
|
633
959
|
maxDataAgeMs: dataAges.length > 0 ? Math.max(...dataAges) : null,
|
|
634
960
|
};
|
|
@@ -694,18 +1020,31 @@ function socketPipelineMeasurements(records) {
|
|
|
694
1020
|
return sockets.flatMap((socket) => {
|
|
695
1021
|
const deliveryId = String(socket.delivery_id);
|
|
696
1022
|
const socketAt = Number(socket.monotonic_ms);
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
1023
|
+
if (!Number.isFinite(socketAt)) return [];
|
|
1024
|
+
const commit = records
|
|
1025
|
+
.filter(
|
|
1026
|
+
(record) =>
|
|
1027
|
+
record.stage === "react_commit" &&
|
|
1028
|
+
String(record.delivery_id ?? "") === deliveryId &&
|
|
1029
|
+
sameIdentity(record, socket) &&
|
|
1030
|
+
Number(record.monotonic_ms) >= socketAt,
|
|
1031
|
+
)
|
|
1032
|
+
.sort(
|
|
1033
|
+
(left, right) => Number(left.monotonic_ms) - Number(right.monotonic_ms),
|
|
1034
|
+
)[0];
|
|
1035
|
+
if (!commit) return [];
|
|
708
1036
|
const commitAt = Number(commit.monotonic_ms);
|
|
1037
|
+
const frame = records
|
|
1038
|
+
.filter(
|
|
1039
|
+
(record) =>
|
|
1040
|
+
record.stage === "next_frame_checkpoint" &&
|
|
1041
|
+
String(record.delivery_id ?? "") === deliveryId &&
|
|
1042
|
+
sameIdentity(record, socket) &&
|
|
1043
|
+
frameTime(record) !== null &&
|
|
1044
|
+
frameTime(record) >= commitAt,
|
|
1045
|
+
)
|
|
1046
|
+
.sort((left, right) => frameTime(left) - frameTime(right))[0];
|
|
1047
|
+
if (!frame) return [];
|
|
709
1048
|
const frameAt = Number(
|
|
710
1049
|
frame.frame_checkpoint_monotonic_ms ?? frame.monotonic_ms,
|
|
711
1050
|
);
|
|
@@ -714,14 +1053,14 @@ function socketPipelineMeasurements(records) {
|
|
|
714
1053
|
(record) =>
|
|
715
1054
|
record.stage === "subscriber_delivery" &&
|
|
716
1055
|
String(record.delivery_id ?? "") === deliveryId &&
|
|
1056
|
+
sameIdentity(record, socket) &&
|
|
1057
|
+
Number(record.monotonic_ms) >= socketAt &&
|
|
717
1058
|
Number(record.monotonic_ms) <= commitAt,
|
|
718
1059
|
)
|
|
719
1060
|
.map((record) => Number(record.monotonic_ms))
|
|
720
1061
|
.filter(Number.isFinite);
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
? Math.max(...subscriberCandidates)
|
|
724
|
-
: socketAt;
|
|
1062
|
+
if (subscriberCandidates.length === 0) return [];
|
|
1063
|
+
const subscriberAt = Math.max(...subscriberCandidates);
|
|
725
1064
|
return [
|
|
726
1065
|
{
|
|
727
1066
|
deliveryId,
|
|
@@ -757,14 +1096,20 @@ function firstFreshDeliveryChecks(records, requirements) {
|
|
|
757
1096
|
record.delivery_id,
|
|
758
1097
|
);
|
|
759
1098
|
const delivery = socket
|
|
760
|
-
? records
|
|
761
|
-
(
|
|
762
|
-
record
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
1099
|
+
? records
|
|
1100
|
+
.filter(
|
|
1101
|
+
(record) =>
|
|
1102
|
+
record.stage === "subscriber_delivery" &&
|
|
1103
|
+
record.delivery_id === socket.delivery_id &&
|
|
1104
|
+
record.stream === stream &&
|
|
1105
|
+
sameIdentity(record, socket) &&
|
|
1106
|
+
Number(record.monotonic_ms) >= Number(socket.monotonic_ms) &&
|
|
1107
|
+
Number(record.throttle_ms ?? 0) === subscriberThrottleMs,
|
|
1108
|
+
)
|
|
1109
|
+
.sort(
|
|
1110
|
+
(left, right) =>
|
|
1111
|
+
Number(left.monotonic_ms) - Number(right.monotonic_ms),
|
|
1112
|
+
)[0]
|
|
768
1113
|
: null;
|
|
769
1114
|
const socketAt = Number(socket?.monotonic_ms);
|
|
770
1115
|
const subscriberAt = Number(delivery?.monotonic_ms);
|
|
@@ -797,6 +1142,7 @@ function summarize(
|
|
|
797
1142
|
bytesScanned,
|
|
798
1143
|
requiredFreshContentVariants = [],
|
|
799
1144
|
firstFreshDeliveryRequirements = [],
|
|
1145
|
+
includeCacheTakeover = false,
|
|
800
1146
|
) {
|
|
801
1147
|
const measurements = visibleMeasurements(
|
|
802
1148
|
records,
|
|
@@ -849,10 +1195,9 @@ function summarize(
|
|
|
849
1195
|
monotonicValues.length > 0 ? Math.max(...monotonicValues) : null,
|
|
850
1196
|
visibleMeasurements: measurements,
|
|
851
1197
|
visibleMeasurementCohorts: visibleMeasurementCohorts(measurements),
|
|
852
|
-
cacheTakeoverMeasurement:
|
|
853
|
-
records,
|
|
854
|
-
|
|
855
|
-
),
|
|
1198
|
+
cacheTakeoverMeasurement: includeCacheTakeover
|
|
1199
|
+
? cacheTakeoverMeasurement(records, requiredFreshContentVariants)
|
|
1200
|
+
: { status: "not_required" },
|
|
856
1201
|
socketPipelineMeasurements: socketPipelineMeasurements(records),
|
|
857
1202
|
firstFreshDeliveryChecks: firstFreshDeliveryChecks(
|
|
858
1203
|
records,
|
|
@@ -885,15 +1230,29 @@ export async function capturePerpsPerformance(input) {
|
|
|
885
1230
|
await mkdir(artifactsDir, { recursive: true });
|
|
886
1231
|
|
|
887
1232
|
if (phase === "start") {
|
|
888
|
-
const
|
|
889
|
-
const
|
|
890
|
-
|
|
1233
|
+
const sourceIdentity = await fileState(sourceFile);
|
|
1234
|
+
const offset = sourceIdentity?.size ?? 0;
|
|
1235
|
+
const lookbackStart = Math.max(0, offset - BOOTSTRAP_LOOKBACK_BYTES);
|
|
1236
|
+
const existingContent = sourceIdentity
|
|
1237
|
+
? (
|
|
1238
|
+
await readFileRange(sourceFile, lookbackStart, offset - lookbackStart)
|
|
1239
|
+
).toString("utf8")
|
|
1240
|
+
: "";
|
|
1241
|
+
const boundaryStart = Math.max(0, offset - CAPTURE_BOUNDARY_BYTES);
|
|
1242
|
+
const sourceBoundary = sourceIdentity
|
|
1243
|
+
? captureBoundaryBytes(
|
|
1244
|
+
boundaryStart,
|
|
1245
|
+
await readFileRange(sourceFile, boundaryStart, offset - boundaryStart),
|
|
1246
|
+
)
|
|
1247
|
+
: null;
|
|
891
1248
|
await writeFile(
|
|
892
1249
|
stateFile,
|
|
893
1250
|
`${JSON.stringify(
|
|
894
1251
|
{
|
|
895
1252
|
sourcePath,
|
|
896
1253
|
offset,
|
|
1254
|
+
sourceIdentity,
|
|
1255
|
+
sourceBoundary,
|
|
897
1256
|
captureStartedAtEpochMs: Date.now(),
|
|
898
1257
|
perpsBootstrapStartRecord:
|
|
899
1258
|
latestPerpsBootstrapStartRecord(existingContent),
|
|
@@ -918,6 +1277,19 @@ export async function capturePerpsPerformance(input) {
|
|
|
918
1277
|
state.sourcePath !== sourcePath ||
|
|
919
1278
|
!Number.isInteger(state.offset) ||
|
|
920
1279
|
state.offset < 0 ||
|
|
1280
|
+
!(
|
|
1281
|
+
state.sourceIdentity === null ||
|
|
1282
|
+
(state.sourceIdentity &&
|
|
1283
|
+
Number.isInteger(state.sourceIdentity.device) &&
|
|
1284
|
+
Number.isInteger(state.sourceIdentity.inode))
|
|
1285
|
+
) ||
|
|
1286
|
+
!(
|
|
1287
|
+
state.sourceBoundary === null ||
|
|
1288
|
+
(state.sourceBoundary &&
|
|
1289
|
+
Number.isInteger(state.sourceBoundary.start) &&
|
|
1290
|
+
Number.isInteger(state.sourceBoundary.length) &&
|
|
1291
|
+
typeof state.sourceBoundary.sha256 === "string")
|
|
1292
|
+
) ||
|
|
921
1293
|
!Number.isFinite(state.captureStartedAtEpochMs)
|
|
922
1294
|
) {
|
|
923
1295
|
throw new Error(
|
|
@@ -928,11 +1300,11 @@ export async function capturePerpsPerformance(input) {
|
|
|
928
1300
|
await waitForPerformanceRecord({
|
|
929
1301
|
node,
|
|
930
1302
|
sourceFile,
|
|
931
|
-
|
|
1303
|
+
state,
|
|
932
1304
|
});
|
|
933
1305
|
|
|
934
|
-
const content = await
|
|
935
|
-
const offset = state.offset
|
|
1306
|
+
const content = await readCaptureSource(sourceFile, state);
|
|
1307
|
+
const offset = state.offset;
|
|
936
1308
|
const segment = content.subarray(offset).toString("utf8");
|
|
937
1309
|
const segmentLines = segment.split(/\r?\n/u);
|
|
938
1310
|
const lines = segmentLines
|
|
@@ -959,10 +1331,7 @@ export async function capturePerpsPerformance(input) {
|
|
|
959
1331
|
.map((line) => parseRecord(line, LOAD_PROOF_MARKER))
|
|
960
1332
|
.filter(Boolean),
|
|
961
1333
|
];
|
|
962
|
-
const loadProofRecords = mergeLoadProofRecords(
|
|
963
|
-
...markerLoadProofRecords,
|
|
964
|
-
...existingLiveStreamRecords(segmentLines, markerLoadProofRecords),
|
|
965
|
-
]);
|
|
1334
|
+
const loadProofRecords = mergeLoadProofRecords(markerLoadProofRecords);
|
|
966
1335
|
const requireRecords = node.require_records !== false;
|
|
967
1336
|
const requiredStages = Array.isArray(node.required_stages)
|
|
968
1337
|
? node.required_stages.map(String)
|
|
@@ -1048,17 +1417,19 @@ export async function capturePerpsPerformance(input) {
|
|
|
1048
1417
|
const requiredLiveStreams = Array.isArray(node.required_live_streams)
|
|
1049
1418
|
? node.required_live_streams.map(String)
|
|
1050
1419
|
: [];
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1420
|
+
const coherentProof = coherentPerformanceProof(
|
|
1421
|
+
records,
|
|
1422
|
+
loadProofRecords,
|
|
1423
|
+
requiredStages,
|
|
1424
|
+
requiredLiveStreams,
|
|
1425
|
+
node.wait_for_lifecycle === undefined
|
|
1426
|
+
? null
|
|
1427
|
+
: String(node.wait_for_lifecycle),
|
|
1428
|
+
node.wait_for_content_variant === undefined
|
|
1429
|
+
? null
|
|
1430
|
+
: String(node.wait_for_content_variant),
|
|
1061
1431
|
);
|
|
1432
|
+
const missingLiveStreams = coherentProof.missingLiveStreams;
|
|
1062
1433
|
const measuredPerpsBootstrapBoundary = perpsBootstrapBoundary(
|
|
1063
1434
|
records,
|
|
1064
1435
|
loadProofRecords,
|
|
@@ -1088,6 +1459,7 @@ export async function capturePerpsPerformance(input) {
|
|
|
1088
1459
|
missingFreshContentVariants.length === 0 &&
|
|
1089
1460
|
failedFirstFreshDeliveries.length === 0 &&
|
|
1090
1461
|
missingLiveStreams.length === 0 &&
|
|
1462
|
+
coherentProof.status !== "fail" &&
|
|
1091
1463
|
(!requirePerpsBootstrapStartBeforeDemand ||
|
|
1092
1464
|
measuredPerpsBootstrapBoundary.status === "pass") &&
|
|
1093
1465
|
(!requireCacheBeforeFresh || cacheTakeover.status === "pass")
|
|
@@ -1102,6 +1474,9 @@ export async function capturePerpsPerformance(input) {
|
|
|
1102
1474
|
missingFreshContentVariants,
|
|
1103
1475
|
failedFirstFreshDeliveries,
|
|
1104
1476
|
missingLiveStreams,
|
|
1477
|
+
coherentSurfaceAndLive: coherentProof.status,
|
|
1478
|
+
coherentIdentity: coherentProof.identity,
|
|
1479
|
+
coherentSurfaceSequence: coherentProof.sequence,
|
|
1105
1480
|
perpsBootstrapStartBeforeDemand: requirePerpsBootstrapStartBeforeDemand
|
|
1106
1481
|
? measuredPerpsBootstrapBoundary.status
|
|
1107
1482
|
: "not_required",
|
|
@@ -1120,6 +1495,7 @@ export async function capturePerpsPerformance(input) {
|
|
|
1120
1495
|
content.length - offset,
|
|
1121
1496
|
requiredFreshContentVariants,
|
|
1122
1497
|
firstFreshDeliveryRequirements,
|
|
1498
|
+
requireCacheBeforeFresh,
|
|
1123
1499
|
),
|
|
1124
1500
|
captureWindow: {
|
|
1125
1501
|
startedAtEpochMs: state.captureStartedAtEpochMs,
|
|
@@ -1149,6 +1525,11 @@ export async function capturePerpsPerformance(input) {
|
|
|
1149
1525
|
`Missing required ${STARTUP_MARKER} stages: ${missingStartupStages.join(", ")}.`,
|
|
1150
1526
|
);
|
|
1151
1527
|
}
|
|
1528
|
+
if (coherentProof.status === "fail") {
|
|
1529
|
+
throw new Error(
|
|
1530
|
+
`Required ${MARKER} surface stages and live streams did not form one ordered identity-coherent demand tuple.`,
|
|
1531
|
+
);
|
|
1532
|
+
}
|
|
1152
1533
|
if (
|
|
1153
1534
|
requirePerpsBootstrapStartBeforeDemand &&
|
|
1154
1535
|
measuredPerpsBootstrapBoundary.status !== "pass"
|