* Measure average time for an async operation over multiple iterations. * @param {Object} options Measurement options. * @param {string} options.name Name of the operation being measured. * @param {Function} options.operation Async function to measure. * @param {number} options.iterations Number
({ name, operation, iterations, skipWarmup = false, dbLatency })
| 115 | * this benchmark. |
| 116 | */ |
| 117 | async function measureOperation({ name, operation, iterations, skipWarmup = false, dbLatency }) { |
| 118 | // Override iterations if global ITERATIONS is set |
| 119 | iterations = ITERATIONS || iterations; |
| 120 | |
| 121 | // Determine warmup count (20% of iterations) |
| 122 | const warmupCount = skipWarmup ? 0 : Math.floor(iterations * 0.2); |
| 123 | const times = []; |
| 124 | |
| 125 | // Apply artificial latency if specified |
| 126 | let unwrapLatency = null; |
| 127 | if (dbLatency !== undefined && dbLatency > 0) { |
| 128 | logInfo(`Applying ${dbLatency}ms artificial DB latency for this benchmark`); |
| 129 | unwrapLatency = wrapMongoDBWithLatency(dbLatency); |
| 130 | } |
| 131 | |
| 132 | try { |
| 133 | if (warmupCount > 0) { |
| 134 | logInfo(`Starting warmup phase of ${warmupCount} iterations...`); |
| 135 | const warmupStart = performance.now(); |
| 136 | for (let i = 0; i < warmupCount; i++) { |
| 137 | await operation(); |
| 138 | } |
| 139 | logInfo(`Warmup took: ${(performance.now() - warmupStart).toFixed(2)}ms`); |
| 140 | } |
| 141 | |
| 142 | // Measurement phase |
| 143 | logInfo(`Starting measurement phase of ${iterations} iterations...`); |
| 144 | const progressInterval = Math.ceil(iterations / 10); // Log every 10% |
| 145 | const measurementStart = performance.now(); |
| 146 | |
| 147 | for (let i = 0; i < iterations; i++) { |
| 148 | const start = performance.now(); |
| 149 | await operation(); |
| 150 | const end = performance.now(); |
| 151 | const duration = end - start; |
| 152 | times.push(duration); |
| 153 | |
| 154 | // Log progress every 10% or individual iterations if LOG_ITERATIONS is enabled |
| 155 | if (LOG_ITERATIONS) { |
| 156 | logInfo(`Iteration ${i + 1}: ${duration.toFixed(2)}ms`); |
| 157 | } else if ((i + 1) % progressInterval === 0 || i + 1 === iterations) { |
| 158 | const progress = Math.round(((i + 1) / iterations) * 100); |
| 159 | logInfo(`Progress: ${progress}%`); |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | logInfo(`Measurement took: ${(performance.now() - measurementStart).toFixed(2)}ms`); |
| 164 | |
| 165 | // Sort times for percentile calculations |
| 166 | times.sort((a, b) => a - b); |
| 167 | |
| 168 | // Filter outliers using Interquartile Range (IQR) method |
| 169 | const q1Index = Math.floor(times.length * 0.25); |
| 170 | const q3Index = Math.floor(times.length * 0.75); |
| 171 | const q1 = times[q1Index]; |
| 172 | const q3 = times[q3Index]; |
| 173 | const iqr = q3 - q1; |
| 174 | const lowerBound = q1 - 1.5 * iqr; |
no test coverage detected