fix(process): localize capture truncation diagnostics (#1284)

Report per-source capture coverage and existing file-hash omissions while preserving comparable observations in unaffected dimensions. Keep legacy truncated captures conservative and retain known partial trace events.

Closes #1250
Partially addresses #1251; independent hashing remains out of scope.
This commit is contained in:
Ysopwnable authored and GitHub committed 2026-10-09 21:56:43 +08:00
1 parent 7e96f25f8b
commit 33bf585185
14 files changed
+739 -50

No files matched your search

+30 -3
View File
@@ -1,4 +1,5 @@
import { readFile, readdir } from "node:fs/promises";
import type { ProcessSamplingCoverage } from "../../domain/process/processCaptureCoverage.js";
import type {
ProcessSample,
RecordProcessCaptureEvent,
@@ -455,7 +456,10 @@ export const startProcessSampler = (options: {
readonly limit: number;
readonly samples: ProcessSample[];
readonly recordEvent?: RecordProcessCaptureEvent;
}): (() => Promise<{ readonly partial: boolean }>) => {
}): (() => Promise<{
readonly partial: boolean;
readonly coverage: ProcessSamplingCoverage;
}>) => {
const { rootPid, runId, started, limit, samples } = options;
const identities = new Map<number, string>();
const lastObservations = new Map<number, string>();
@@ -463,6 +467,9 @@ export const startProcessSampler = (options: {
let stopped = false;
let abortCurrent: (() => void) | undefined;
let partial = false;
let limitReached = false;
let samplingFailures = 0;
let firstSamplingFailure: string | null = null;
let rootInitialized = false;
const sample = (): void => {
@@ -501,6 +508,7 @@ export const startProcessSampler = (options: {
if (lastObservations.get(value.pid) === observation) continue;
if (samples.length >= limit) {
partial = true;
limitReached = true;
continue;
}
lastObservations.set(value.pid, observation);
@@ -510,8 +518,16 @@ export const startProcessSampler = (options: {
}
})
.catch((cause: unknown) => {
if (!(cause instanceof Error && cause.name === "AbortError"))
if (!(cause instanceof Error && cause.name === "AbortError")) {
partial = true;
samplingFailures += 1;
firstSamplingFailure ??=
cause instanceof Error
? `${cause.name}: ${cause.message}`
: typeof cause === "string"
? cause
: `Unexpected sampling rejection (${typeof cause})`;
}
})
.then(() => {
abortCurrent = undefined;
@@ -527,6 +543,17 @@ export const startProcessSampler = (options: {
clearInterval(timer);
abortCurrent?.();
await pending;
return { partial };
return {
partial,
coverage: {
sample_limit: limit,
sampling_partial: partial,
retained_samples: samples.length,
sample_limit_reached: limitReached,
sampling_failures: samplingFailures,
first_sampling_failure: firstSamplingFailure,
coverage: "sampled",
},
};
};
};