modestbench 0.2.0 → 0.3.1

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 (357) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +131 -34
  3. package/dist/cli/commands/analyze.cjs +60 -0
  4. package/dist/cli/commands/analyze.cjs.map +1 -0
  5. package/dist/cli/commands/analyze.d.cts +35 -0
  6. package/dist/cli/commands/analyze.d.cts.map +1 -0
  7. package/dist/cli/commands/analyze.d.ts +35 -0
  8. package/dist/cli/commands/analyze.d.ts.map +1 -0
  9. package/dist/cli/commands/analyze.js +56 -0
  10. package/dist/cli/commands/analyze.js.map +1 -0
  11. package/dist/cli/commands/baseline.cjs +404 -0
  12. package/dist/cli/commands/baseline.cjs.map +1 -0
  13. package/dist/cli/commands/baseline.d.cts +72 -0
  14. package/dist/cli/commands/baseline.d.cts.map +1 -0
  15. package/dist/cli/commands/baseline.d.ts +72 -0
  16. package/dist/cli/commands/baseline.d.ts.map +1 -0
  17. package/dist/cli/commands/baseline.js +396 -0
  18. package/dist/cli/commands/baseline.js.map +1 -0
  19. package/dist/cli/commands/history.d.cts +1 -1
  20. package/dist/cli/commands/history.d.cts.map +1 -1
  21. package/dist/cli/commands/history.d.ts +1 -1
  22. package/dist/cli/commands/history.d.ts.map +1 -1
  23. package/dist/cli/commands/init.cjs +99 -166
  24. package/dist/cli/commands/init.cjs.map +1 -1
  25. package/dist/cli/commands/init.d.cts +4 -4
  26. package/dist/cli/commands/init.d.cts.map +1 -1
  27. package/dist/cli/commands/init.d.ts +4 -4
  28. package/dist/cli/commands/init.d.ts.map +1 -1
  29. package/dist/cli/commands/init.js +99 -166
  30. package/dist/cli/commands/init.js.map +1 -1
  31. package/dist/cli/commands/run.cjs +146 -127
  32. package/dist/cli/commands/run.cjs.map +1 -1
  33. package/dist/cli/commands/run.d.cts +16 -3
  34. package/dist/cli/commands/run.d.cts.map +1 -1
  35. package/dist/cli/commands/run.d.ts +16 -3
  36. package/dist/cli/commands/run.d.ts.map +1 -1
  37. package/dist/cli/commands/run.js +145 -93
  38. package/dist/cli/commands/run.js.map +1 -1
  39. package/dist/cli/index.cjs +583 -394
  40. package/dist/cli/index.cjs.map +1 -1
  41. package/dist/cli/index.d.cts +4 -16
  42. package/dist/cli/index.d.cts.map +1 -1
  43. package/dist/cli/index.d.ts +4 -16
  44. package/dist/cli/index.d.ts.map +1 -1
  45. package/dist/cli/index.js +575 -386
  46. package/dist/cli/index.js.map +1 -1
  47. package/dist/config/budget-schema.cjs +172 -0
  48. package/dist/config/budget-schema.cjs.map +1 -0
  49. package/dist/config/budget-schema.d.cts +59 -0
  50. package/dist/config/budget-schema.d.cts.map +1 -0
  51. package/dist/config/budget-schema.d.ts +59 -0
  52. package/dist/config/budget-schema.d.ts.map +1 -0
  53. package/dist/config/budget-schema.js +166 -0
  54. package/dist/config/budget-schema.js.map +1 -0
  55. package/dist/config/schema.cjs +182 -2
  56. package/dist/config/schema.cjs.map +1 -1
  57. package/dist/config/schema.d.cts +122 -3
  58. package/dist/config/schema.d.cts.map +1 -1
  59. package/dist/config/schema.d.ts +122 -3
  60. package/dist/config/schema.d.ts.map +1 -1
  61. package/dist/config/schema.js +180 -1
  62. package/dist/config/schema.js.map +1 -1
  63. package/dist/constants.cjs +45 -2
  64. package/dist/constants.cjs.map +1 -1
  65. package/dist/constants.d.cts +41 -0
  66. package/dist/constants.d.cts.map +1 -1
  67. package/dist/constants.d.ts +41 -0
  68. package/dist/constants.d.ts.map +1 -1
  69. package/dist/constants.js +44 -1
  70. package/dist/constants.js.map +1 -1
  71. package/dist/core/engine.cjs +114 -23
  72. package/dist/core/engine.cjs.map +1 -1
  73. package/dist/core/engine.d.cts +7 -7
  74. package/dist/core/engine.d.cts.map +1 -1
  75. package/dist/core/engine.d.ts +7 -7
  76. package/dist/core/engine.d.ts.map +1 -1
  77. package/dist/core/engine.js +115 -24
  78. package/dist/core/engine.js.map +1 -1
  79. package/dist/core/engines/accurate-engine.cjs +171 -36
  80. package/dist/core/engines/accurate-engine.cjs.map +1 -1
  81. package/dist/core/engines/accurate-engine.d.cts +5 -0
  82. package/dist/core/engines/accurate-engine.d.cts.map +1 -1
  83. package/dist/core/engines/accurate-engine.d.ts +5 -0
  84. package/dist/core/engines/accurate-engine.d.ts.map +1 -1
  85. package/dist/core/engines/accurate-engine.js +171 -36
  86. package/dist/core/engines/accurate-engine.js.map +1 -1
  87. package/dist/core/engines/tinybench-engine.cjs +3 -2
  88. package/dist/core/engines/tinybench-engine.cjs.map +1 -1
  89. package/dist/core/engines/tinybench-engine.d.cts.map +1 -1
  90. package/dist/core/engines/tinybench-engine.d.ts.map +1 -1
  91. package/dist/core/engines/tinybench-engine.js +3 -2
  92. package/dist/core/engines/tinybench-engine.js.map +1 -1
  93. package/dist/core/output-path-resolver.cjs +8 -1
  94. package/dist/core/output-path-resolver.cjs.map +1 -1
  95. package/dist/core/output-path-resolver.d.cts.map +1 -1
  96. package/dist/core/output-path-resolver.d.ts.map +1 -1
  97. package/dist/core/output-path-resolver.js +9 -2
  98. package/dist/core/output-path-resolver.js.map +1 -1
  99. package/dist/errors/base.cjs +12 -3
  100. package/dist/errors/base.cjs.map +1 -1
  101. package/dist/errors/base.d.cts +7 -0
  102. package/dist/errors/base.d.cts.map +1 -1
  103. package/dist/errors/base.d.ts +7 -0
  104. package/dist/errors/base.d.ts.map +1 -1
  105. package/dist/errors/base.js +10 -2
  106. package/dist/errors/base.js.map +1 -1
  107. package/dist/errors/budget.cjs +37 -0
  108. package/dist/errors/budget.cjs.map +1 -0
  109. package/dist/errors/budget.d.cts +31 -0
  110. package/dist/errors/budget.d.cts.map +1 -0
  111. package/dist/errors/budget.d.ts +31 -0
  112. package/dist/errors/budget.d.ts.map +1 -0
  113. package/dist/errors/budget.js +33 -0
  114. package/dist/errors/budget.js.map +1 -0
  115. package/dist/errors/index.cjs +4 -1
  116. package/dist/errors/index.cjs.map +1 -1
  117. package/dist/errors/index.d.cts +1 -0
  118. package/dist/errors/index.d.cts.map +1 -1
  119. package/dist/errors/index.d.ts +1 -0
  120. package/dist/errors/index.d.ts.map +1 -1
  121. package/dist/errors/index.js +2 -0
  122. package/dist/errors/index.js.map +1 -1
  123. package/dist/index.cjs +13 -1
  124. package/dist/index.cjs.map +1 -1
  125. package/dist/index.d.cts +5 -0
  126. package/dist/index.d.cts.map +1 -1
  127. package/dist/index.d.ts +5 -0
  128. package/dist/index.d.ts.map +1 -1
  129. package/dist/index.js +7 -0
  130. package/dist/index.js.map +1 -1
  131. package/dist/reporters/csv.cjs +37 -17
  132. package/dist/reporters/csv.cjs.map +1 -1
  133. package/dist/reporters/csv.d.cts +3 -6
  134. package/dist/reporters/csv.d.cts.map +1 -1
  135. package/dist/reporters/csv.d.ts +3 -6
  136. package/dist/reporters/csv.d.ts.map +1 -1
  137. package/dist/reporters/csv.js +37 -17
  138. package/dist/reporters/csv.js.map +1 -1
  139. package/dist/reporters/human.cjs +290 -67
  140. package/dist/reporters/human.cjs.map +1 -1
  141. package/dist/reporters/human.d.cts +25 -13
  142. package/dist/reporters/human.d.cts.map +1 -1
  143. package/dist/reporters/human.d.ts +25 -13
  144. package/dist/reporters/human.d.ts.map +1 -1
  145. package/dist/reporters/human.js +290 -67
  146. package/dist/reporters/human.js.map +1 -1
  147. package/dist/reporters/json.cjs +23 -48
  148. package/dist/reporters/json.cjs.map +1 -1
  149. package/dist/reporters/json.d.cts +2 -28
  150. package/dist/reporters/json.d.cts.map +1 -1
  151. package/dist/reporters/json.d.ts +2 -28
  152. package/dist/reporters/json.d.ts.map +1 -1
  153. package/dist/reporters/json.js +25 -50
  154. package/dist/reporters/json.js.map +1 -1
  155. package/dist/reporters/profile-human.cjs +154 -0
  156. package/dist/reporters/profile-human.cjs.map +1 -0
  157. package/dist/reporters/profile-human.d.cts +44 -0
  158. package/dist/reporters/profile-human.d.cts.map +1 -0
  159. package/dist/reporters/profile-human.d.ts +44 -0
  160. package/dist/reporters/profile-human.d.ts.map +1 -0
  161. package/dist/reporters/profile-human.js +147 -0
  162. package/dist/reporters/profile-human.js.map +1 -0
  163. package/dist/reporters/simple.cjs +67 -45
  164. package/dist/reporters/simple.cjs.map +1 -1
  165. package/dist/reporters/simple.d.cts +14 -14
  166. package/dist/reporters/simple.d.cts.map +1 -1
  167. package/dist/reporters/simple.d.ts +14 -14
  168. package/dist/reporters/simple.d.ts.map +1 -1
  169. package/dist/reporters/simple.js +67 -45
  170. package/dist/reporters/simple.js.map +1 -1
  171. package/dist/schema/modestbench-config.schema.json +153 -0
  172. package/dist/services/baseline-storage.cjs +151 -0
  173. package/dist/services/baseline-storage.cjs.map +1 -0
  174. package/dist/services/baseline-storage.d.cts +55 -0
  175. package/dist/services/baseline-storage.d.cts.map +1 -0
  176. package/dist/services/baseline-storage.d.ts +55 -0
  177. package/dist/services/baseline-storage.d.ts.map +1 -0
  178. package/dist/services/baseline-storage.js +147 -0
  179. package/dist/services/baseline-storage.js.map +1 -0
  180. package/dist/services/budget-evaluator.cjs +146 -0
  181. package/dist/services/budget-evaluator.cjs.map +1 -0
  182. package/dist/services/budget-evaluator.d.cts +29 -0
  183. package/dist/services/budget-evaluator.d.cts.map +1 -0
  184. package/dist/services/budget-evaluator.d.ts +29 -0
  185. package/dist/services/budget-evaluator.d.ts.map +1 -0
  186. package/dist/services/budget-evaluator.js +142 -0
  187. package/dist/services/budget-evaluator.js.map +1 -0
  188. package/dist/services/config-manager.cjs +24 -10
  189. package/dist/services/config-manager.cjs.map +1 -1
  190. package/dist/services/config-manager.d.cts +6 -1
  191. package/dist/services/config-manager.d.cts.map +1 -1
  192. package/dist/services/config-manager.d.ts +6 -1
  193. package/dist/services/config-manager.d.ts.map +1 -1
  194. package/dist/services/config-manager.js +24 -10
  195. package/dist/services/config-manager.js.map +1 -1
  196. package/dist/services/file-loader.cjs +3 -6
  197. package/dist/services/file-loader.cjs.map +1 -1
  198. package/dist/services/file-loader.d.cts.map +1 -1
  199. package/dist/services/file-loader.d.ts.map +1 -1
  200. package/dist/services/file-loader.js +3 -6
  201. package/dist/services/file-loader.js.map +1 -1
  202. package/dist/services/profiler/profile-filter.cjs +116 -0
  203. package/dist/services/profiler/profile-filter.cjs.map +1 -0
  204. package/dist/services/profiler/profile-filter.d.cts +20 -0
  205. package/dist/services/profiler/profile-filter.d.cts.map +1 -0
  206. package/dist/services/profiler/profile-filter.d.ts +20 -0
  207. package/dist/services/profiler/profile-filter.d.ts.map +1 -0
  208. package/dist/services/profiler/profile-filter.js +112 -0
  209. package/dist/services/profiler/profile-filter.js.map +1 -0
  210. package/dist/services/profiler/profile-parser.cjs +139 -0
  211. package/dist/services/profiler/profile-parser.cjs.map +1 -0
  212. package/dist/services/profiler/profile-parser.d.cts +18 -0
  213. package/dist/services/profiler/profile-parser.d.cts.map +1 -0
  214. package/dist/services/profiler/profile-parser.d.ts +18 -0
  215. package/dist/services/profiler/profile-parser.d.ts.map +1 -0
  216. package/dist/services/profiler/profile-parser.js +132 -0
  217. package/dist/services/profiler/profile-parser.js.map +1 -0
  218. package/dist/services/profiler/profile-runner.cjs +90 -0
  219. package/dist/services/profiler/profile-runner.cjs.map +1 -0
  220. package/dist/services/profiler/profile-runner.d.cts +29 -0
  221. package/dist/services/profiler/profile-runner.d.cts.map +1 -0
  222. package/dist/services/profiler/profile-runner.d.ts +29 -0
  223. package/dist/services/profiler/profile-runner.d.ts.map +1 -0
  224. package/dist/services/profiler/profile-runner.js +86 -0
  225. package/dist/services/profiler/profile-runner.js.map +1 -0
  226. package/dist/services/progress-manager.cjs +10 -2
  227. package/dist/services/progress-manager.cjs.map +1 -1
  228. package/dist/services/progress-manager.d.cts +2 -0
  229. package/dist/services/progress-manager.d.cts.map +1 -1
  230. package/dist/services/progress-manager.d.ts +2 -0
  231. package/dist/services/progress-manager.d.ts.map +1 -1
  232. package/dist/services/progress-manager.js +10 -2
  233. package/dist/services/progress-manager.js.map +1 -1
  234. package/dist/services/reporter-registry.cjs +18 -24
  235. package/dist/services/reporter-registry.cjs.map +1 -1
  236. package/dist/services/reporter-registry.d.cts +18 -40
  237. package/dist/services/reporter-registry.d.cts.map +1 -1
  238. package/dist/services/reporter-registry.d.ts +18 -40
  239. package/dist/services/reporter-registry.d.ts.map +1 -1
  240. package/dist/services/reporter-registry.js +18 -24
  241. package/dist/services/reporter-registry.js.map +1 -1
  242. package/dist/types/budgets.cjs +8 -0
  243. package/dist/types/budgets.cjs.map +1 -0
  244. package/dist/types/budgets.d.cts +149 -0
  245. package/dist/types/budgets.d.cts.map +1 -0
  246. package/dist/types/budgets.d.ts +149 -0
  247. package/dist/types/budgets.d.ts.map +1 -0
  248. package/dist/types/budgets.js +7 -0
  249. package/dist/types/budgets.js.map +1 -0
  250. package/dist/types/cli.cjs +2 -11
  251. package/dist/types/cli.cjs.map +1 -1
  252. package/dist/types/cli.d.cts +3 -227
  253. package/dist/types/cli.d.cts.map +1 -1
  254. package/dist/types/cli.d.ts +3 -227
  255. package/dist/types/cli.d.ts.map +1 -1
  256. package/dist/types/cli.js +2 -11
  257. package/dist/types/cli.js.map +1 -1
  258. package/dist/types/core.cjs +6 -1
  259. package/dist/types/core.cjs.map +1 -1
  260. package/dist/types/core.d.cts +15 -2
  261. package/dist/types/core.d.cts.map +1 -1
  262. package/dist/types/core.d.ts +15 -2
  263. package/dist/types/core.d.ts.map +1 -1
  264. package/dist/types/core.js +2 -1
  265. package/dist/types/core.js.map +1 -1
  266. package/dist/types/index.cjs +5 -0
  267. package/dist/types/index.cjs.map +1 -1
  268. package/dist/types/index.d.cts +2 -0
  269. package/dist/types/index.d.cts.map +1 -1
  270. package/dist/types/index.d.ts +2 -0
  271. package/dist/types/index.d.ts.map +1 -1
  272. package/dist/types/index.js +2 -0
  273. package/dist/types/index.js.map +1 -1
  274. package/dist/types/interfaces.d.cts +19 -8
  275. package/dist/types/interfaces.d.cts.map +1 -1
  276. package/dist/types/interfaces.d.ts +19 -8
  277. package/dist/types/interfaces.d.ts.map +1 -1
  278. package/dist/types/profiler.cjs +11 -0
  279. package/dist/types/profiler.cjs.map +1 -0
  280. package/dist/types/profiler.d.cts +102 -0
  281. package/dist/types/profiler.d.cts.map +1 -0
  282. package/dist/types/profiler.d.ts +102 -0
  283. package/dist/types/profiler.d.ts.map +1 -0
  284. package/dist/types/profiler.js +10 -0
  285. package/dist/types/profiler.js.map +1 -0
  286. package/dist/types/utility.cjs.map +1 -1
  287. package/dist/types/utility.d.cts +0 -8
  288. package/dist/types/utility.d.cts.map +1 -1
  289. package/dist/types/utility.d.ts +0 -8
  290. package/dist/types/utility.d.ts.map +1 -1
  291. package/dist/types/utility.js.map +1 -1
  292. package/dist/utils/identifiers.cjs +32 -0
  293. package/dist/utils/identifiers.cjs.map +1 -0
  294. package/dist/utils/identifiers.d.cts +32 -0
  295. package/dist/utils/identifiers.d.cts.map +1 -0
  296. package/dist/utils/identifiers.d.ts +32 -0
  297. package/dist/utils/identifiers.d.ts.map +1 -0
  298. package/dist/utils/identifiers.js +27 -0
  299. package/dist/utils/identifiers.js.map +1 -0
  300. package/dist/utils/package.cjs +40 -0
  301. package/dist/utils/package.cjs.map +1 -0
  302. package/dist/utils/package.d.cts +15 -0
  303. package/dist/utils/package.d.cts.map +1 -0
  304. package/dist/utils/package.d.ts +15 -0
  305. package/dist/utils/package.d.ts.map +1 -0
  306. package/dist/utils/package.js +33 -0
  307. package/dist/utils/package.js.map +1 -0
  308. package/dist/utils/type-guards.cjs +48 -0
  309. package/dist/utils/type-guards.cjs.map +1 -0
  310. package/dist/utils/type-guards.d.cts +22 -0
  311. package/dist/utils/type-guards.d.cts.map +1 -0
  312. package/dist/utils/type-guards.d.ts +22 -0
  313. package/dist/utils/type-guards.d.ts.map +1 -0
  314. package/dist/utils/type-guards.js +43 -0
  315. package/dist/utils/type-guards.js.map +1 -0
  316. package/package.json +18 -19
  317. package/src/cli/commands/analyze.ts +101 -0
  318. package/src/cli/commands/baseline.ts +577 -0
  319. package/src/cli/commands/history.ts +1 -1
  320. package/src/cli/commands/init.ts +116 -194
  321. package/src/cli/commands/run.ts +183 -113
  322. package/src/cli/index.ts +425 -183
  323. package/src/config/budget-schema.ts +189 -0
  324. package/src/config/schema.ts +260 -1
  325. package/src/constants.ts +53 -1
  326. package/src/core/engine.ts +169 -22
  327. package/src/core/engines/accurate-engine.ts +195 -44
  328. package/src/core/engines/tinybench-engine.ts +3 -2
  329. package/src/core/output-path-resolver.ts +10 -2
  330. package/src/errors/base.ts +11 -2
  331. package/src/errors/budget.ts +38 -0
  332. package/src/errors/index.ts +3 -0
  333. package/src/index.ts +9 -0
  334. package/src/reporters/csv.ts +54 -25
  335. package/src/reporters/human.ts +434 -115
  336. package/src/reporters/json.ts +26 -71
  337. package/src/reporters/profile-human.ts +210 -0
  338. package/src/reporters/simple.ts +88 -54
  339. package/src/services/baseline-storage.ts +199 -0
  340. package/src/services/budget-evaluator.ts +182 -0
  341. package/src/services/config-manager.ts +24 -9
  342. package/src/services/file-loader.ts +3 -6
  343. package/src/services/profiler/profile-filter.ts +147 -0
  344. package/src/services/profiler/profile-parser.ts +194 -0
  345. package/src/services/profiler/profile-runner.ts +121 -0
  346. package/src/services/progress-manager.ts +12 -2
  347. package/src/services/reporter-registry.ts +46 -81
  348. package/src/types/budgets.ts +180 -0
  349. package/src/types/cli.ts +5 -238
  350. package/src/types/core.ts +52 -10
  351. package/src/types/index.ts +5 -0
  352. package/src/types/interfaces.ts +24 -6
  353. package/src/types/profiler.ts +135 -0
  354. package/src/types/utility.ts +0 -10
  355. package/src/utils/identifiers.ts +58 -0
  356. package/src/utils/package.ts +35 -0
  357. package/src/utils/type-guards.ts +51 -0
