/** * Performance Testing Utilities for postgres.do * * This module provides infrastructure for performance baseline tests and * regression detection. It includes: * * - Timing measurement utilities with statistical analysis * - Custom vitest matchers for performance assertions * - Baseline management for tracking expected performance * - Regression detection with configurable thresholds * * @module @dotdo/postgres-shared/perf-test-utils * * @example * ```typescript * import { * measureTiming, * PerformanceBaseline, * setupPerfMatchers, * } from '@dotdo/postgres-shared/perf-test-utils' * * // Setup custom matchers in vitest * setupPerfMatchers() * * describe('performance tests', () => { * it('completes query within baseline', async () => { * const timing = await measureTiming(async () => { * await db.query('SELECT 1') * }) * expect(timing).toBeWithinBaseline('simple-query', { p95: 10 }) * }) * }) * ``` */ /** * Statistical summary of timing measurements */ export interface TimingStats { /** Number of samples collected */ count: number; /** Minimum execution time in milliseconds */ min: number; /** Maximum execution time in milliseconds */ max: number; /** Mean (average) execution time in milliseconds */ mean: number; /** Median (p50) execution time in milliseconds */ median: number; /** 95th percentile execution time in milliseconds */ p95: number; /** 99th percentile execution time in milliseconds */ p99: number; /** Standard deviation in milliseconds */ stdDev: number; /** Coefficient of variation (stdDev/mean) as a ratio */ cv: number; /** All individual timing samples in milliseconds */ samples: number[]; } /** * Options for measuring timing */ export interface MeasureTimingOptions { /** Number of iterations to run (default: 10) */ iterations?: number; /** Number of warmup iterations to discard (default: 2) */ warmupIterations?: number; /** Optional setup function to run before each iteration */ setup?: () => void | Promise; /** Optional teardown function to run after each iteration */ teardown?: () => void | Promise; /** Whether to use high-resolution timer (default: true) */ highResolution?: boolean; } /** * Performance baseline definition */ export interface PerformanceBaselineDefinition { /** Operation name for identification */ name: string; /** Description of what is being measured */ description?: string; /** Expected p50 (median) latency in milliseconds */ p50?: number; /** Expected p95 latency in milliseconds */ p95?: number; /** Expected p99 latency in milliseconds */ p99?: number; /** Expected maximum latency in milliseconds */ maxLatency?: number; /** Expected minimum throughput (operations per second) */ minThroughput?: number; /** Tolerance factor for regression detection (default: 1.2 = 20% tolerance) */ toleranceFactor?: number; /** Environment-specific overrides */ environments?: Record>; } /** * Result of a baseline comparison */ export interface BaselineComparisonResult { /** Whether the measured values are within the baseline */ withinBaseline: boolean; /** Detailed results for each metric */ metrics: { name: string; expected: number; actual: number; tolerance: number; passed: boolean; deviation: number; deviationPercent: number; }[]; /** Summary message */ message: string; } /** * Throughput measurement result */ export interface ThroughputResult { /** Operations per second */ opsPerSecond: number; /** Total operations completed */ totalOps: number; /** Total duration in milliseconds */ durationMs: number; /** Average operation time in milliseconds */ avgOpTimeMs: number; } /** * Options for throughput measurement */ export interface MeasureThroughputOptions { /** Duration to measure in milliseconds (default: 1000) */ durationMs?: number; /** Number of warmup operations to run first (default: 10) */ warmupOps?: number; /** Optional setup function */ setup?: () => void | Promise; /** Optional teardown function */ teardown?: () => void | Promise; } /** * Gets current high-resolution time in milliseconds. * Uses performance.now() when available, falls back to Date.now(). */ export declare function getHighResTime(): number; /** * Calculates statistical summary from an array of timing samples. * * @param samples - Array of timing measurements in milliseconds * @returns Statistical summary of the samples */ export declare function calculateStats(samples: number[]): TimingStats; /** * Measures execution time of an async function with statistical analysis. * * @param fn - Function to measure * @param options - Measurement options * @returns Statistical summary of timing measurements * * @example * ```typescript * const stats = await measureTiming(async () => { * await db.query('SELECT * FROM users LIMIT 100') * }, { iterations: 20 }) * * console.log(`Mean latency: ${stats.mean}ms, p95: ${stats.p95}ms`) * ``` */ export declare function measureTiming(fn: () => void | Promise, options?: MeasureTimingOptions): Promise; /** * Measures execution time of a synchronous function with statistical analysis. * * @param fn - Function to measure * @param options - Measurement options (without async setup/teardown) * @returns Statistical summary of timing measurements */ export declare function measureTimingSync(fn: () => void, options?: Omit & { setup?: () => void; teardown?: () => void; }): TimingStats; /** * Measures throughput (operations per second) for an async operation. * * @param fn - Function to measure * @param options - Measurement options * @returns Throughput measurement result * * @example * ```typescript * const throughput = await measureThroughput(async () => { * await cache.set('key', 'value') * }, { durationMs: 5000 }) * * console.log(`Throughput: ${throughput.opsPerSecond} ops/sec`) * ``` */ export declare function measureThroughput(fn: () => void | Promise, options?: MeasureThroughputOptions): Promise; /** * Measures throughput for a synchronous operation. * * @param fn - Function to measure * @param options - Measurement options * @returns Throughput measurement result */ export declare function measureThroughputSync(fn: () => void, options?: Omit & { setup?: () => void; teardown?: () => void; }): ThroughputResult; /** * Registry of performance baselines for regression detection. */ export declare class PerformanceBaseline { private baselines; private environment; constructor(environment?: string); /** * Registers a performance baseline. * * @param baseline - Baseline definition */ register(baseline: PerformanceBaselineDefinition): void; /** * Registers multiple baselines at once. * * @param baselines - Array of baseline definitions */ registerAll(baselines: PerformanceBaselineDefinition[]): void; /** * Gets a baseline definition by name. * * @param name - Baseline name * @returns Baseline definition or undefined */ get(name: string): PerformanceBaselineDefinition | undefined; /** * Gets all registered baselines. * * @returns Map of all baselines */ getAll(): Map; /** * Gets the effective baseline values for the current environment. * * @param name - Baseline name * @returns Merged baseline with environment overrides applied */ getEffective(name: string): PerformanceBaselineDefinition | undefined; /** * Compares timing stats against a baseline. * * @param name - Baseline name * @param stats - Measured timing statistics * @returns Comparison result */ compare(name: string, stats: TimingStats): BaselineComparisonResult; /** * Compares throughput against a baseline. * * @param name - Baseline name * @param throughput - Measured throughput * @returns Comparison result */ compareThroughput(name: string, throughput: ThroughputResult): BaselineComparisonResult; /** * Sets the current environment for baseline lookups. * * @param environment - Environment name */ setEnvironment(environment: string): void; /** * Clears all registered baselines. */ clear(): void; } /** * Default performance baselines for common postgres.do operations. * These are conservative baselines that should pass in most environments. */ export declare const DEFAULT_BASELINES: PerformanceBaselineDefinition[]; /** * Global baseline registry instance. */ export declare const globalBaselines: PerformanceBaseline; /** * Custom matcher result type for vitest compatibility. */ interface MatcherResult { pass: boolean; message: () => string; } /** * Custom matchers for performance testing in vitest. */ export declare const perfMatchers: { /** * Asserts that timing stats are within a registered baseline. * * @example * expect(stats).toBeWithinBaseline('simple-select') */ toBeWithinBaseline(received: TimingStats, baselineName: string, customBaseline?: Partial): MatcherResult; /** * Asserts that timing is faster than a specified threshold. * * @example * expect(stats).toBeFasterThan(10) // p95 < 10ms */ toBeFasterThan(received: TimingStats, maxP95Ms: number): MatcherResult; /** * Asserts that timing has low variance (deterministic). * * @example * expect(stats).toHaveLowVariance(0.5) // CV < 0.5 */ toHaveLowVariance(received: TimingStats, maxCV?: number): MatcherResult; /** * Asserts that throughput meets minimum requirements. * * @example * expect(throughput).toMeetThroughput(1000) // >= 1000 ops/sec */ toMeetThroughput(received: ThroughputResult, minOpsPerSec: number): MatcherResult; /** * Asserts that a single timing value is within range. * * @example * expect(latency).toBeWithinRange(1, 10) // 1ms <= latency <= 10ms */ toBeWithinRange(received: number, minMs: number, maxMs: number): MatcherResult; }; /** * Type declarations for custom matchers. * This extends vitest's expect interface. */ declare module 'vitest' { interface Assertion { toBeWithinBaseline(baselineName: string, customBaseline?: Partial): this; toBeFasterThan(maxP95Ms: number): this; toHaveLowVariance(maxCV?: number): this; toMeetThroughput(minOpsPerSec: number): this; toBeWithinRange(minMs: number, maxMs: number): this; } interface AsymmetricMatchersContaining { toBeWithinBaseline(baselineName: string, customBaseline?: Partial): unknown; toBeFasterThan(maxP95Ms: number): unknown; toHaveLowVariance(maxCV?: number): unknown; toMeetThroughput(minOpsPerSec: number): unknown; toBeWithinRange(minMs: number, maxMs: number): unknown; } } /** * Sets up performance matchers in vitest. * Call this in your test setup file or at the beginning of performance tests. * * @param expect - The vitest expect function * * @example * ```typescript * import { expect } from 'vitest' * import { setupPerfMatchers } from '@dotdo/postgres-shared/perf-test-utils' * * // In setup file or before tests * setupPerfMatchers(expect) * ``` */ export declare function setupPerfMatchers(expect: { extend: (matchers: Record MatcherResult>) => void; }): void; /** * Creates a simple delay for testing purposes. * * @param ms - Milliseconds to delay */ export declare function delay(ms: number): Promise; /** * Formats timing stats as a human-readable string. * * @param stats - Timing statistics to format * @returns Formatted string */ export declare function formatStats(stats: TimingStats): string; /** * Formats throughput result as a human-readable string. * * @param result - Throughput result to format * @returns Formatted string */ export declare function formatThroughput(result: ThroughputResult): string; /** * Runs a performance test with automatic baseline comparison. * * @param name - Test name (used as baseline key) * @param fn - Function to measure * @param options - Measurement options * @returns Object with stats and comparison result */ export declare function runPerfTest(name: string, fn: () => void | Promise, options?: MeasureTimingOptions): Promise<{ stats: TimingStats; comparison: BaselineComparisonResult; }>; /** * Asserts performance is within baseline, throwing if not. * Useful for non-vitest environments or simple assertions. * * @param name - Baseline name * @param stats - Measured statistics * @throws Error if performance exceeds baseline */ export declare function assertWithinBaseline(name: string, stats: TimingStats): void; /** * Configuration for regression detection. */ export interface RegressionDetectionConfig { /** Minimum number of samples required for valid comparison */ minSamples: number; /** Tolerance factor for accepting variance */ toleranceFactor: number; /** Whether to fail on high variance (flaky tests) */ failOnHighVariance: boolean; /** Maximum acceptable coefficient of variation */ maxCV: number; } /** * Default regression detection configuration. */ export declare const DEFAULT_REGRESSION_CONFIG: RegressionDetectionConfig; /** * Detects performance regression between two timing stats. * * @param baseline - Baseline (historical) timing stats * @param current - Current timing stats * @param config - Detection configuration * @returns Regression detection result */ export declare function detectRegression(baseline: TimingStats, current: TimingStats, config?: Partial): { regressed: boolean; improved: boolean; stable: boolean; details: { p50Change: number; p95Change: number; p99Change: number; varianceIssue: boolean; }; message: string; }; export {}; //# sourceMappingURL=perf-test-utils.d.ts.map