import { metrics, SpanStatusCode, trace } from "@opentelemetry/api"; import type { AudioDocument } from "@suaveplan/audio-core/document/document"; import { AUDIO_ANALYSIS_SPECTRUM_CODES, AudioAnalysisSpectrumError, } from "../error-codes/error-codes.js"; import { type SpectrumAnalyzerOptions, type SpectrumAnalyzerOptionsInput, SpectrumAnalyzerOptionsSchema, } from "./options.schemas.js"; const TRACER = trace.getTracer("@suaveplan/audio-analysis-spectrum"); const METER = metrics.getMeter("@suaveplan/audio-analysis-spectrum"); const RUN_COUNTER = METER.createCounter("audio.analysis.runs_total", { description: "Completed analysis runs by op and mode.", }); const DURATION_HIST = METER.createHistogram("audio.analysis.duration_ms", { description: "Wall-clock duration of analysis runs.", }); const BLOCK_DURATION_HIST = METER.createHistogram( "audio.analysis.block_duration_ms", { description: "Wall-clock duration of single-block analysis." } ); /** Validate option input against the shared Zod schema. */ export function parseOptions( raw: SpectrumAnalyzerOptionsInput | undefined ): SpectrumAnalyzerOptions { const parsed = SpectrumAnalyzerOptionsSchema.safeParse(raw ?? {}); if (!parsed.success) { throw new AudioAnalysisSpectrumError( AUDIO_ANALYSIS_SPECTRUM_CODES.INVALID_OPTIONS, "audio-analysis-spectrum.invalid-options", { context: { issues: parsed.error.issues } } ); } return parsed.data; } /** Throws `ABORTED` if `signal` is already aborted. */ export function ensureNotAborted(signal: AbortSignal | undefined): void { if (signal?.aborted === true) { throw new AudioAnalysisSpectrumError( AUDIO_ANALYSIS_SPECTRUM_CODES.ABORTED, "audio-analysis-spectrum.aborted", { context: { reason: signal.reason } } ); } } export interface OtelRunHandle { readonly end: () => void; readonly fail: (err: unknown) => void; } /** Start an OTel span + duration timer for an `analyze` invocation. */ export function startRun( op: string, doc: AudioDocument, mode: "analyze" | "stream" ): OtelRunHandle { const span = TRACER.startSpan(`audio.analysis.${op}`, { attributes: { "analysis.op": op, "analysis.mode": mode, "audio.sample_rate": doc.sampleRate, "audio.duration_samples": doc.sourceLength, }, }); const startedAt = performance.now(); return { end: () => { RUN_COUNTER.add(1, { op, mode }); DURATION_HIST.record(performance.now() - startedAt, { op, mode }); span.end(); }, fail: (err) => { const error = err as Error; span.setStatus({ code: SpanStatusCode.ERROR, message: error.message, }); span.recordException(error); span.end(); }, }; } /** Record a single `analyzeBlock` invocation in the block-duration histogram. */ export function recordBlock(op: string, durationMs: number): void { BLOCK_DURATION_HIST.record(durationMs, { op }); } /** Build the audio-core-compatible result envelope. */ export function buildBase( op: string, doc: AudioDocument ): { analyserName: string; analyserVersion: string; sampleRate: number; durationSamples: number; computedAt: number; } { return { analyserName: op, analyserVersion: "0.1.0", sampleRate: doc.sampleRate, durationSamples: doc.sourceLength, computedAt: Date.now(), }; }