@@ -40,6 +40,12 @@ export class AccurateEngine extends ModestBenchEngine {
40
40
  */
41
41
  private static readonly MAX_ITERATIONS_PER_ROUND = 10000;
42
42
 
43
+ /**
44
+ * Maximum iterations per round for async functions (much lower due to
45
+ * sequential await overhead)
46
+ */
47
+ private static readonly MAX_ITERATIONS_PER_ROUND_ASYNC = 100;
48
+
43
49
  private hasCheckedNativeSyntax = false;
44
50
 
45
51
  private nativeSyntaxErrorShown = false;
@@ -65,34 +71,75 @@ export class AccurateEngine extends ModestBenchEngine {
65
71
  );
66
72
  }
67
73
 
74
+ // Detect if function is async by testing it
75
+ const testResult = taskData.fn();
76
+ const isAsync =
77
+ testResult != null &&
78
+ typeof testResult === 'object' &&
79
+ 'then' in testResult &&
80
+ typeof testResult.then === 'function';
81
+ if (isAsync) {
82
+ // Clean up the test promise/thenable
83
+ const thenable = testResult as PromiseLike<unknown>;
84
+ thenable.then(
85
+ () => {
86
+ /* ignore */
87
+ },
88
+ () => {
89
+ /* ignore */
90
+ },
91
+ );
92
+ }
93
+
94
+ // For async functions, cap iterations to prevent excessive sequential rounds
95
+ // Even with limitBy: 'iterations', 5000 samples = 5000 rounds of async calls
96
+ // which is impractically slow. Cap at 1000 for reasonable balance.
97
+ const effectiveConfig =
98
+ isAsync && config.iterations > 1000
99
+ ? { ...config, iterations: 1000 }
100
+ : config;
101
+
68
102
  // Check for V8 native syntax support
