206 lines
6.6 KiB
TypeScript
206 lines
6.6 KiB
TypeScript
import type {
|
|
Counter,
|
|
Histogram,
|
|
Meter,
|
|
MeterProvider,
|
|
} from "@opentelemetry/api";
|
|
import {
|
|
afterEach,
|
|
beforeEach,
|
|
describe,
|
|
expect,
|
|
it,
|
|
} from "@suaveplan/testing/runner";
|
|
import {
|
|
getInstruments,
|
|
recordOp,
|
|
resetInstruments,
|
|
setStorageBrowserOpfsMeterProvider,
|
|
} from "./otel.js";
|
|
|
|
interface RecordedHistogram {
|
|
name: string;
|
|
samples: { value: number; attrs: Record<string, unknown> | undefined }[];
|
|
}
|
|
interface RecordedCounter {
|
|
name: string;
|
|
additions: { value: number; attrs: Record<string, unknown> | undefined }[];
|
|
}
|
|
|
|
class StubHistogram implements Histogram {
|
|
public readonly entry: RecordedHistogram;
|
|
public constructor(name: string, bag: RecordedHistogram[]) {
|
|
this.entry = { name, samples: [] };
|
|
bag.push(this.entry);
|
|
}
|
|
public record(value: number, attrs?: Record<string, unknown>): void {
|
|
this.entry.samples.push({ value, attrs });
|
|
}
|
|
}
|
|
|
|
class StubCounter implements Counter {
|
|
public readonly entry: RecordedCounter;
|
|
public constructor(name: string, bag: RecordedCounter[]) {
|
|
this.entry = { name, additions: [] };
|
|
bag.push(this.entry);
|
|
}
|
|
public add(value: number, attrs?: Record<string, unknown>): void {
|
|
this.entry.additions.push({ value, attrs });
|
|
}
|
|
}
|
|
|
|
class StubMeter implements Meter {
|
|
public histograms: RecordedHistogram[] = [];
|
|
public counters: RecordedCounter[] = [];
|
|
public createHistogram(name: string): Histogram {
|
|
return new StubHistogram(name, this.histograms);
|
|
}
|
|
public createCounter(name: string): Counter {
|
|
return new StubCounter(name, this.counters);
|
|
}
|
|
public createObservableGauge(): never {
|
|
throw new Error("unused");
|
|
}
|
|
public createUpDownCounter(): never {
|
|
throw new Error("unused");
|
|
}
|
|
public createObservableCounter(): never {
|
|
throw new Error("unused");
|
|
}
|
|
public createObservableUpDownCounter(): never {
|
|
throw new Error("unused");
|
|
}
|
|
public addBatchObservableCallback(): void {}
|
|
public removeBatchObservableCallback(): void {}
|
|
public createGauge(): never {
|
|
throw new Error("unused");
|
|
}
|
|
}
|
|
|
|
class StubMeterProvider implements MeterProvider {
|
|
public meters: StubMeter[] = [];
|
|
public getMeter(): Meter {
|
|
const meter = new StubMeter();
|
|
this.meters.push(meter);
|
|
return meter;
|
|
}
|
|
}
|
|
|
|
describe("storage-browser-opfs otel", () => {
|
|
let provider: StubMeterProvider;
|
|
beforeEach(() => {
|
|
provider = new StubMeterProvider();
|
|
setStorageBrowserOpfsMeterProvider(provider);
|
|
});
|
|
afterEach(() => {
|
|
setStorageBrowserOpfsMeterProvider(undefined);
|
|
resetInstruments();
|
|
});
|
|
|
|
function findCounter(name: string): RecordedCounter | undefined {
|
|
return provider.meters[0]?.counters.find((c) => c.name === name);
|
|
}
|
|
function findHistogram(name: string): RecordedHistogram | undefined {
|
|
return provider.meters[0]?.histograms.find((h) => h.name === name);
|
|
}
|
|
|
|
it("recordOp emits the documented counter and histogram set", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
recordOp({
|
|
op: "set",
|
|
outcome: "success",
|
|
durationMs: 12,
|
|
bytes: 100,
|
|
direction: "write",
|
|
});
|
|
const dur = findHistogram("storage_browser_opfs.op_duration_ms");
|
|
expect(dur?.samples).toEqual([
|
|
{ value: 12, attrs: { op: "set", outcome: "success" } },
|
|
]);
|
|
const writes = findCounter("storage_browser_opfs.writes_total");
|
|
expect(writes?.additions).toEqual([
|
|
{ value: 1, attrs: { op: "set", outcome: "success" } },
|
|
]);
|
|
const io = findHistogram("storage_browser_opfs.io_bytes");
|
|
expect(io?.samples).toEqual([
|
|
{ value: 100, attrs: { op: "set", direction: "write" } },
|
|
]);
|
|
});
|
|
|
|
it("records error counter on a failed op", { timeout: 5000 }, () => {
|
|
recordOp({ op: "get", outcome: "error", durationMs: 3 });
|
|
const errs = findCounter("storage_browser_opfs.errors_total");
|
|
expect(errs?.additions).toEqual([
|
|
{ value: 1, attrs: { op: "get", outcome: "error" } },
|
|
]);
|
|
});
|
|
|
|
it("records aborted operations on the errors counter", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
recordOp({ op: "get", outcome: "aborted", durationMs: 0 });
|
|
expect(
|
|
findCounter("storage_browser_opfs.errors_total")?.additions[0]
|
|
?.attrs
|
|
).toEqual({ op: "get", outcome: "aborted" });
|
|
});
|
|
|
|
it("increments reads/writes/deletes counters by op kind", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
recordOp({ op: "get", outcome: "success", durationMs: 1 });
|
|
recordOp({ op: "getPartial", outcome: "success", durationMs: 1 });
|
|
recordOp({ op: "getWithMetadata", outcome: "success", durationMs: 1 });
|
|
recordOp({ op: "set", outcome: "success", durationMs: 1 });
|
|
recordOp({ op: "setPartial", outcome: "success", durationMs: 1 });
|
|
recordOp({ op: "delete", outcome: "success", durationMs: 1 });
|
|
recordOp({ op: "clear", outcome: "success", durationMs: 1 });
|
|
recordOp({ op: "exists", outcome: "success", durationMs: 1 });
|
|
recordOp({ op: "list", outcome: "success", durationMs: 1 });
|
|
expect(
|
|
findCounter("storage_browser_opfs.reads_total")?.additions
|
|
).toHaveLength(3);
|
|
expect(
|
|
findCounter("storage_browser_opfs.writes_total")?.additions
|
|
).toHaveLength(2);
|
|
expect(
|
|
findCounter("storage_browser_opfs.deletes_total")?.additions
|
|
).toHaveLength(2);
|
|
});
|
|
|
|
it("returns a cached instrument set on repeat getInstruments calls", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const a = getInstruments();
|
|
const b = getInstruments();
|
|
expect(a).toBe(b);
|
|
});
|
|
|
|
it("rebuilds the instrument set when getInstruments(true) is called", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const a = getInstruments();
|
|
const b = getInstruments(true);
|
|
expect(a).not.toBe(b);
|
|
});
|
|
|
|
it("omits the io_bytes record when bytes is undefined", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
recordOp({ op: "list", outcome: "success", durationMs: 5 });
|
|
expect(
|
|
findHistogram("storage_browser_opfs.io_bytes")?.samples ?? []
|
|
).toHaveLength(0);
|
|
});
|
|
|
|
it("uses the global metrics fallback when provider is unset", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
setStorageBrowserOpfsMeterProvider(undefined);
|
|
// Should not throw — falls back to the global no-op meter provider.
|
|
const inst = getInstruments(true);
|
|
expect(inst.meter).toBeDefined();
|
|
});
|
|
});
|