import { performance } from 'node:perf_hooks'; export class MetricsCollector { constructor() { this.entries = new Map(); } record(name, durationMs, error = null) { let entry = this.entries.get(name); if (!entry) { entry = { name, durations: [], count: 0, errors: 0, totalMs: 0, }; this.entries.set(name, entry); } entry.count += 1; entry.totalMs += durationMs; entry.durations.push(durationMs); if (error) { entry.errors += 1; } } getStats(name) { const entry = this.entries.get(name); if (!entry || entry.count === 0) return null; const sorted = [...entry.durations].sort((a, b) => a - b); const count = entry.count; const totalMs = entry.totalMs; const avgMs = totalMs / count; const minMs = sorted[0]; const maxMs = sorted[count - 1]; const p50Index = Math.min(Math.floor(count * 0.5), count - 1); const p95Index = Math.min(Math.floor(count * 0.95), count - 1); const p99Index = Math.min(Math.floor(count * 0.99), count - 1); const p50Ms = sorted[p50Index]; const p95Ms = sorted[p95Index]; const p99Ms = sorted[p99Index]; return { name, count, errors: entry.errors, totalMs, avgMs, minMs, maxMs, p50Ms, p95Ms, p99Ms, }; } getAllStats() { const results = []; for (const name of this.entries.keys()) { results.push(this.getStats(name)); } return results.sort((a, b) => a.name.localeCompare(b.name)); } } /** * Wraps a LibDashStore instance so all method calls across all namespaces * are intercepted and measured with high-resolution performance timers. * * @param {import('../src/index.js').default} store * @param {MetricsCollector} metrics * @returns {import('../src/index.js').default} Instrumented store */ export function instrumentStore(store, metrics = new MetricsCollector()) { const ignoredProperties = new Set(['provider', 'pool', 'constructor']); for (const [namespaceKey, namespaceVal] of Object.entries(store)) { if (ignoredProperties.has(namespaceKey)) continue; // Direct root function like store.purge or store.initialize if (typeof namespaceVal === 'function') { const originalFn = namespaceVal.bind(store); store[namespaceKey] = async (...args) => { const start = performance.now(); let error = null; try { return await originalFn(...args); } catch (err) { error = err; throw err; } finally { const duration = performance.now() - start; metrics.record(`store.${namespaceKey}`, duration, error); } }; continue; } if (typeof namespaceVal === 'object' && namespaceVal !== null) { for (const [methodKey, methodVal] of Object.entries(namespaceVal)) { if (typeof methodVal === 'function') { const originalMethod = methodVal.bind(namespaceVal); namespaceVal[methodKey] = async (...args) => { const start = performance.now(); let error = null; try { return await originalMethod(...args); } catch (err) { error = err; throw err; } finally { const duration = performance.now() - start; metrics.record(`${namespaceKey}.${methodKey}`, duration, error); } }; } } } } return { store, metrics }; }