69
103
  const useOptGuards = this.checkNativeSyntax();
70
104
 
71
105
  // Show helpful error if native syntax not available
72
106
  if (!useOptGuards && !this.nativeSyntaxErrorShown) {
73
107
  if (!config.quiet) {
108
+ // Get the path to the modestbench executable from process.argv
109
+ const modestbenchPath = process.argv[1] || 'modestbench';
110
+
74
111
  console.warn(
75
- '\n⚠️ AccurateEngine requires --allow-natives-syntax flag for best accuracy.',
112
+ '\n⚠️ AccurateEngine recommends --allow-natives-syntax flag for best accuracy.',
76
113
  '\nRunning in fallback mode (reduced accuracy).',
77
114
  '\n\nTo enable V8 optimization guards:',
78
- '\n node --allow-natives-syntax --test',
79
- '\n or add to package.json: "test": "node --allow-natives-syntax ..."',
115
+ `\n node --allow-natives-syntax ${modestbenchPath}`,
80
116
  '\n',
81
117
  );
82
118
  }
83
119
  this.nativeSyntaxErrorShown = true;
84
120
  }
85
121
 
86
- // Execute benchmark with or without opt guards
122
+ // Execute benchmark with or without opt guards, and with async support
87
123
  const rawSamples = useOptGuards
