mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 21:56:18 -08:00
test(perf): measure the serial IPC depth before J3 playing (#1862)
J3 listed renderer.ipcSerialDepthToPlaying as unavailable because the serial-depth helper was not on master yet. #1773 merged computeJourneyIpcSerialDepth and the main IPC capture records the timeline for every journey, so J3 now computes the counter from ipc.timeline as J1 does, with ipcSerialDepth, ipcTimelineAmbiguousCompletions and ipcTimeline as evidence. The unavailable list is empty. No baseline is added: the counter is new, a measured counter without a baseline is only noted by the ratchet, and journey baselines come from the runner once master runs agree. One local run read 3 in all six iterations. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a2d4459562
commit
1058f8d985
3 files changed
+101
-12
No files matched your search
@@ -181,6 +181,7 @@ test('maps the media-terminated probe, IPC window and mock ledger to exact count
|
||||
'renderer.domMutationsToPlaying': 6_188,
|
||||
'renderer.httpRequestsToPlaying': 2,
|
||||
'renderer.ipcCallsToPlaying': 4,
|
||||
'renderer.ipcSerialDepthToPlaying': 3,
|
||||
'renderer.layoutShiftScore': 0.001,
|
||||
'renderer.longTasks': 0,
|
||||
});
|
||||
@@ -225,6 +226,71 @@ test('maps the media-terminated probe, IPC window and mock ledger to exact count
|
||||
assert.equal(record.evidence['ipcCallsAfterPlaying'], 3);
|
||||
});
|
||||
|
||||
test('derives the serial IPC depth from the capture timeline, not the call count', () => {
|
||||
const record = toPlaybackIterationRecord(1, false, measurement());
|
||||
// xtreamRequest overlaps getEpgMapping and updateRemoteControlStatus, so
|
||||
// four calls take three round trips.
|
||||
assert.deepEqual(record.evidence['ipcSerialDepth'], {
|
||||
chain: ['setUserAgent', 'getEpgMapping', 'updateRemoteControlStatus'],
|
||||
depth: 3,
|
||||
depthLowerBound: 3,
|
||||
inFlightAtEnd: 0,
|
||||
});
|
||||
assert.deepEqual(record.evidence['ipcTimeline'], [
|
||||
'+setUserAgent',
|
||||
'-setUserAgent',
|
||||
'+xtreamRequest',
|
||||
'+getEpgMapping',
|
||||
'-getEpgMapping',
|
||||
'+updateRemoteControlStatus',
|
||||
'-updateRemoteControlStatus',
|
||||
'-xtreamRequest',
|
||||
]);
|
||||
assert.equal(record.evidence['ipcTimelineAmbiguousCompletions'], 0);
|
||||
|
||||
const base = measurement();
|
||||
const parallel = toPlaybackIterationRecord(1, false, {
|
||||
...base,
|
||||
ipc: {
|
||||
...base.ipc,
|
||||
ambiguousTimelineCompletions: 1,
|
||||
timeline: [
|
||||
{ method: 'setUserAgent', phase: 'start' },
|
||||
{ method: 'xtreamRequest', phase: 'start' },
|
||||
{ method: 'getEpgMapping', phase: 'start' },
|
||||
{ method: 'updateRemoteControlStatus', phase: 'start' },
|
||||
{ method: 'getEpgMapping', phase: 'end' },
|
||||
{ method: 'setUserAgent', phase: 'end' },
|
||||
{ method: 'xtreamRequest', phase: 'end' },
|
||||
],
|
||||
},
|
||||
});
|
||||
assert.equal(parallel.counters['renderer.ipcCallsToPlaying'], 4);
|
||||
// A call still in flight at `playing` is excluded from the depth.
|
||||
assert.equal(parallel.counters['renderer.ipcSerialDepthToPlaying'], 1);
|
||||
assert.equal(
|
||||
(parallel.evidence['ipcSerialDepth'] as { inFlightAtEnd: number })
|
||||
.inFlightAtEnd,
|
||||
1
|
||||
);
|
||||
assert.equal(parallel.evidence['ipcTimelineAmbiguousCompletions'], 1);
|
||||
});
|
||||
|
||||
test('fails the iteration on a timeline completion without a start', () => {
|
||||
const base = measurement();
|
||||
assert.throws(
|
||||
() =>
|
||||
toPlaybackIterationRecord(0, false, {
|
||||
...base,
|
||||
ipc: {
|
||||
...base.ipc,
|
||||
timeline: [{ method: 'xtreamRequest', phase: 'end' }],
|
||||
},
|
||||
}),
|
||||
/journey-ipc-serial-depth-unmatched-end:xtreamRequest/
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects measurements that did not start at a live channel or did not play a video', () => {
|
||||
const base = measurement();
|
||||
const start = base.renderer.start as Start;
|
||||
@@ -345,7 +411,7 @@ test('rejects activity that moved between the settle snapshot and the click', ()
|
||||
);
|
||||
});
|
||||
|
||||
test('summarizes playback iterations with J3 counters and unavailable reasons', () => {
|
||||
test('summarizes playback iterations with every J3 counter measured', () => {
|
||||
const iterations = [0, 1, 2].map((index) =>
|
||||
toPlaybackIterationRecord(index, index === 0, {
|
||||
...measurement(),
|
||||
@@ -364,7 +430,10 @@ test('summarizes playback iterations with J3 counters and unavailable reasons',
|
||||
assert.equal(entry.wallClock['clickToPlayingMs.p50'], 349.8);
|
||||
assert.equal(entry.wallClock['clickToLoadedMetadataMs.p90'], 95);
|
||||
assert.equal(entry.counters['renderer.cdTicksToPlaying'], 14);
|
||||
assert.deepEqual(Object.keys(entry.unavailable).sort(), [
|
||||
'renderer.ipcSerialDepthToPlaying',
|
||||
]);
|
||||
assert.equal(entry.counters['renderer.ipcSerialDepthToPlaying'], 3);
|
||||
assert.deepEqual(
|
||||
entry.counterStability['renderer.ipcSerialDepthToPlaying'],
|
||||
{ stable: true, values: [3, 3] }
|
||||
);
|
||||
assert.deepEqual(entry.unavailable, {});
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
journeyActivityBeforeClick,
|
||||
type JourneyClickSettle,
|
||||
} from './journey-click-settle';
|
||||
import { computeJourneyIpcSerialDepth } from './journey-ipc-serial-depth';
|
||||
import type { JourneyMainIpcCaptureState } from './journey-main-ipc-capture';
|
||||
import {
|
||||
countJourneyMockRoutes,
|
||||
@@ -23,6 +24,7 @@ export const PLAYBACK_JOURNEY_COUNTER = {
|
||||
DOM_MUTATIONS: 'renderer.domMutationsToPlaying',
|
||||
HTTP_REQUESTS: 'renderer.httpRequestsToPlaying',
|
||||
IPC_CALLS: 'renderer.ipcCallsToPlaying',
|
||||
IPC_SERIAL_DEPTH: 'renderer.ipcSerialDepthToPlaying',
|
||||
LAYOUT_SHIFT_SCORE: 'renderer.layoutShiftScore',
|
||||
LONG_TASKS: 'renderer.longTasks',
|
||||
} as const;
|
||||
@@ -37,12 +39,13 @@ export const PLAYBACK_JOURNEY_WALL_CLOCK = {
|
||||
/** How long requests after `playing` are observed, for evidence only. */
|
||||
export const PLAYBACK_JOURNEY_AFTER_PLAYING_WINDOW_MS = 1_000;
|
||||
|
||||
/**
|
||||
* Counters the plan lists for J3 that this harness cannot measure. Every one
|
||||
* is measured now; the list stays so a future gap is reported, not faked.
|
||||
*/
|
||||
export const PLAYBACK_JOURNEY_UNAVAILABLE_COUNTERS: Readonly<
|
||||
Record<string, string>
|
||||
> = Object.freeze({
|
||||
'renderer.ipcSerialDepthToPlaying':
|
||||
'The serial-depth helper is being added for J1 in a separate thread and is not on master yet; J3 adopts it once it lands.',
|
||||
});
|
||||
> = Object.freeze({});
|
||||
|
||||
export interface PlaybackJourneyMeasurement {
|
||||
/** Logo requests to picsum.photos cancelled in the main process. */
|
||||
@@ -148,6 +151,7 @@ export function toPlaybackIterationRecord(
|
||||
`playback-journey-record-cd-ticks-${renderer.capabilities.changeDetectionTicks}`
|
||||
);
|
||||
}
|
||||
const serialDepth = computeJourneyIpcSerialDepth(ipc.timeline);
|
||||
return Object.freeze({
|
||||
counters: Object.freeze({
|
||||
[PLAYBACK_JOURNEY_COUNTER.CD_TICKS]: cdTicks,
|
||||
@@ -155,6 +159,7 @@ export function toPlaybackIterationRecord(
|
||||
renderer.counters.domMutations,
|
||||
[PLAYBACK_JOURNEY_COUNTER.HTTP_REQUESTS]: http.toPlaying.length,
|
||||
[PLAYBACK_JOURNEY_COUNTER.IPC_CALLS]: ipc.callsBeforeSentinel,
|
||||
[PLAYBACK_JOURNEY_COUNTER.IPC_SERIAL_DEPTH]: serialDepth.depth,
|
||||
// The click's 500 ms input window covers the start of the
|
||||
// journey, so shifts flagged hadRecentInput are included, as
|
||||
// in J2.
|
||||
@@ -196,6 +201,13 @@ export function toPlaybackIterationRecord(
|
||||
}),
|
||||
ipcCallsAfterPlaying: ipc.callsAfterSentinel,
|
||||
ipcCallsByMethod: ipc.callsByMethod,
|
||||
ipcSerialDepth: serialDepth,
|
||||
ipcTimelineAmbiguousCompletions: ipc.ambiguousTimelineCompletions,
|
||||
// `+method` for a start, `-method` for a completion.
|
||||
ipcTimeline: ipc.timeline.map(
|
||||
({ method, phase }) =>
|
||||
`${phase === 'start' ? '+' : '-'}${method}`
|
||||
),
|
||||
layoutShift: Object.freeze({
|
||||
recentInput: roundThousandth(
|
||||
renderer.counters.recentInputLayoutShiftScore
|
||||
|
||||
@@ -821,6 +821,7 @@ ignored.
|
||||
| Counter | Source |
|
||||
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `renderer.ipcCallsToPlaying` | Bridge `start` trace events between the start and end sentinels, as `renderer.ipcCallsToFirstPage` in J2. |
|
||||
| `renderer.ipcSerialDepthToPlaying` | `computeJourneyIpcSerialDepth` over the capture's timeline between the start and end sentinels, as `renderer.ipcSerialDepthToResults` in J4 (see [Serial IPC depth](#serial-ipc-depth)). `evidence.ipcSerialDepth.chain` and `evidence.ipcTimeline` show the calls; `evidence.ipcTimelineAmbiguousCompletions` counts completions the start marker left ambiguous. |
|
||||
| `renderer.httpRequestsToPlaying` | Requests the ledger proxy received from the click stamp until the `playing` stamp, from either process (the stream request comes from the renderer, Xtream API calls from the main process). Both stamps are `performance.timeOrigin + performance.now()` of processes on the same host clock, as in J2. Unlike J2's counter the window ends at the terminal, not at a quiet mock: a live stream has no quiet end. Later requests are kept as `evidence.httpRequestsAfterPlayingByRoute`, the ones in the window as `evidence.httpRequestsByRoute`. |
|
||||
| `renderer.domMutationsToPlaying` | `MutationRecord`s from the click until the `playing` event, including records still queued when it fires. |
|
||||
| `renderer.cdTicksToPlaying` | `ApplicationRef` ticks from the click until the `playing` event, read like `renderer.cdTicksToFirstPage` in J2 (see [Change-detection ticks](#change-detection-ticks)). Not yet measured on a run; the counter shipped after J3's first measurements. |
|
||||
@@ -841,10 +842,7 @@ above a sub-millisecond origin difference. `evidence.httpRequestsAfterPlayingByR
|
||||
window of 1 s after `playing` (the test waits that long before reading the
|
||||
ledger), not a quiet mock as in J2: a live stream has no quiet end.
|
||||
|
||||
One counter is listed under `unavailable`.
|
||||
`renderer.ipcSerialDepthToPlaying` is missing because the serial-depth
|
||||
helper (see [Startup work before the first card](#startup-work-before-the-first-card))
|
||||
was not on `master` when J3 landed; J3 adopts it once the J1 thread adds it.
|
||||
No counter is listed under `unavailable`.
|
||||
`main.sqlStatementsToPlaying` is not measured for the same reason as J2's
|
||||
SQL counter.
|
||||
|
||||
@@ -877,6 +875,13 @@ mutation and request counts. On the runner `renderer.httpRequestsToPlaying`
|
||||
and `renderer.layoutShiftScore` are enforced as guards; see
|
||||
[Enforced journey counters](#enforced-journey-counters).
|
||||
|
||||
`renderer.ipcSerialDepthToPlaying` came later. One local macOS
|
||||
`perf:journeys` run on 2026-10-10 read 3 in all six iterations, warm-up
|
||||
included, with `depthLowerBound` 3, no ambiguous completion and no call in
|
||||
flight at `playing`. The chain was `getEpgMapping` → `xtreamRequest` →
|
||||
`updateRemoteControlStatus`; the last is the second of two
|
||||
`updateRemoteControlStatus` calls in the window (five calls in all).
|
||||
|
||||
## J4 `search`: type a query until the results settle
|
||||
|
||||
`search.journey.ts` follows J3: the profile is seeded once through the "Add
|
||||
@@ -1299,6 +1304,9 @@ Not enforced, with the reason:
|
||||
- J3 `renderer.ipcCallsToPlaying` (4 or 5) and
|
||||
`renderer.domMutationsToPlaying` (6,182, 6,183 or 6,199): not identical,
|
||||
and the EPG rendering work changes the mutation count.
|
||||
- J3 `renderer.ipcSerialDepthToPlaying` (3 locally): added after these
|
||||
runs and not yet measured on the runner; a candidate once `master` runs
|
||||
agree.
|
||||
- J4: not measured yet.
|
||||
|
||||
Runner counters differ from a Mac (the Linux-only `getWindowState` call, for
|
||||
|
||||
Reference in new issue
Block a user