Files
OmniRoute/tests/unit/chatcore-streaming-pipeline.test.ts
Paijo 3b7090a1cc feat(perf): add performance.mark/measure to SSE pipeline + request-size metric (#7045)
* feat(perf): add performance.mark/measure to SSE pipeline + request-size metric

- streamingPipeline.ts: mark/measure around assembly of SSE transform
  chain — 'omni-pipeline-start'/'omni-pipeline-end'/'omni-pipeline'
- stream.ts: compute JSON body byte count on stream creation, emit as
  performance.mark('omni-request-body-size', { detail: bytes })

Marks are visible via performance.getEntriesByType('mark') and
performance.getEntriesByType('measure') for DevTools/monitoring.

* fix(perf): prevent memory leak and TextEncoder allocation on hot path

- Add performance.clearMarks/clearMeasures before creating new marks to
  prevent timeline accumulation in long-lived processes.
- Replace new TextEncoder().encode(str).length with Buffer.byteLength to
  avoid allocating a full Uint8Array just to measure byte length.

* test(perf): add performance instrumentation tests

* chore(ci): rebaseline stream.ts 2796->2805 for perf instrumentation

Add _rebaseline_ entry documenting the +9 line growth from:
- b48ba21c4: performance.mark/measure instrumentation around SSE dispatch
- c35e8a9b4: TextEncoder hoisting fix

These are irreducible instrumentations at the stream dispatch chokepoint.

* chore: trigger CI re-run

* fix(ci): restore file-size-baseline.json corrupted by prior rebaseline commit

The rebaseline commit (9efdd636d) accidentally replaced the entire
config/quality/file-size-baseline.json with the literal string
"test content" instead of adding the intended stream.ts entry,
breaking JSON.parse() in check:file-size for every subsequent CI run.

Restore the full baseline from origin/release/v3.8.49 and apply the
intended bump: open-sse/utils/stream.ts 2796->2806 (measured LOC,
matching the script's countLines() split("\n").length, not wc -l)
for the performance.mark/measure instrumentation + TextEncoder
hoisting fix added by this PR.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(perf): clear the omni-request-body-size mark immediately after creation

Addresses review feedback on this PR: the fixed-name "omni-request-body-size"
performance mark was created on every createSSEStream() call and never
cleared, so it accumulated without bound in Node's global performance
timeline over a long-running server's lifetime (unlike the pipeline-assembly
marks in streamingPipeline.ts, which are bounded — cleared at the start of
the next call). A wired PerformanceObserver still receives the entry;
clearMarks() only removes it from getEntriesByName()/getEntriesByType().

Rebaseline file-size-baseline.json to the actual measured LOC (2796->2813)
for the comment + clear call, and add
tests/unit/stream-request-body-size-mark-7045.test.ts covering: the mark
fires with the correct JSON-byte-length detail, it does not accumulate
across repeated calls, and it is skipped when there is no request body.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(ci): correct file-size-baseline.json off-by-one for stream.ts

check-file-size.mjs counts lines via fs.readFileSync().split("\n").length,
which is wc -l + 1 for a file ending in a trailing newline (the last split
element is an empty string after the final newline). The previous commit
baselined the wc -l value (2813) instead of the script's own metric (2814),
so CI still failed by exactly 1 line.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-07-18 11:34:01 -03:00

136 lines
5.5 KiB
TypeScript

// Characterization of assembleStreamingPipeline — the streaming transform-chain assembly extracted
// from handleChatCore's streaming success path (chatCore god-file decomposition, #3501). All
// transform factories are injected; a fake "stream" records each pipeThrough so the exact chain
// order and the branch conditions (PII explicit vs feature-flag, progress, echo) are observable
// without real ReadableStreams. Locks: transform order, the progress header side-effect, and the
// PII branch precedence (explicit createPiiTransform wins over the feature flag).
import { test } from "node:test";
import assert from "node:assert/strict";
const { assembleStreamingPipeline } =
await import("../../open-sse/handlers/chatCore/streamingPipeline.ts");
// A fake stream: each pipeThrough appends the transform's tag and returns a new fake stream.
function fakeStream(tag: string, log: string[]) {
return {
tag,
pipeThrough(t: { __tag: string }) {
log.push(t.__tag);
return fakeStream(t.__tag, log);
},
};
}
function makeDeps(over: Record<string, unknown> = {}) {
const log: string[] = [];
const deps = {
wantsProgress: () => false,
pipeWithDisconnect: (..._a: unknown[]) => fakeStream("pii-base", log),
isFeatureFlagEnabled: () => false,
createPiiSseTransform: () => ({ __tag: "pii-flag" }),
createProgressTransform: () => ({ __tag: "progress" }),
createSseHeartbeatTransform: () => ({ __tag: "heartbeat" }),
shapeForClientFormat: (f: unknown) => f,
createModelEchoTransform: () => ({ __tag: "echo" }),
...over,
} as Parameters<typeof assembleStreamingPipeline>[1];
return { deps, log };
}
function baseArgs(over: Record<string, unknown> = {}) {
return {
providerResponse: {},
transformStream: {},
streamController: { signal: {} as AbortSignal },
createPiiTransform: undefined,
clientRawRequestHeaders: null,
clientResponseFormat: "openai",
echoModel: null,
responseHeaders: {} as Record<string, string>,
...over,
} as Parameters<typeof assembleStreamingPipeline>[0];
}
test("baseline (no pii, no progress, no echo) → only heartbeat in the chain", () => {
const { deps, log } = makeDeps();
assembleStreamingPipeline(baseArgs(), deps);
assert.deepEqual(log, ["heartbeat"]);
});
test("feature-flag PII → pii-flag transform applied before heartbeat", () => {
const { deps, log } = makeDeps({ isFeatureFlagEnabled: () => true });
assembleStreamingPipeline(baseArgs(), deps);
assert.deepEqual(log, ["pii-flag", "heartbeat"]);
});
test("explicit createPiiTransform wins over the feature flag", () => {
const { deps, log } = makeDeps({ isFeatureFlagEnabled: () => true });
const explicit = () => ({ __tag: "pii-explicit" });
assembleStreamingPipeline(baseArgs({ createPiiTransform: explicit }), deps);
assert.deepEqual(log, ["pii-explicit", "heartbeat"]);
});
test("progress enabled → progress transform + progress header set", () => {
const { deps, log } = makeDeps({ wantsProgress: () => true });
const args = baseArgs();
assembleStreamingPipeline(args, deps);
assert.deepEqual(log, ["progress", "heartbeat"]);
assert.ok(Object.values(args.responseHeaders).includes("enabled"));
});
test("progress disabled → no progress header", () => {
const { deps } = makeDeps({ wantsProgress: () => false });
const args = baseArgs();
assembleStreamingPipeline(args, deps);
assert.deepEqual(args.responseHeaders, {});
});
test("echoModel set → echo transform applied last", () => {
const { deps, log } = makeDeps();
assembleStreamingPipeline(baseArgs({ echoModel: "alias-x" }), deps);
assert.deepEqual(log, ["heartbeat", "echo"]);
});
test("full chain order: pii → progress → heartbeat → echo", () => {
const { deps, log } = makeDeps({
isFeatureFlagEnabled: () => true,
wantsProgress: () => true,
});
assembleStreamingPipeline(baseArgs({ echoModel: "alias-x" }), deps);
assert.deepEqual(log, ["pii-flag", "progress", "heartbeat", "echo"]);
});
test("pipeline assembly creates performance mark and measure entries", () => {
performance.clearMarks();
performance.clearMeasures();
const { deps } = makeDeps();
assembleStreamingPipeline(baseArgs(), deps);
assert.equal(performance.getEntriesByName("omni-pipeline-start").length, 1, "start mark");
assert.equal(performance.getEntriesByName("omni-pipeline-end").length, 1, "end mark");
assert.equal(performance.getEntriesByName("omni-pipeline").length, 1, "measure");
});
test("re-entering pipeline clears previous timeline entries", () => {
performance.clearMarks();
performance.clearMeasures();
const { deps } = makeDeps();
// First call creates entries
assembleStreamingPipeline(baseArgs(), deps);
// Second call — clearMarks/clearMeasures runs before new marks, so count stays 1
assembleStreamingPipeline(baseArgs(), deps);
assert.equal(performance.getEntriesByName("omni-pipeline-start").length, 1, "start cleared");
assert.equal(performance.getEntriesByName("omni-pipeline-end").length, 1, "end cleared");
assert.equal(performance.getEntriesByName("omni-pipeline").length, 1, "measure cleared");
});
test("performance measure has positive duration", () => {
performance.clearMarks();
performance.clearMeasures();
const { deps } = makeDeps();
assembleStreamingPipeline(baseArgs(), deps);
const [entry] = performance.getEntriesByName("omni-pipeline");
assert.ok(entry, "measure exists");
assert.equal(entry.entryType, "measure");
assert.ok(entry.duration >= 0, `duration ${entry.duration} >= 0`);
});