modestbench 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +45 -2
  3. package/dist/adapters/ava-adapter.cjs +421 -0
  4. package/dist/adapters/ava-adapter.cjs.map +1 -0
  5. package/dist/adapters/ava-adapter.d.cts +39 -0
  6. package/dist/adapters/ava-adapter.d.cts.map +1 -0
  7. package/dist/adapters/ava-adapter.d.ts +39 -0
  8. package/dist/adapters/ava-adapter.d.ts.map +1 -0
  9. package/dist/adapters/ava-adapter.js +384 -0
  10. package/dist/adapters/ava-adapter.js.map +1 -0
  11. package/dist/adapters/ava-hooks.cjs +66 -0
  12. package/dist/adapters/ava-hooks.cjs.map +1 -0
  13. package/dist/adapters/ava-hooks.d.cts +24 -0
  14. package/dist/adapters/ava-hooks.d.cts.map +1 -0
  15. package/dist/adapters/ava-hooks.d.ts +24 -0
  16. package/dist/adapters/ava-hooks.d.ts.map +1 -0
  17. package/dist/adapters/ava-hooks.js +61 -0
  18. package/dist/adapters/ava-hooks.js.map +1 -0
  19. package/dist/adapters/ava-register.cjs +16 -0
  20. package/dist/adapters/ava-register.cjs.map +1 -0
  21. package/dist/adapters/ava-register.d.cts +11 -0
  22. package/dist/adapters/ava-register.d.cts.map +1 -0
  23. package/dist/adapters/ava-register.d.ts +11 -0
  24. package/dist/adapters/ava-register.d.ts.map +1 -0
  25. package/dist/adapters/ava-register.js +14 -0
  26. package/dist/adapters/ava-register.js.map +1 -0
  27. package/dist/adapters/mocha-adapter.cjs +254 -0
  28. package/dist/adapters/mocha-adapter.cjs.map +1 -0
  29. package/dist/adapters/mocha-adapter.d.cts +26 -0
  30. package/dist/adapters/mocha-adapter.d.cts.map +1 -0
  31. package/dist/adapters/mocha-adapter.d.ts +26 -0
  32. package/dist/adapters/mocha-adapter.d.ts.map +1 -0
  33. package/dist/adapters/mocha-adapter.js +217 -0
  34. package/dist/adapters/mocha-adapter.js.map +1 -0
  35. package/dist/adapters/node-test-adapter.cjs +335 -0
  36. package/dist/adapters/node-test-adapter.cjs.map +1 -0
  37. package/dist/adapters/node-test-adapter.d.cts +41 -0
  38. package/dist/adapters/node-test-adapter.d.cts.map +1 -0
  39. package/dist/adapters/node-test-adapter.d.ts +41 -0
  40. package/dist/adapters/node-test-adapter.d.ts.map +1 -0
  41. package/dist/adapters/node-test-adapter.js +298 -0
  42. package/dist/adapters/node-test-adapter.js.map +1 -0
  43. package/dist/adapters/node-test-hooks.cjs +72 -0
  44. package/dist/adapters/node-test-hooks.cjs.map +1 -0
  45. package/dist/adapters/node-test-hooks.d.cts +24 -0
  46. package/dist/adapters/node-test-hooks.d.cts.map +1 -0
  47. package/dist/adapters/node-test-hooks.d.ts +24 -0
  48. package/dist/adapters/node-test-hooks.d.ts.map +1 -0
  49. package/dist/adapters/node-test-hooks.js +67 -0
  50. package/dist/adapters/node-test-hooks.js.map +1 -0
  51. package/dist/adapters/node-test-register.cjs +7 -0
  52. package/dist/adapters/node-test-register.cjs.map +1 -0
  53. package/dist/adapters/node-test-register.d.cts +2 -0
  54. package/dist/adapters/node-test-register.d.cts.map +1 -0
  55. package/dist/adapters/node-test-register.d.ts +2 -0
  56. package/dist/adapters/node-test-register.d.ts.map +1 -0
  57. package/dist/adapters/node-test-register.js +5 -0
  58. package/dist/adapters/node-test-register.js.map +1 -0
  59. package/dist/adapters/types.cjs +152 -0
  60. package/dist/adapters/types.cjs.map +1 -0
  61. package/dist/adapters/types.d.cts +112 -0
  62. package/dist/adapters/types.d.cts.map +1 -0
  63. package/dist/adapters/types.d.ts +112 -0
  64. package/dist/adapters/types.d.ts.map +1 -0
  65. package/dist/adapters/types.js +148 -0
  66. package/dist/adapters/types.js.map +1 -0
  67. package/dist/cli/commands/init.cjs +21 -17
  68. package/dist/cli/commands/init.cjs.map +1 -1
  69. package/dist/cli/commands/init.d.cts.map +1 -1
  70. package/dist/cli/commands/init.d.ts.map +1 -1
  71. package/dist/cli/commands/init.js +21 -17
  72. package/dist/cli/commands/init.js.map +1 -1
  73. package/dist/cli/commands/run.cjs +6 -2
  74. package/dist/cli/commands/run.cjs.map +1 -1
  75. package/dist/cli/commands/run.js +6 -2
  76. package/dist/cli/commands/run.js.map +1 -1
  77. package/dist/cli/commands/test.cjs +392 -0
  78. package/dist/cli/commands/test.cjs.map +1 -0
  79. package/dist/cli/commands/test.d.cts +38 -0
  80. package/dist/cli/commands/test.d.cts.map +1 -0
  81. package/dist/cli/commands/test.d.ts +38 -0
  82. package/dist/cli/commands/test.d.ts.map +1 -0
  83. package/dist/cli/commands/test.js +388 -0
  84. package/dist/cli/commands/test.js.map +1 -0
  85. package/dist/cli/index.cjs +72 -1
  86. package/dist/cli/index.cjs.map +1 -1
  87. package/dist/cli/index.d.cts.map +1 -1
  88. package/dist/cli/index.d.ts.map +1 -1
  89. package/dist/cli/index.js +73 -2
  90. package/dist/cli/index.js.map +1 -1
  91. package/dist/constants.cjs +13 -1
  92. package/dist/constants.cjs.map +1 -1
  93. package/dist/constants.d.cts +12 -0
  94. package/dist/constants.d.cts.map +1 -1
  95. package/dist/constants.d.ts +12 -0
  96. package/dist/constants.d.ts.map +1 -1
  97. package/dist/constants.js +12 -0
  98. package/dist/constants.js.map +1 -1
  99. package/dist/core/engine.cjs +4 -0
  100. package/dist/core/engine.cjs.map +1 -1
  101. package/dist/core/engine.d.cts.map +1 -1
  102. package/dist/core/engine.d.ts.map +1 -1
  103. package/dist/core/engine.js +4 -0
  104. package/dist/core/engine.js.map +1 -1
  105. package/dist/core/engines/tinybench-engine.cjs +163 -131
  106. package/dist/core/engines/tinybench-engine.cjs.map +1 -1
  107. package/dist/core/engines/tinybench-engine.d.cts +6 -0
  108. package/dist/core/engines/tinybench-engine.d.cts.map +1 -1
  109. package/dist/core/engines/tinybench-engine.d.ts +6 -0
  110. package/dist/core/engines/tinybench-engine.d.ts.map +1 -1
  111. package/dist/core/engines/tinybench-engine.js +163 -131
  112. package/dist/core/engines/tinybench-engine.js.map +1 -1
  113. package/dist/errors/base.cjs +2 -1
  114. package/dist/errors/base.cjs.map +1 -1
  115. package/dist/errors/base.d.cts.map +1 -1
  116. package/dist/errors/base.d.ts.map +1 -1
  117. package/dist/errors/base.js +2 -1
  118. package/dist/errors/base.js.map +1 -1
  119. package/dist/reporters/human.cjs +83 -27
  120. package/dist/reporters/human.cjs.map +1 -1
  121. package/dist/reporters/human.d.cts +1 -0
  122. package/dist/reporters/human.d.cts.map +1 -1
  123. package/dist/reporters/human.d.ts +1 -0
  124. package/dist/reporters/human.d.ts.map +1 -1
  125. package/dist/reporters/human.js +83 -27
  126. package/dist/reporters/human.js.map +1 -1
  127. package/dist/reporters/simple.cjs +68 -21
  128. package/dist/reporters/simple.cjs.map +1 -1
  129. package/dist/reporters/simple.d.cts +1 -0
  130. package/dist/reporters/simple.d.cts.map +1 -1
  131. package/dist/reporters/simple.d.ts +1 -0
  132. package/dist/reporters/simple.d.ts.map +1 -1
  133. package/dist/reporters/simple.js +68 -21
  134. package/dist/reporters/simple.js.map +1 -1
  135. package/dist/services/config-manager.cjs +1 -1
  136. package/dist/services/config-manager.cjs.map +1 -1
  137. package/dist/services/config-manager.js +2 -2
  138. package/dist/services/config-manager.js.map +1 -1
  139. package/package.json +60 -31
  140. package/src/adapters/ava-adapter.ts +553 -0
  141. package/src/adapters/ava-hooks.ts +65 -0
  142. package/src/adapters/ava-register.ts +15 -0
  143. package/src/adapters/mocha-adapter.ts +284 -0
  144. package/src/adapters/node-test-adapter.ts +391 -0
  145. package/src/adapters/node-test-hooks.ts +71 -0
  146. package/src/adapters/node-test-register.ts +5 -0
  147. package/src/adapters/types.ts +281 -0
  148. package/src/cli/commands/init.ts +25 -17
  149. package/src/cli/commands/run.ts +9 -2
  150. package/src/cli/commands/test.ts +546 -0
  151. package/src/cli/index.ts +81 -1
  152. package/src/constants.ts +15 -0
  153. package/src/core/engine.ts +5 -0
  154. package/src/core/engines/tinybench-engine.ts +213 -141
  155. package/src/errors/base.ts +3 -2
  156. package/src/reporters/human.ts +107 -36
  157. package/src/reporters/simple.ts +81 -22
  158. package/src/services/config-manager.ts +2 -2
