chore(vendor): import pi 0.85.1
Imported as a linear vendor commit so forge rebases do not replay the upstream root tree. git-subtree-dir: third_party/pi git-subtree-split: d981de1229ef899957bbe968bc8dcda02a21f477
This commit is contained in:
219
third_party/pi/packages/telemetry/src/memory.ts
vendored
Normal file
219
third_party/pi/packages/telemetry/src/memory.ts
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
import type {
|
||||
AttributeValue,
|
||||
SpanAttributes,
|
||||
SpanOptions,
|
||||
SpanStatus,
|
||||
TelemetryContext,
|
||||
TelemetrySpan,
|
||||
} from "./index.ts";
|
||||
import { NOOP_TELEMETRY_CONTEXT } from "./noop.ts";
|
||||
|
||||
export interface RecordedTelemetryEvent {
|
||||
readonly name: string;
|
||||
readonly attributes: Readonly<SpanAttributes>;
|
||||
}
|
||||
|
||||
export interface RecordedTelemetrySpan {
|
||||
readonly id: number;
|
||||
readonly parentId: number | null;
|
||||
readonly name: string;
|
||||
readonly attributes: Readonly<SpanAttributes>;
|
||||
readonly events: readonly RecordedTelemetryEvent[];
|
||||
readonly status: SpanStatus;
|
||||
readonly settled: boolean;
|
||||
readonly endSequence?: number;
|
||||
}
|
||||
|
||||
interface MutableRecordedTelemetryEvent {
|
||||
name: string;
|
||||
attributes: SpanAttributes;
|
||||
}
|
||||
|
||||
interface MutableRecordedTelemetrySpan {
|
||||
id: number;
|
||||
parentId: number | null;
|
||||
name: string;
|
||||
attributes: SpanAttributes;
|
||||
events: MutableRecordedTelemetryEvent[];
|
||||
status: SpanStatus;
|
||||
explicitStatus: boolean;
|
||||
settled: boolean;
|
||||
endSequence?: number;
|
||||
}
|
||||
|
||||
interface InMemoryTelemetryState {
|
||||
spans: MutableRecordedTelemetrySpan[];
|
||||
nextSpanId: number;
|
||||
nextEndSequence: number;
|
||||
}
|
||||
|
||||
function copyAttributeValue(value: AttributeValue): AttributeValue {
|
||||
return Array.isArray(value) ? ([...value] as AttributeValue) : value;
|
||||
}
|
||||
|
||||
function copyAttributes(attributes?: SpanAttributes): SpanAttributes {
|
||||
const copy: SpanAttributes = {};
|
||||
if (!attributes) return copy;
|
||||
for (const [name, value] of Object.entries(attributes)) {
|
||||
if (value !== undefined) copy[name] = copyAttributeValue(value);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
function mergeAttributes(current: SpanAttributes, attributes: SpanAttributes): SpanAttributes {
|
||||
const merged = copyAttributes(current);
|
||||
for (const [name, value] of Object.entries(attributes)) {
|
||||
if (value !== undefined) merged[name] = copyAttributeValue(value);
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
function copyStatus(status: SpanStatus): SpanStatus {
|
||||
if (status.status === "ok") return { status: "ok" };
|
||||
return status.error
|
||||
? { status: "error", error: { name: status.error.name, message: status.error.message } }
|
||||
: { status: "error" };
|
||||
}
|
||||
|
||||
function automaticErrorStatus(error: unknown): SpanStatus {
|
||||
try {
|
||||
if (error instanceof Error) {
|
||||
return { status: "error", error: { name: error.name, message: error.message } };
|
||||
}
|
||||
} catch {
|
||||
// Error inspection is passive. Fall through to an error status without details.
|
||||
}
|
||||
return { status: "error" };
|
||||
}
|
||||
|
||||
function settleSpan(
|
||||
state: InMemoryTelemetryState,
|
||||
span: MutableRecordedTelemetrySpan,
|
||||
failed: boolean,
|
||||
error?: unknown,
|
||||
): void {
|
||||
if (span.settled) return;
|
||||
if (failed && !span.explicitStatus) span.status = automaticErrorStatus(error);
|
||||
span.settled = true;
|
||||
span.endSequence = state.nextEndSequence++;
|
||||
}
|
||||
|
||||
function createSpan(
|
||||
state: InMemoryTelemetryState,
|
||||
parent: MutableRecordedTelemetrySpan | undefined,
|
||||
options: SpanOptions,
|
||||
): MutableRecordedTelemetrySpan {
|
||||
const name = options.name;
|
||||
const attributes = copyAttributes(options.attributes);
|
||||
return {
|
||||
id: state.nextSpanId++,
|
||||
parentId: parent?.id ?? null,
|
||||
name,
|
||||
attributes,
|
||||
events: [],
|
||||
status: { status: "ok" },
|
||||
explicitStatus: false,
|
||||
settled: false,
|
||||
};
|
||||
}
|
||||
|
||||
function startInMemorySpan<T>(
|
||||
state: InMemoryTelemetryState,
|
||||
parent: MutableRecordedTelemetrySpan | undefined,
|
||||
options: SpanOptions,
|
||||
callback: (span: TelemetrySpan) => T | Promise<T>,
|
||||
): Promise<T> {
|
||||
if (parent?.settled) return NOOP_TELEMETRY_CONTEXT.startSpan(options, callback);
|
||||
|
||||
let recordedSpan: MutableRecordedTelemetrySpan;
|
||||
try {
|
||||
recordedSpan = createSpan(state, parent, options);
|
||||
state.spans.push(recordedSpan);
|
||||
} catch {
|
||||
return NOOP_TELEMETRY_CONTEXT.startSpan(options, callback);
|
||||
}
|
||||
|
||||
const span: TelemetrySpan = {
|
||||
startSpan: <Result>(
|
||||
childOptions: SpanOptions,
|
||||
childCallback: (child: TelemetrySpan) => Result | Promise<Result>,
|
||||
) => startInMemorySpan(state, recordedSpan, childOptions, childCallback),
|
||||
addEvent(name, attributes) {
|
||||
if (recordedSpan.settled) return;
|
||||
try {
|
||||
recordedSpan.events.push({ name, attributes: copyAttributes(attributes) });
|
||||
} catch {
|
||||
// Recording is passive. Ignore malformed or unreadable telemetry payloads.
|
||||
}
|
||||
},
|
||||
setAttributes(attributes) {
|
||||
if (recordedSpan.settled) return;
|
||||
try {
|
||||
recordedSpan.attributes = mergeAttributes(recordedSpan.attributes, attributes);
|
||||
} catch {
|
||||
// Recording is passive. Ignore malformed or unreadable telemetry payloads.
|
||||
}
|
||||
},
|
||||
setStatus(status) {
|
||||
if (recordedSpan.settled) return;
|
||||
try {
|
||||
recordedSpan.status = copyStatus(status);
|
||||
recordedSpan.explicitStatus = true;
|
||||
} catch {
|
||||
// Recording is passive. Ignore malformed or unreadable telemetry payloads.
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let result: T | Promise<T>;
|
||||
try {
|
||||
result = callback(span);
|
||||
} catch (error) {
|
||||
settleSpan(state, recordedSpan, true, error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
return Promise.resolve(result).then(
|
||||
(value) => {
|
||||
settleSpan(state, recordedSpan, false);
|
||||
return value;
|
||||
},
|
||||
(error: unknown) => {
|
||||
settleSpan(state, recordedSpan, true, error);
|
||||
throw error;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Backend-neutral reference implementation that records spans in process memory.
|
||||
* Create a fresh instance to isolate tests or independent recording scopes.
|
||||
*/
|
||||
export class InMemoryTelemetryContext implements TelemetryContext {
|
||||
private readonly state: InMemoryTelemetryState = {
|
||||
spans: [],
|
||||
nextSpanId: 1,
|
||||
nextEndSequence: 1,
|
||||
};
|
||||
|
||||
startSpan<T>(options: SpanOptions, callback: (span: TelemetrySpan) => T | Promise<T>): Promise<T> {
|
||||
return startInMemorySpan(this.state, undefined, options, callback);
|
||||
}
|
||||
|
||||
/** Returns detached snapshots in span-start order. */
|
||||
getSpans(): readonly RecordedTelemetrySpan[] {
|
||||
return this.state.spans.map((span) => ({
|
||||
id: span.id,
|
||||
parentId: span.parentId,
|
||||
name: span.name,
|
||||
attributes: copyAttributes(span.attributes),
|
||||
events: span.events.map((event) => ({
|
||||
name: event.name,
|
||||
attributes: copyAttributes(event.attributes),
|
||||
})),
|
||||
status: copyStatus(span.status),
|
||||
settled: span.settled,
|
||||
...(span.endSequence === undefined ? {} : { endSequence: span.endSequence }),
|
||||
}));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user