Initial Commit
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
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(),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user