@@ -5,7 +5,7 @@
5
5
  * benchmark execution and measurement.
6
6
  */
7
7
 
8
- import { Bench } from 'tinybench';
8
+ import { Bench, type TaskResult as TinybenchTaskResult } from 'tinybench';
9
9
 
10
10
  import type {
11
11
  BenchmarkTask,
@@ -83,6 +83,7 @@ export class TinybenchEngine extends ModestBenchEngine {
83
83
 
84
84
  const bench = new Bench({
85
85
  iterations: effectiveIterations,
86
+ retainSamples: true, // Required in tinybench v6+ to access samples for custom stats
86
87
  time: effectiveTime,
87
88
  warmupIterations: config.warmup,
88
89
  warmupTime: config.warmup > 0 ? Math.min(config.warmup || 0, 500) : 0,
@@ -129,6 +130,7 @@ export class TinybenchEngine extends ModestBenchEngine {
129
130
 
130
131
  const minimalBench = new Bench({
131
132
  iterations: config.iterations,
133
+ retainSamples: true,
132
134
  time: retryTime,
133
135
  warmupIterations: config.warmup,
134
136
  warmupTime: 0,
@@ -136,8 +138,6 @@ export class TinybenchEngine extends ModestBenchEngine {
136
138
  minimalBench.add(
137
139
  taskName,
138
140
  taskData.fn,
139
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
140
- // @ts-ignore - Pending https://github.com/tinylibs/tinybench/pull/364
141
141
  signal ? { signal } : undefined,
142
142
  );
143
143
  try {
@@ -149,34 +149,29 @@ export class TinybenchEngine extends ModestBenchEngine {
149
149
  );
150
150
  }
151
151
  const minimalResults = minimalBench.results[0];
152
- if (!minimalResults || minimalResults.error) {
152
+ // Handle discriminated union: check state for error/completion
153
+ if (!minimalResults || minimalResults.state === 'errored') {
154
+ const errorMsg =
155
+ minimalResults?.state === 'errored'
156
+ ? minimalResults.error.message
157
+ : 'unknown error';
153
158
  throw new OperationTooFastError(
154
- `Benchmark too fast to measure reliably: ${minimalResults?.error?.message || 'unknown error'}`,
159
+ `Benchmark too fast to measure reliably: ${errorMsg}`,
155
160
  );
156
161
  }
157
- // Continue with minimal results - apply outlier removal
158
- const minimalRawSamples = minimalResults.latency.samples || [];
159
- const minimalSamplesInNs = minimalRawSamples.map((s) => s * 1e6);
160
- const minimalCleanedSamples = removeOutliersIQR(minimalSamplesInNs);
161
- const minimalStats = calculateStatistics(minimalCleanedSamples);
162
-
163
- const taskResult: TaskResult = {
164
- cv: minimalStats.cv,
165
- iterations: minimalCleanedSamples.length,
166
- marginOfError: minimalStats.marginOfError,
167
- max: minimalStats.max,
168
- mean: minimalStats.mean,
169
- metadata: taskData.metadata ?? {},
170
- min: minimalStats.min,
171
- name: taskName,
172
- opsPerSecond: minimalResults.throughput.mean || 0,
173
- p95: minimalStats.p95,
174
- p99: minimalStats.p99,
175
- stdDev: minimalStats.stdDev,
176
- ...(taskData.tags ? { tags: taskData.tags } : {}),
177
- variance: minimalStats.variance,
178
- };
179
- return taskResult;
162
+ // Extract stats from completed result
163
+ const taskResultFromMinimal =
164
+ this.extractTaskResultFromTinybenchResult(
165
+ taskName,
166
+ taskData,
167
+ minimalResults,
168
+ );
169
+ if (taskResultFromMinimal) {
170
+ return taskResultFromMinimal;
171
+ }
172
+ throw new OperationTooFastError(
173
+ `Benchmark too fast to measure reliably: no statistics available`,
174
+ );
180
175
  }
181
176
  throw error;
182
177
  } finally {
@@ -190,132 +185,146 @@ export class TinybenchEngine extends ModestBenchEngine {
190
185
  throw new BenchmarkExecutionError('No benchmark results returned');
191
186
  }
192
187
 
193
- // Check if the task was aborted
194
- if (results.aborted) {
195
- // Task was aborted via signal - return minimal valid result marked as aborted
196
- // (abort message is shown at run level, not per-task)
197
- const taskResult: TaskResult = {
198
- aborted: true,
199
- cv: 0,
200
- iterations: results.latency?.samples?.length || 0,
201
- marginOfError: 0,
202
- max: 0,
203
- mean: 0,
204
- metadata: taskData.metadata ?? {},
205
- min: 0,
206
- name: taskName,
207
- opsPerSecond: 0,
208
- p95: 0,
209
- p99: 0,
210
- stdDev: 0,
211
- ...(taskData.tags ? { tags: taskData.tags } : {}),
212
- variance: 0,
213
- };
214
- return taskResult;
215
- }
216
-
217
- // Check if tinybench detected an error during execution
218
- if (results.error) {
219
- const errorMessage =
220
- results.error instanceof Error
221
- ? results.error.message
222
- : String(results.error);
188
+ // Handle discriminated union based on state
189
+ switch (results.state) {
190
+ case 'aborted':
191
+ // Task was aborted via signal - return minimal valid result marked as aborted
192
+ return {
193
+ aborted: true,
194
+ cv: 0,
195
+ iterations: 0,
196
+ marginOfError: 0,
197
+ max: 0,
198
+ mean: 0,
199
+ metadata: taskData.metadata ?? {},
200
+ min: 0,
201
+ name: taskName,
202
+ opsPerSecond: 0,
203
+ p95: 0,
204
+ p99: 0,
205
+ stdDev: 0,
206
+ ...(taskData.tags ? { tags: taskData.tags } : {}),
207
+ variance: 0,
208
+ };
223
209
 
224
- // Handle array length errors for extremely fast operations
225
- if (errorMessage.includes('Invalid array length')) {
226
- // Retry with minimal time for extremely fast operations
227
- // Use same limiting logic but with minimal time for fast ops
228
- let retryTime: number;
229
- switch (config.limitBy) {
230
- case 'all':
231
- case 'any':
232
- retryTime = 10;
233
- break;
234
- case 'iterations':
235
- retryTime = 1;
236
- break;
237
- case 'time':
238
- retryTime = 10;
239
- break;
240
- default:
241
- retryTime = 1;
210
+ case 'aborted-with-statistics': {
211
+ // Aborted but has partial stats - use them
212
+ const taskResultFromAborted =
213
+ this.extractTaskResultFromTinybenchResult(
214
+ taskName,
215
+ taskData,
216
+ results,
217
+ );
218
+ if (taskResultFromAborted) {
219
+ return { ...taskResultFromAborted, aborted: true };
242
220
  }
221
+ // Fall through to return minimal aborted result
222
+ return {
223
+ aborted: true,
224
+ cv: 0,
225
+ iterations: 0,
226
+ marginOfError: 0,
227
+ max: 0,
228
+ mean: 0,
229
+ metadata: taskData.metadata ?? {},
230
+ min: 0,
231
+ name: taskName,
232
+ opsPerSecond: 0,
233
+ p95: 0,
234
+ p99: 0,
235
+ stdDev: 0,
236
+ ...(taskData.tags ? { tags: taskData.tags } : {}),
237
+ variance: 0,
238
+ };
239
+ }
243
240
 
244
- const minimalBench = new Bench({
245
- iterations: config.iterations,
246
- time: retryTime,
247
- warmupIterations: config.warmup,
248
- warmupTime: 0,
249
- });
250
- minimalBench.add(
251
- taskName,
252
- taskData.fn,
253
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
254
- // @ts-ignore - Pending https://github.com/tinylibs/tinybench/pull/364
255
- signal ? { signal } : undefined,
256
- );
257
- await minimalBench.run();
258
- const minimalResults = minimalBench.results[0];
241
+ case 'errored': {
242
+ const errorMessage = results.error.message;
243
+
244
+ // Handle array length errors for extremely fast operations
245
+ if (errorMessage.includes('Invalid array length')) {
246
+ // Retry with minimal time for extremely fast operations
247
+ let retryTime: number;
248
+ switch (config.limitBy) {
249
+ case 'all':
250
+ case 'any':
251
+ retryTime = 10;
252
+ break;
253
+ case 'iterations':
254
+ retryTime = 1;
255
+ break;
256
+ case 'time':
257
+ retryTime = 10;
258
+ break;
259
+ default:
260
+ retryTime = 1;
261
+ }
259
262
 
260
- if (!minimalResults || minimalResults.error) {
261
- // If retry also fails, just accept it failed
263
+ const minimalBench = new Bench({
264
+ iterations: config.iterations,
265
+ retainSamples: true,
266
+ time: retryTime,
267
+ warmupIterations: config.warmup,
268
+ warmupTime: 0,
269
+ });
270
+ minimalBench.add(
271
+ taskName,
272
+ taskData.fn,
273
+ signal ? { signal } : undefined,
274
+ );
275
+ await minimalBench.run();
276
+ const minimalResults = minimalBench.results[0];
277
+
278
+ if (!minimalResults || minimalResults.state === 'errored') {
279
+ throw new OperationTooFastError(
280
+ `Benchmark operation is too fast to measure reliably`,
281
+ );
282
+ }
283
+
284
+ const retryTaskResult = this.extractTaskResultFromTinybenchResult(
285
+ taskName,
286
+ taskData,
287
+ minimalResults,
288
+ );
289
+ if (retryTaskResult) {
290
+ return retryTaskResult;
291
+ }
262
292
  throw new OperationTooFastError(
263
293
  `Benchmark operation is too fast to measure reliably`,
264
294
  );
265
295
  }
266
296
 
267
- // Return minimal results - apply outlier removal
268
- const minimalRawSamples2 = minimalResults.latency.samples || [];
269
- const minimalSamplesInNs2 = minimalRawSamples2.map((s) => s * 1e6);
270
- const minimalCleanedSamples2 = removeOutliersIQR(minimalSamplesInNs2);
271
- const minimalStats2 = calculateStatistics(minimalCleanedSamples2);
272
-
273
- const taskResult: TaskResult = {
274
- cv: minimalStats2.cv,
275
- iterations: minimalCleanedSamples2.length,
276
- marginOfError: minimalStats2.marginOfError,
277
- max: minimalStats2.max,
278
- mean: minimalStats2.mean,
279
- metadata: taskData.metadata ?? {},
280
- min: minimalStats2.min,
281
- name: taskName,
282
- opsPerSecond: minimalResults.throughput.mean || 0,
283
- p95: minimalStats2.p95,
284
- p99: minimalStats2.p99,
285
- stdDev: minimalStats2.stdDev,
286
- ...(taskData.tags ? { tags: taskData.tags } : {}),
287
- variance: minimalStats2.variance,
288
- };
289
- return taskResult;
297
+ throw results.error;
290
298
  }
291
299
 
292
- throw results.error;
293
- }
300
+ case 'completed': {
301
+ // Normal completion - extract full stats
302
+ const taskResultFromCompleted =
303
+ this.extractTaskResultFromTinybenchResult(
304
+ taskName,
305
+ taskData,
306
+ results,
307
+ );
308
+ if (taskResultFromCompleted) {
309
+ return taskResultFromCompleted;
310
+ }
311
+ throw new BenchmarkExecutionError(
312
+ 'Completed benchmark but no statistics available',
313
+ );
314
+ }
294
315
 
295
- // Apply IQR outlier removal to raw samples
296
- const rawSamples = results.latency.samples || [];
297
- const samplesInNs = rawSamples.map((s) => s * 1e6); // Convert ms to ns
298
- const cleanedSamples = removeOutliersIQR(samplesInNs);
299
- const stats = calculateStatistics(cleanedSamples);
300
-
301
- const taskResult: TaskResult = {
302
- cv: stats.cv,
303
- iterations: cleanedSamples.length,
304
- marginOfError: stats.marginOfError,
305
- max: stats.max,
306
- mean: stats.mean,
307
- metadata: taskData.metadata ?? {},
308
- min: stats.min,
309
- name: taskName,
310
- opsPerSecond: results.throughput.mean || 0, // Keep tinybench's ops/sec
311
- p95: stats.p95,
312
- p99: stats.p99,
313
- stdDev: stats.stdDev,
314
- ...(taskData.tags ? { tags: taskData.tags } : {}),
315
- variance: stats.variance,
316
- };
316
+ case 'not-started':
317
+ case 'started':
318
+ throw new BenchmarkExecutionError(
319
+ `Unexpected benchmark state: ${results.state}`,
320
+ );
317
321
 
318
- return taskResult;
322
+ default:
323
+ // Exhaustiveness check
324
+ throw new BenchmarkExecutionError(
325
+ `Unknown benchmark state: ${(results as TinybenchTaskResult).state}`,
326
+ );
327
+ }
319
328
  } catch (error) {
320
329
  const executionError =
321
330
  error instanceof Error ? error : new Error(String(error));
@@ -340,4 +349,67 @@ export class TinybenchEngine extends ModestBenchEngine {
340
349
  return errorResult;
341
350
  }
342
351
  }
352
+
353
+ /**
354
+ * Extract TaskResult from a tinybench result that has statistics
355
+ *
356
+ * Handles the discriminated union types from tinybench v6+
357
+ */
358
+ private extractTaskResultFromTinybenchResult(
359
+ taskName: string,
360
+ taskData: BenchmarkTask,
361
+ result: TinybenchTaskResult,
362
+ ): null | TaskResult {
363
+ // Only states with statistics: 'completed' and 'aborted-with-statistics'
364
+ if (
365
+ result.state !== 'completed' &&
366
+ result.state !== 'aborted-with-statistics'
367
+ ) {
368
+ return null;
369
+ }
370
+
371
+ // Apply IQR outlier removal to raw samples
372
+ // Note: samples may be undefined if retainSamples wasn't enabled
373
+ const rawSamples = result.latency.samples ?? [];
374
+ if (rawSamples.length === 0) {
375
+ // Fall back to using tinybench's calculated stats directly
376
+ return {
377
+ cv: result.latency.rme, // Use relative margin of error as CV approximation
378
+ iterations: result.latency.samplesCount,
379
+ marginOfError: result.latency.moe,
380
+ max: result.latency.max,
381
+ mean: result.latency.mean,
382
+ metadata: taskData.metadata ?? {},
383
+ min: result.latency.min,
384
+ name: taskName,
385
+ opsPerSecond: result.throughput.mean || 0,
386
+ p95: result.latency.p99, // tinybench v6 doesn't have p95, use p99
387
+ p99: result.latency.p99,
388
+ stdDev: result.latency.sd,
389
+ ...(taskData.tags ? { tags: taskData.tags } : {}),
390
+ variance: result.latency.variance,
391
+ };
392
+ }
393
+
394
+ const samplesInNs = rawSamples.map((s: number) => s * 1e6); // Convert ms to ns
395
+ const cleanedSamples = removeOutliersIQR(samplesInNs);
396
+ const stats = calculateStatistics(cleanedSamples);
397
+
398
+ return {
399
+ cv: stats.cv,
400
+ iterations: cleanedSamples.length,
401
+ marginOfError: stats.marginOfError,
402
+ max: stats.max,
403
+ mean: stats.mean,
404
+ metadata: taskData.metadata ?? {},
405
+ min: stats.min,
406
+ name: taskName,
407
+ opsPerSecond: result.throughput.mean || 0,
408
+ p95: stats.p95,
409
+ p99: stats.p99,
410
+ stdDev: stats.stdDev,
411
+ ...(taskData.tags ? { tags: taskData.tags } : {}),
412
+ variance: stats.variance,
413
+ };
414
+ }
343
415
  }
@@ -5,11 +5,12 @@
5
5
  * documentation URLs, and consistent error display.
6
6
  */
7
7
 
8
+ import { SITE_URL } from '../constants.js';
9
+
8
10
  /**
9
11
  * Base URL for error documentation
10
12
  */
11
- const ERROR_DOC_BASE_URL =
12
- 'https://boneskull.github.io/modestbench/reference/errors';
13
+ const ERROR_DOC_BASE_URL = `${SITE_URL}/reference/errors`;
13
14
 
14
15
  /**
15
16
  * Abstract base class for ModestBench aggregate errors