88
- ? await this.executeBenchmarkWithOptGuards(taskData.fn, config, signal)
89
- : await this.executeBenchmarkBasic(taskData.fn, config, signal);
124
+ ? await this.executeBenchmarkWithOptGuards(
125
+ taskData.fn,
126
+ effectiveConfig,
127
+ signal,
128
+ isAsync,
129
+ )
130
+ : await this.executeBenchmarkBasic(
131
+ taskData.fn,
132
+ effectiveConfig,
133
+ signal,
134
+ isAsync,
135
+ );
90
136
 
91
- // Check if aborted
137
+ // Check if aborted - return minimal valid result marked as aborted
138
+ // (abort message is shown at run level, not per-task)
92
139
  if (signal?.aborted) {
93
140
  return {
141
+ aborted: true,
94
142
  cv: 0,
95
- error: new Error('Benchmark aborted by user signal'),
96
143
  iterations: rawSamples.length,
97
144
  marginOfError: 0,
98
145
  max: 0,
@@ -166,18 +213,25 @@ export class AccurateEngine extends ModestBenchEngine {
166
213
  private async calculateInitialIterations(
167
214
  fn: (...args: unknown[]) => unknown,
168
215
  targetTime: number, // in seconds
216
+ isAsync = false,
169
217
  ): Promise<number> {
170
218
  // eslint-disable-next-line @typescript-eslint/unbound-method
171
219
  const timer = process.hrtime.bigint;
172
220
  const MIN_RESOLUTION = 0.5; // nanoseconds
173
221
  const SCALE = 1e9; // ns to seconds
174
222
 
175
- // Run a quick test with 30 iterations
176
- const testIterations = 30;
223
+ // Run a quick test with 30 iterations (fewer for async to keep it fast)
224
+ const testIterations = isAsync ? 10 : 30;
177
225
  const start = timer();
178
226
 
179
- for (let i = 0; i < testIterations; i++) {
180
- fn();
227
+ if (isAsync) {
228
+ for (let i = 0; i < testIterations; i++) {
229
+ await fn();
230
+ }
231
+ } else {
232
+ for (let i = 0; i < testIterations; i++) {
233
+ fn();
234
+ }
181
235
  }
182
236
 
183
237
  const duration = Number(timer() - start);
@@ -186,8 +240,13 @@ export class AccurateEngine extends ModestBenchEngine {
186
240
  // Calculate how many iterations we need for targetTime
187
241
  const totalOpsForTargetTime = targetTime / (durationPerOp / SCALE);
188
242
 
243
+ // Use appropriate max based on sync/async
244
+ const maxIterations = isAsync
245
+ ? AccurateEngine.MAX_ITERATIONS_PER_ROUND_ASYNC
246
+ : AccurateEngine.MAX_ITERATIONS_PER_ROUND;
247
+
189
248
  return Math.min(
190
- Number.MAX_SAFE_INTEGER,
249
+ maxIterations,
191
250
  Math.max(1, Math.round(totalOpsForTargetTime)),
192
251
  );
193
252
  }
@@ -224,6 +283,7 @@ export class AccurateEngine extends ModestBenchEngine {
224
283
  fn: (...args: unknown[]) => unknown,
225
284
  config: ModestBenchConfig,
226
285
  signal?: AbortSignal,
286
+ isAsync = false,
227
287
  ): Promise<number[]> {
228
288
  // eslint-disable-next-line @typescript-eslint/unbound-method
229
289
  const timer = process.hrtime.bigint;
@@ -234,26 +294,70 @@ export class AccurateEngine extends ModestBenchEngine {
234
294
  const initialIterations = await this.calculateInitialIterations(
235
295
  fn,
236
296
  targetTime,
297
+ isAsync,
237
298
  );
238
299
 
239
300
  if (config.warmup > 0) {
240
301
  const warmupTime = Math.min(config.warmup / 1000, 0.05);
241
- await this.runWarmup(fn, initialIterations, warmupTime);
302
+ await this.runWarmup(fn, initialIterations, warmupTime, isAsync);
242
303
  }
243
304
 
244
305
  const maxDuration = (config.time / 1000) * SCALE;
245
306
  let timeSpent = 0;
246
307
  let iterations = initialIterations;
247
308
 
248
- while (timeSpent < maxDuration || samples.length < config.iterations) {
309
+ // Use appropriate max based on sync/async
310
+ const maxIterations = isAsync
311
+ ? AccurateEngine.MAX_ITERATIONS_PER_ROUND_ASYNC
312
+ : AccurateEngine.MAX_ITERATIONS_PER_ROUND;
313
+
314
+ // Determine loop condition based on limitBy setting
315
+ const shouldContinue = (): boolean => {
249
316
  if (signal?.aborted) {
250
- break;
317
+ return false;
318
+ }
319
+
320
+ const timeRemaining = timeSpent < maxDuration;
321
+ const samplesRemaining = samples.length < config.iterations;
322
+
323
+ switch (config.limitBy) {
324
+ case 'all':
325
+ return timeRemaining && samplesRemaining;
326
+ case 'any':
327
+ return timeRemaining || samplesRemaining;
328
+ case 'iterations':
329
+ return samplesRemaining;
330
+ case 'time':
331
+ return timeRemaining;
332
+ default:
333
+ return timeRemaining && samplesRemaining;
251
334
  }
335
+ };
252
336
 
337
+ while (shouldContinue()) {
253
338
  const start = timer();
254
339
 
255
- for (let i = 0; i < iterations; i++) {
256
- fn();
340
+ if (isAsync) {
341
+ // For async functions, await each call individually and check for abort
342
+ for (let i = 0; i < iterations; i++) {
343
+ if (signal?.aborted) {
344
+ break;
345
+ }
346
+ await fn();
347
+ }
348
+ } else {
349
+ // For sync functions, check for abort every 50 iterations
350
+ for (let i = 0; i < iterations; i++) {
351
+ if (i % 50 === 0 && signal?.aborted) {
352
+ break;
353
+ }
354
+ fn();
355
+ }
356
+ }
357
+
358
+ // Early exit if aborted during inner loop
359
+ if (signal?.aborted) {
360
+ break;
257
361
  }
258
362
 
259
363
  const duration = Number(timer() - start);
@@ -268,10 +372,7 @@ export class AccurateEngine extends ModestBenchEngine {
268
372
 
269
373
  const remainingTime = Math.max(0, (maxDuration - timeSpent) / SCALE);
270
374
  iterations = Math.round(remainingTime / (durationPerOp / SCALE));
271
- iterations = Math.max(
272
- 1,
273
- Math.min(AccurateEngine.MAX_ITERATIONS_PER_ROUND, iterations),
274
- );
375
+ iterations = Math.max(1, Math.min(maxIterations, iterations));
275
376
  }
276
377
 
277
378
  return samples;
@@ -284,6 +385,7 @@ export class AccurateEngine extends ModestBenchEngine {
284
385
  fn: (...args: unknown[]) => unknown,
285
386
  config: ModestBenchConfig,
286
387
  signal?: AbortSignal,
388
+ isAsync = false,
287
389
  ): Promise<number[]> {
288
390
  // eslint-disable-next-line @typescript-eslint/unbound-method
289
391
  const timer = process.hrtime.bigint;
@@ -295,12 +397,13 @@ export class AccurateEngine extends ModestBenchEngine {
295
397
  const initialIterations = await this.calculateInitialIterations(
296
398
  fn,
297
399
  targetTime,
400
+ isAsync,
298
401
  );
299
402
 
300
403
  // Run warmup
301
404
  if (config.warmup > 0) {
302
405
  const warmupTime = Math.min(config.warmup / 1000, 0.05); // Max 50ms warmup
303
- await this.runWarmup(fn, initialIterations, warmupTime);
406
+ await this.runWarmup(fn, initialIterations, warmupTime, isAsync);
304
407
  }
305
408
 
306
409
  // Create DoNotOptimize wrapper using V8 intrinsics
@@ -321,17 +424,61 @@ export class AccurateEngine extends ModestBenchEngine {
321
424
  let timeSpent = 0;
322
425
  let iterations = initialIterations;
323
426
 
324
- // Main benchmark loop
325
- while (timeSpent < maxDuration || samples.length < config.iterations) {
427
+ // Use appropriate max based on sync/async
428
+ const maxIterations = isAsync
429
+ ? AccurateEngine.MAX_ITERATIONS_PER_ROUND_ASYNC
430
+ : AccurateEngine.MAX_ITERATIONS_PER_ROUND;
431
+
432
+ // Determine loop condition based on limitBy setting
433
+ const shouldContinue = (): boolean => {
326
434
  if (signal?.aborted) {
327
- break;
435
+ return false;
328
436
  }
329
437
 
438
+ const timeRemaining = timeSpent < maxDuration;
439
+ const samplesRemaining = samples.length < config.iterations;
440
+
441
+ switch (config.limitBy) {
442
+ case 'all':
443
+ return timeRemaining && samplesRemaining;
444
+ case 'any':
445
+ return timeRemaining || samplesRemaining;
446
+ case 'iterations':
447
+ return samplesRemaining;
448
+ case 'time':
449
+ return timeRemaining;
450
+ default:
451
+ return timeRemaining && samplesRemaining;
452
+ }
453
+ };
454
+
455
+ // Main benchmark loop
456
+ while (shouldContinue()) {
330
457
  const start = timer();
331
458
 
332
- for (let i = 0; i < iterations; i++) {
333
- const result = fn();
334
- guardedDoNotOptimize(result); // Prevent optimization
459
+ if (isAsync) {
460
+ // For async functions, await each call individually and check for abort
461
+ for (let i = 0; i < iterations; i++) {
462
+ if (signal?.aborted) {
463
+ break;
464
+ }
465
+ const result = await fn();
466
+ guardedDoNotOptimize(result); // Prevent optimization
467
+ }
468
+ } else {
469
+ // For sync functions, check for abort every 50 iterations
470
+ for (let i = 0; i < iterations; i++) {
471
+ if (i % 50 === 0 && signal?.aborted) {
472
+ break;
473
+ }
474
+ const result = fn();
475
+ guardedDoNotOptimize(result); // Prevent optimization
476
+ }
477
+ }
478
+
479
+ // Early exit if aborted during inner loop
480
+ if (signal?.aborted) {
481
+ break;
335
482
  }
336
483
 
337
484
  const duration = Number(timer() - start);
@@ -348,10 +495,7 @@ export class AccurateEngine extends ModestBenchEngine {
348
495
  // Adjust iterations for next round
349
496
  const remainingTime = Math.max(0, (maxDuration - timeSpent) / SCALE);
350
497
  iterations = Math.round(remainingTime / (durationPerOp / SCALE));
351
- iterations = Math.max(
352
- 1,
353
- Math.min(AccurateEngine.MAX_ITERATIONS_PER_ROUND, iterations),
354
- );
498
+ iterations = Math.max(1, Math.min(maxIterations, iterations));
355
499
  }
356
500
 
357
501
  return samples;
@@ -365,6 +509,7 @@ export class AccurateEngine extends ModestBenchEngine {
365
509
  fn: Function,
366
510
  initialIterations: number,
367
511
  warmupTime: number, // in seconds
512
+ isAsync = false,
368
513
  ): Promise<void> {
369
514
  // eslint-disable-next-line @typescript-eslint/unbound-method
370
515
  const timer = process.hrtime.bigint;
@@ -373,19 +518,28 @@ export class AccurateEngine extends ModestBenchEngine {
373
518
  const maxDuration = warmupTime * SCALE;
374
519
  const minSamples = 10;
375
520
 
521
+ // Use appropriate max based on sync/async
522
+ const maxIterations = isAsync
523
+ ? AccurateEngine.MAX_ITERATIONS_PER_ROUND_ASYNC
524
+ : AccurateEngine.MAX_ITERATIONS_PER_ROUND;
525
+
376
526
  let timeSpent = 0n;
377
527
  let samples = 0;
378
- let iterations = Math.min(
379
- initialIterations,
380
- AccurateEngine.MAX_ITERATIONS_PER_ROUND,
381
- );
528
+ let iterations = Math.min(initialIterations, maxIterations);
382
529
 
383
530
  while (Number(timeSpent) < maxDuration || samples <= minSamples) {
384
531
  const start = timer();
385
532
 
386
- for (let i = 0; i < iterations; i++) {
387
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
388
- fn();
533
+ if (isAsync) {
534
+ for (let i = 0; i < iterations; i++) {
535
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
536
+ await fn();
537
+ }
538
+ } else {
539
+ for (let i = 0; i < iterations; i++) {
540
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
541
+ fn();
542
+ }
389
543
  }
390
544
 
391
545
  const duration = timer() - start;
@@ -402,10 +556,7 @@ export class AccurateEngine extends ModestBenchEngine {
402
556
  (maxDuration - Number(timeSpent)) / SCALE,
403
557
  );
404
558
  iterations = Math.round(remainingTime / (durationPerOp / SCALE));
405
- iterations = Math.max(
406
- 1,
407
- Math.min(AccurateEngine.MAX_ITERATIONS_PER_ROUND, iterations),
408
- );
559
+ iterations = Math.max(1, Math.min(maxIterations, iterations));
409
560
  }
410
561
  }
411
562
  }
@@ -192,10 +192,11 @@ export class TinybenchEngine extends ModestBenchEngine {
192
192
 
193
193
  // Check if the task was aborted
194
194
  if (results.aborted) {
195
- // Task was aborted via signal - return minimal valid result with error
195
+ // Task was aborted via signal - return minimal valid result marked as aborted
196
+ // (abort message is shown at run level, not per-task)
196
197
  const taskResult: TaskResult = {
198
+ aborted: true,
197
199
  cv: 0,
198
- error: new Error('Benchmark aborted by user signal'),
199
200
  iterations: results.latency?.samples?.length || 0,
200
201
  marginOfError: 0,
201
202
  max: 0,
@@ -1,4 +1,4 @@
1
- import { isAbsolute, join, resolve } from 'node:path';
1
+ import { extname, isAbsolute, join, resolve } from 'node:path';
2
2
 
3
3
  /**
4
4
  * Resolves the final output path for a reporter
@@ -29,7 +29,15 @@ export const resolveOutputPath = (
29
29
  return resolve(process.cwd(), outputFile);
30
30
  }
31
31
 
32
- // Fall back to default behavior
32
+ // If outputDir looks like a file (has extension), treat it as a file path
33
+ // This handles cases like: --output results.csv
34
+ if (outputDir && extname(outputDir)) {
35
+ return isAbsolute(outputDir)
36
+ ? outputDir
37
+ : resolve(process.cwd(), outputDir);
38
+ }
39
+
40
+ // Fall back to default behavior (outputDir is a directory)
33
41
  if (outputDir && defaultFilename) {
34
42
  return join(outputDir, defaultFilename);
35
43
  }
@@ -143,10 +143,19 @@ export const isModestBenchError = (
143
143
  error: unknown,
144
144
  ): error is ModestBenchError => {
145
145
  return (
146
- typeof error === 'object' &&
147
- error !== null &&
146
+ isError(error) &&
148
147
  'code' in error &&
149
148
  typeof (error as { code: unknown }).code === 'string' &&
150
149
  (error as { code: string }).code.startsWith('ERR_MB_')
151
150
  );
152
151
  };
152
+
153
+ /**
154
+ * Type guard to check if an error is a standard Error
155
+ *
156
+ * @param error - The error to check
157
+ * @returns `true` if the error is an `Error`
158
+ */
159
+ export const isError = (error: unknown): error is Error => {
160
+ return error instanceof Error;
161
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Budget-related errors
3
+ *
4
+ * Errors that occur during budget evaluation and enforcement.
5
+ */
6
+
7
+ import type { BudgetSummary } from '../types/core.js';
8
+
9
+ import { ModestBenchError } from './base.js';
10
+
11
+ /**
12
+ * Error thrown when performance budgets are exceeded
13
+ *
14
+ * Thrown when budget evaluation fails and budgetMode is set to 'fail'. Contains
15
+ * the full budget summary for detailed reporting.
16
+ */
17
+ export class BudgetExceededError extends ModestBenchError {
18
+ /**
19
+ * Budget summary containing details of all violations
20
+ */
21
+ public readonly budgetSummary: BudgetSummary;
22
+
23
+ /**
24
+ * Error code for budget exceeded errors
25
+ */
26
+ readonly code = 'ERR_MB_BUDGET_EXCEEDED';
27
+
28
+ /**
29
+ * Create a new budget exceeded error
30
+ *
31
+ * @param message - Human-readable error message
32
+ * @param budgetSummary - Budget evaluation results
33
+ */
34
+ constructor(message: string, budgetSummary: BudgetSummary) {
35
+ super(message);
36
+ this.budgetSummary = budgetSummary;
37
+ }
38
+ }
@@ -13,6 +13,9 @@ export {
13
13
  ModestBenchError,
14
14
  } from './base.js';
15
15
 
16
+ // Budget errors
17
+ export { BudgetExceededError } from './budget.js';
18
+
16
19
  // CLI errors
17
20
  export {
18
21
  InvalidArgumentError,
package/src/index.ts CHANGED
@@ -26,11 +26,17 @@ export * from './errors/index.js';
26
26
  export { CsvReporter } from './reporters/csv.js';
27
27
  export { HumanReporter } from './reporters/human.js';
28
28
  export { JsonReporter } from './reporters/json.js';
29
+ export { ProfileHumanReporter } from './reporters/profile-human.js';
29
30
 
30
31
  // Services
31
32
  export { ModestBenchConfigurationManager } from './services/config-manager.js';
32
33
  export { BenchmarkFileLoader } from './services/file-loader.js';
33
34
  export { FileHistoryStorage } from './services/history-storage.js';
35
+ // Profiler services
36
+ export { filterProfile } from './services/profiler/profile-filter.js';
37
+ export { parseProfile } from './services/profiler/profile-parser.js';
38
+
39
+ export { runWithProfiling } from './services/profiler/profile-runner.js';
34
40
  export { ModestBenchProgressManager } from './services/progress-manager.js';
35
41
  export {
36
42
  BaseReporter,
@@ -40,3 +46,6 @@ export {
40
46
 
41
47
  // Export all types
42
48
  export * from './types/index.js';
49
+
50
+ // Utilities
51
+ export { findPackageRoot } from './utils/package.js';