import type { ResourceAttributionEntry, ResourceAttributionSnapshot } from "@t3tools/contracts"; import * as Context from "effect/Context"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Ref from "effect/Ref"; export interface ResourceAttributionRecord { readonly component: string; readonly operation: string; readonly logicalReadBytes?: number; readonly logicalWriteBytes?: number; readonly count?: number; readonly durationMs?: number; } export class ResourceAttribution extends Context.Service< ResourceAttribution, { readonly record: (input: ResourceAttributionRecord) => Effect.Effect; readonly snapshot: Effect.Effect; } >()("t3/resourceTelemetry/ResourceAttribution") {} function key(input: Pick): string { return `${input.component}\u0000${input.operation}`; } function nonNegativeInteger(value: number | undefined, fallback: number): number { if (value === undefined) return fallback; if (!Number.isFinite(value)) return 0; return Math.max(0, Math.round(value)); } export const make = Effect.fn("resourceTelemetry.resourceAttribution.make")(function* () { const entries = yield* Ref.make(new Map()); const record: ResourceAttribution["Service"]["record"] = (input) => Ref.update(entries, (current) => { const next = new Map(current); const entryKey = key(input); const existing = next.get(entryKey); next.set(entryKey, { component: input.component, operation: input.operation, logicalReadBytes: (existing?.logicalReadBytes ?? 0) + nonNegativeInteger(input.logicalReadBytes, 0), logicalWriteBytes: (existing?.logicalWriteBytes ?? 0) + nonNegativeInteger(input.logicalWriteBytes, 0), count: (existing?.count ?? 0) + nonNegativeInteger(input.count, 1), durationMs: (existing?.durationMs ?? 0) + nonNegativeInteger(input.durationMs, 0), }); return next; }); return ResourceAttribution.of({ record, snapshot: Effect.gen(function* () { const readAt = yield* DateTime.now; const current = yield* Ref.get(entries); return { readAt, entries: [...current.values()].toSorted( (left, right) => right.logicalWriteBytes - left.logicalWriteBytes, ), }; }), }); }); export const layer = Layer.effect(ResourceAttribution, make());