modestbench 0.0.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 (275) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE.md +55 -0
  3. package/README.md +699 -0
  4. package/dist/bootstrap.cjs +37 -0
  5. package/dist/bootstrap.cjs.map +1 -0
  6. package/dist/bootstrap.d.cts +17 -0
  7. package/dist/bootstrap.d.cts.map +1 -0
  8. package/dist/bootstrap.d.ts +17 -0
  9. package/dist/bootstrap.d.ts.map +1 -0
  10. package/dist/bootstrap.js +33 -0
  11. package/dist/bootstrap.js.map +1 -0
  12. package/dist/cli/commands/history.cjs +459 -0
  13. package/dist/cli/commands/history.cjs.map +1 -0
  14. package/dist/cli/commands/history.d.cts +34 -0
  15. package/dist/cli/commands/history.d.cts.map +1 -0
  16. package/dist/cli/commands/history.d.ts +34 -0
  17. package/dist/cli/commands/history.d.ts.map +1 -0
  18. package/dist/cli/commands/history.js +422 -0
  19. package/dist/cli/commands/history.js.map +1 -0
  20. package/dist/cli/commands/init.cjs +566 -0
  21. package/dist/cli/commands/init.cjs.map +1 -0
  22. package/dist/cli/commands/init.d.cts +26 -0
  23. package/dist/cli/commands/init.d.cts.map +1 -0
  24. package/dist/cli/commands/init.d.ts +26 -0
  25. package/dist/cli/commands/init.d.ts.map +1 -0
  26. package/dist/cli/commands/init.js +562 -0
  27. package/dist/cli/commands/init.js.map +1 -0
  28. package/dist/cli/commands/run.cjs +285 -0
  29. package/dist/cli/commands/run.cjs.map +1 -0
  30. package/dist/cli/commands/run.d.cts +37 -0
  31. package/dist/cli/commands/run.d.cts.map +1 -0
  32. package/dist/cli/commands/run.d.ts +37 -0
  33. package/dist/cli/commands/run.d.ts.map +1 -0
  34. package/dist/cli/commands/run.js +248 -0
  35. package/dist/cli/commands/run.js.map +1 -0
  36. package/dist/cli/index.cjs +523 -0
  37. package/dist/cli/index.cjs.map +1 -0
  38. package/dist/cli/index.d.cts +58 -0
  39. package/dist/cli/index.d.cts.map +1 -0
  40. package/dist/cli/index.d.ts +58 -0
  41. package/dist/cli/index.d.ts.map +1 -0
  42. package/dist/cli/index.js +515 -0
  43. package/dist/cli/index.js.map +1 -0
  44. package/dist/config/manager.cjs +370 -0
  45. package/dist/config/manager.cjs.map +1 -0
  46. package/dist/config/manager.d.cts +46 -0
  47. package/dist/config/manager.d.cts.map +1 -0
  48. package/dist/config/manager.d.ts +46 -0
  49. package/dist/config/manager.d.ts.map +1 -0
  50. package/dist/config/manager.js +333 -0
  51. package/dist/config/manager.js.map +1 -0
  52. package/dist/config/schema.cjs +182 -0
  53. package/dist/config/schema.cjs.map +1 -0
  54. package/dist/config/schema.d.cts +51 -0
  55. package/dist/config/schema.d.cts.map +1 -0
  56. package/dist/config/schema.d.ts +51 -0
  57. package/dist/config/schema.d.ts.map +1 -0
  58. package/dist/config/schema.js +145 -0
  59. package/dist/config/schema.js.map +1 -0
  60. package/dist/constants.cjs +22 -0
  61. package/dist/constants.cjs.map +1 -0
  62. package/dist/constants.d.cts +10 -0
  63. package/dist/constants.d.cts.map +1 -0
  64. package/dist/constants.d.ts +10 -0
  65. package/dist/constants.d.ts.map +1 -0
  66. package/dist/constants.js +19 -0
  67. package/dist/constants.js.map +1 -0
  68. package/dist/core/benchmark-schema.cjs +135 -0
  69. package/dist/core/benchmark-schema.cjs.map +1 -0
  70. package/dist/core/benchmark-schema.d.cts +139 -0
  71. package/dist/core/benchmark-schema.d.cts.map +1 -0
  72. package/dist/core/benchmark-schema.d.ts +139 -0
  73. package/dist/core/benchmark-schema.d.ts.map +1 -0
  74. package/dist/core/benchmark-schema.js +132 -0
  75. package/dist/core/benchmark-schema.js.map +1 -0
  76. package/dist/core/engine.cjs +669 -0
  77. package/dist/core/engine.cjs.map +1 -0
  78. package/dist/core/engine.d.cts +128 -0
  79. package/dist/core/engine.d.cts.map +1 -0
  80. package/dist/core/engine.d.ts +128 -0
  81. package/dist/core/engine.d.ts.map +1 -0
  82. package/dist/core/engine.js +632 -0
  83. package/dist/core/engine.js.map +1 -0
  84. package/dist/core/engines/accurate-engine.cjs +292 -0
  85. package/dist/core/engines/accurate-engine.cjs.map +1 -0
  86. package/dist/core/engines/accurate-engine.d.cts +63 -0
  87. package/dist/core/engines/accurate-engine.d.cts.map +1 -0
  88. package/dist/core/engines/accurate-engine.d.ts +63 -0
  89. package/dist/core/engines/accurate-engine.d.ts.map +1 -0
  90. package/dist/core/engines/accurate-engine.js +288 -0
  91. package/dist/core/engines/accurate-engine.js.map +1 -0
  92. package/dist/core/engines/index.cjs +21 -0
  93. package/dist/core/engines/index.cjs.map +1 -0
  94. package/dist/core/engines/index.d.cts +16 -0
  95. package/dist/core/engines/index.d.cts.map +1 -0
  96. package/dist/core/engines/index.d.ts +16 -0
  97. package/dist/core/engines/index.d.ts.map +1 -0
  98. package/dist/core/engines/index.js +16 -0
  99. package/dist/core/engines/index.js.map +1 -0
  100. package/dist/core/engines/tinybench-engine.cjs +286 -0
  101. package/dist/core/engines/tinybench-engine.cjs.map +1 -0
  102. package/dist/core/engines/tinybench-engine.d.cts +18 -0
  103. package/dist/core/engines/tinybench-engine.d.cts.map +1 -0
  104. package/dist/core/engines/tinybench-engine.d.ts +18 -0
  105. package/dist/core/engines/tinybench-engine.d.ts.map +1 -0
  106. package/dist/core/engines/tinybench-engine.js +282 -0
  107. package/dist/core/engines/tinybench-engine.js.map +1 -0
  108. package/dist/core/error-manager.cjs +303 -0
  109. package/dist/core/error-manager.cjs.map +1 -0
  110. package/dist/core/error-manager.d.cts +77 -0
  111. package/dist/core/error-manager.d.cts.map +1 -0
  112. package/dist/core/error-manager.d.ts +77 -0
  113. package/dist/core/error-manager.d.ts.map +1 -0
  114. package/dist/core/error-manager.js +299 -0
  115. package/dist/core/error-manager.js.map +1 -0
  116. package/dist/core/loader.cjs +287 -0
  117. package/dist/core/loader.cjs.map +1 -0
  118. package/dist/core/loader.d.cts +55 -0
  119. package/dist/core/loader.d.cts.map +1 -0
  120. package/dist/core/loader.d.ts +55 -0
  121. package/dist/core/loader.d.ts.map +1 -0
  122. package/dist/core/loader.js +250 -0
  123. package/dist/core/loader.js.map +1 -0
  124. package/dist/core/stats-utils.cjs +99 -0
  125. package/dist/core/stats-utils.cjs.map +1 -0
  126. package/dist/core/stats-utils.d.cts +50 -0
  127. package/dist/core/stats-utils.d.cts.map +1 -0
  128. package/dist/core/stats-utils.d.ts +50 -0
  129. package/dist/core/stats-utils.d.ts.map +1 -0
  130. package/dist/core/stats-utils.js +94 -0
  131. package/dist/core/stats-utils.js.map +1 -0
  132. package/dist/index.cjs +64 -0
  133. package/dist/index.cjs.map +1 -0
  134. package/dist/index.d.cts +22 -0
  135. package/dist/index.d.cts.map +1 -0
  136. package/dist/index.d.ts +22 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +30 -0
  139. package/dist/index.js.map +1 -0
  140. package/dist/progress/manager.cjs +325 -0
  141. package/dist/progress/manager.cjs.map +1 -0
  142. package/dist/progress/manager.d.cts +125 -0
  143. package/dist/progress/manager.d.cts.map +1 -0
  144. package/dist/progress/manager.d.ts +125 -0
  145. package/dist/progress/manager.d.ts.map +1 -0
  146. package/dist/progress/manager.js +321 -0
  147. package/dist/progress/manager.js.map +1 -0
  148. package/dist/reporters/csv.cjs +250 -0
  149. package/dist/reporters/csv.cjs.map +1 -0
  150. package/dist/reporters/csv.d.cts +92 -0
  151. package/dist/reporters/csv.d.cts.map +1 -0
  152. package/dist/reporters/csv.d.ts +92 -0
  153. package/dist/reporters/csv.d.ts.map +1 -0
  154. package/dist/reporters/csv.js +246 -0
  155. package/dist/reporters/csv.js.map +1 -0
  156. package/dist/reporters/human.cjs +516 -0
  157. package/dist/reporters/human.cjs.map +1 -0
  158. package/dist/reporters/human.d.cts +86 -0
  159. package/dist/reporters/human.d.cts.map +1 -0
  160. package/dist/reporters/human.d.ts +86 -0
  161. package/dist/reporters/human.d.ts.map +1 -0
  162. package/dist/reporters/human.js +509 -0
  163. package/dist/reporters/human.js.map +1 -0
  164. package/dist/reporters/index.cjs +17 -0
  165. package/dist/reporters/index.cjs.map +1 -0
  166. package/dist/reporters/index.d.cts +10 -0
  167. package/dist/reporters/index.d.cts.map +1 -0
  168. package/dist/reporters/index.d.ts +10 -0
  169. package/dist/reporters/index.d.ts.map +1 -0
  170. package/dist/reporters/index.js +10 -0
  171. package/dist/reporters/index.js.map +1 -0
  172. package/dist/reporters/json.cjs +215 -0
  173. package/dist/reporters/json.cjs.map +1 -0
  174. package/dist/reporters/json.d.cts +79 -0
  175. package/dist/reporters/json.d.cts.map +1 -0
  176. package/dist/reporters/json.d.ts +79 -0
  177. package/dist/reporters/json.d.ts.map +1 -0
  178. package/dist/reporters/json.js +211 -0
  179. package/dist/reporters/json.js.map +1 -0
  180. package/dist/reporters/registry.cjs +255 -0
  181. package/dist/reporters/registry.cjs.map +1 -0
  182. package/dist/reporters/registry.d.cts +155 -0
  183. package/dist/reporters/registry.d.cts.map +1 -0
  184. package/dist/reporters/registry.d.ts +155 -0
  185. package/dist/reporters/registry.d.ts.map +1 -0
  186. package/dist/reporters/registry.js +249 -0
  187. package/dist/reporters/registry.js.map +1 -0
  188. package/dist/reporters/simple.cjs +328 -0
  189. package/dist/reporters/simple.cjs.map +1 -0
  190. package/dist/reporters/simple.d.cts +51 -0
  191. package/dist/reporters/simple.d.cts.map +1 -0
  192. package/dist/reporters/simple.d.ts +51 -0
  193. package/dist/reporters/simple.d.ts.map +1 -0
  194. package/dist/reporters/simple.js +321 -0
  195. package/dist/reporters/simple.js.map +1 -0
  196. package/dist/schema/modestbench-config.schema.json +162 -0
  197. package/dist/storage/history.cjs +456 -0
  198. package/dist/storage/history.cjs.map +1 -0
  199. package/dist/storage/history.d.cts +99 -0
  200. package/dist/storage/history.d.cts.map +1 -0
  201. package/dist/storage/history.d.ts +99 -0
  202. package/dist/storage/history.d.ts.map +1 -0
  203. package/dist/storage/history.js +452 -0
  204. package/dist/storage/history.js.map +1 -0
  205. package/dist/types/cli.cjs +21 -0
  206. package/dist/types/cli.cjs.map +1 -0
  207. package/dist/types/cli.d.cts +296 -0
  208. package/dist/types/cli.d.cts.map +1 -0
  209. package/dist/types/cli.d.ts +296 -0
  210. package/dist/types/cli.d.ts.map +1 -0
  211. package/dist/types/cli.js +18 -0
  212. package/dist/types/cli.js.map +1 -0
  213. package/dist/types/core.cjs +14 -0
  214. package/dist/types/core.cjs.map +1 -0
  215. package/dist/types/core.d.cts +380 -0
  216. package/dist/types/core.d.cts.map +1 -0
  217. package/dist/types/core.d.ts +380 -0
  218. package/dist/types/core.d.ts.map +1 -0
  219. package/dist/types/core.js +13 -0
  220. package/dist/types/core.js.map +1 -0
  221. package/dist/types/index.cjs +27 -0
  222. package/dist/types/index.cjs.map +1 -0
  223. package/dist/types/index.d.cts +11 -0
  224. package/dist/types/index.d.cts.map +1 -0
  225. package/dist/types/index.d.ts +11 -0
  226. package/dist/types/index.d.ts.map +1 -0
  227. package/dist/types/index.js +11 -0
  228. package/dist/types/index.js.map +1 -0
  229. package/dist/types/interfaces.cjs +10 -0
  230. package/dist/types/interfaces.cjs.map +1 -0
  231. package/dist/types/interfaces.d.cts +381 -0
  232. package/dist/types/interfaces.d.cts.map +1 -0
  233. package/dist/types/interfaces.d.ts +381 -0
  234. package/dist/types/interfaces.d.ts.map +1 -0
  235. package/dist/types/interfaces.js +9 -0
  236. package/dist/types/interfaces.js.map +1 -0
  237. package/dist/types/utility.cjs +92 -0
  238. package/dist/types/utility.cjs.map +1 -0
  239. package/dist/types/utility.d.cts +330 -0
  240. package/dist/types/utility.d.cts.map +1 -0
  241. package/dist/types/utility.d.ts +330 -0
  242. package/dist/types/utility.d.ts.map +1 -0
  243. package/dist/types/utility.js +78 -0
  244. package/dist/types/utility.js.map +1 -0
  245. package/package.json +211 -0
  246. package/src/bootstrap.ts +35 -0
  247. package/src/cli/commands/history.ts +569 -0
  248. package/src/cli/commands/init.ts +658 -0
  249. package/src/cli/commands/run.ts +346 -0
  250. package/src/cli/index.ts +642 -0
  251. package/src/config/manager.ts +387 -0
  252. package/src/config/schema.ts +188 -0
  253. package/src/constants.ts +21 -0
  254. package/src/core/benchmark-schema.ts +185 -0
  255. package/src/core/engine.ts +888 -0
  256. package/src/core/engines/accurate-engine.ts +408 -0
  257. package/src/core/engines/index.ts +16 -0
  258. package/src/core/engines/tinybench-engine.ts +335 -0
  259. package/src/core/error-manager.ts +372 -0
  260. package/src/core/loader.ts +324 -0
  261. package/src/core/stats-utils.ts +135 -0
  262. package/src/index.ts +46 -0
  263. package/src/progress/manager.ts +415 -0
  264. package/src/reporters/csv.ts +368 -0
  265. package/src/reporters/human.ts +707 -0
  266. package/src/reporters/index.ts +10 -0
  267. package/src/reporters/json.ts +302 -0
  268. package/src/reporters/registry.ts +349 -0
  269. package/src/reporters/simple.ts +459 -0
  270. package/src/storage/history.ts +600 -0
  271. package/src/types/cli.ts +312 -0
  272. package/src/types/core.ts +414 -0
  273. package/src/types/index.ts +18 -0
  274. package/src/types/interfaces.ts +451 -0
  275. package/src/types/utility.ts +446 -0
@@ -0,0 +1,415 @@
1
+ /**
2
+ * ModestBench Progress Manager
3
+ *
4
+ * Tracks execution progress, estimates completion times, and manages progress
5
+ * callbacks for real-time updates during benchmark runs.
6
+ */
7
+
8
+ import type {
9
+ BenchmarkRun,
10
+ ProgressManager,
11
+ ProgressState,
12
+ } from '../types/index.js';
13
+
14
+ /**
15
+ * Progress callback function type
16
+ */
17
+ type ProgressCallback = (state: ProgressState) => void;
18
+
19
+ /**
20
+ * Progress calculation utilities
21
+ */
22
+ interface ProgressMetrics {
23
+ readonly currentThroughput: number;
24
+ readonly estimatedTotal: number;
25
+ readonly recentTimings: number[];
26
+ readonly startTime: number;
27
+ }
28
+
29
+ /**
30
+ * Default progress manager implementation
31
+ */
32
+ export class ModestBenchProgressManager implements ProgressManager {
33
+ private callbacks: ProgressCallback[] = [];
34
+
35
+ private lastUpdate = 0;
36
+
37
+ private readonly maxRecentTimings = 10;
38
+
39
+ private metrics: null | ProgressMetrics = null;
40
+
41
+ private state: ProgressState;
42
+
43
+ private readonly updateThrottleMs = 100; // Limit updates to avoid spam
44
+
45
+ constructor() {
46
+ this.state = this.createInitialState();
47
+ }
48
+
49
+ /**
50
+ * Clean up progress tracking resources
51
+ */
52
+ cleanup(): void {
53
+ this.callbacks = [];
54
+ this.metrics = null;
55
+ this.state = this.createInitialState();
56
+ }
57
+
58
+ /**
59
+ * Estimate completion time
60
+ */
61
+ estimateCompletion(): Date | null {
62
+ if (
63
+ !this.metrics ||
64
+ this.state.totalTasks === 0 ||
65
+ this.state.tasksCompleted === 0
66
+ ) {
67
+ return null;
68
+ }
69
+
70
+ const remainingTasks = this.state.totalTasks - this.state.tasksCompleted;
71
+
72
+ if (remainingTasks <= 0) {
73
+ return new Date(); // Already complete
74
+ }
75
+
76
+ // Calculate average throughput from recent timings
77
+ const throughput = this.calculateThroughput();
78
+
79
+ if (throughput <= 0) {
80
+ return null; // Can't estimate with no throughput data
81
+ }
82
+
83
+ const estimatedRemainingMs = (remainingTasks / throughput) * 1000;
84
+ return new Date(Date.now() + estimatedRemainingMs);
85
+ }
86
+
87
+ /**
88
+ * Force an immediate progress update (bypassing throttling)
89
+ */
90
+ forceUpdate(): void {
91
+ const oldThrottle = this.lastUpdate;
92
+ this.lastUpdate = 0; // Reset throttle
93
+ this.update({}); // Trigger update with no changes
94
+ this.lastUpdate = oldThrottle; // Restore throttle timing
95
+ }
96
+
97
+ /**
98
+ * Format elapsed time as human-readable string
99
+ */
100
+ getFormattedElapsed(): string {
101
+ const seconds = Math.floor(this.state.elapsed / 1000);
102
+ const minutes = Math.floor(seconds / 60);
103
+ const hours = Math.floor(minutes / 60);
104
+
105
+ if (hours > 0) {
106
+ return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
107
+ } else if (minutes > 0) {
108
+ return `${minutes}m ${seconds % 60}s`;
109
+ } else {
110
+ return `${seconds}s`;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Format estimated remaining time as human-readable string
116
+ */
117
+ getFormattedEstimate(): null | string {
118
+ const completion = this.estimateCompletion();
119
+ if (!completion) {
120
+ return null;
121
+ }
122
+
123
+ const remaining = completion.getTime() - Date.now();
124
+ if (remaining <= 0) {
125
+ return 'Complete';
126
+ }
127
+
128
+ const seconds = Math.floor(remaining / 1000);
129
+ const minutes = Math.floor(seconds / 60);
130
+ const hours = Math.floor(minutes / 60);
131
+
132
+ if (hours > 0) {
133
+ return `~${hours}h ${minutes % 60}m remaining`;
134
+ } else if (minutes > 0) {
135
+ return `~${minutes}m ${seconds % 60}s remaining`;
136
+ } else {
137
+ return `~${seconds}s remaining`;
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Get detailed progress metrics
143
+ */
144
+ getMetrics(): null | {
145
+ elapsedMs: number;
146
+ estimatedCompletion: Date | null;
147
+ remainingTasks: number;
148
+ throughput: number;
149
+ } {
150
+ if (!this.metrics) {
151
+ return null;
152
+ }
153
+
154
+ const throughput = this.calculateThroughput();
155
+ const estimatedCompletion = this.estimateCompletion();
156
+ const elapsedMs = Date.now() - this.metrics.startTime;
157
+ const remainingTasks = Math.max(
158
+ 0,
159
+ this.state.totalTasks - this.state.tasksCompleted,
160
+ );
161
+
162
+ return {
163
+ elapsedMs,
164
+ estimatedCompletion,
165
+ remainingTasks,
166
+ throughput,
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Get progress as a fraction (0.0 to 1.0)
172
+ */
173
+ getProgressFraction(): number {
174
+ return this.state.percentage / 100;
175
+ }
176
+
177
+ /**
178
+ * Get current progress state
179
+ */
180
+ getState(): ProgressState {
181
+ return { ...this.state };
182
+ }
183
+
184
+ /**
185
+ * Increment the completed files counter
186
+ */
187
+ incrementFiles(): void {
188
+ this.update({ filesCompleted: this.state.filesCompleted + 1 });
189
+ }
190
+
191
+ /**
192
+ * Increment the completed suites counter
193
+ */
194
+ incrementSuites(): void {
195
+ this.update({ suitesCompleted: this.state.suitesCompleted + 1 });
196
+ }
197
+
198
+ /**
199
+ * Increment the completed tasks counter
200
+ */
201
+ incrementTasks(): void {
202
+ this.update({ tasksCompleted: this.state.tasksCompleted + 1 });
203
+ }
204
+
205
+ /**
206
+ * Initialize progress tracking for a benchmark run
207
+ */
208
+ initialize(run: BenchmarkRun): void {
209
+ // Use summary totals if available (from pre-calculation), otherwise calculate from files
210
+ const totalFiles = run.summary?.totalFiles ?? run.files.length;
211
+ let totalSuites = run.summary?.totalSuites ?? 0;
212
+ let totalTasks = run.summary?.totalTasks ?? 0;
213
+
214
+ // If we don't have summary data and have detailed run information, calculate actual totals
215
+ if (!run.summary?.totalTasks && run.files.length > 0) {
216
+ for (const file of run.files) {
217
+ for (const suite of file.suites) {
218
+ totalSuites++;
219
+ totalTasks += suite.tasks.length;
220
+ }
221
+ }
222
+ }
223
+
224
+ this.state = {
225
+ elapsed: 0,
226
+ filesCompleted: 0,
227
+ percentage: 0,
228
+ suitesCompleted: 0,
229
+ tasksCompleted: 0,
230
+ totalFiles,
231
+ totalSuites,
232
+ totalTasks,
233
+ };
234
+
235
+ this.metrics = {
236
+ currentThroughput: 0,
237
+ estimatedTotal: totalTasks, // Use tasks as the primary progress unit
238
+ recentTimings: [],
239
+ startTime: Date.now(),
240
+ };
241
+
242
+ this.lastUpdate = Date.now();
243
+ this.notifyCallbacks();
244
+ }
245
+
246
+ /**
247
+ * Check if the run is complete
248
+ */
249
+ isComplete(): boolean {
250
+ return (
251
+ this.state.tasksCompleted >= this.state.totalTasks &&
252
+ this.state.totalTasks > 0
253
+ );
254
+ }
255
+
256
+ /**
257
+ * Register a callback for progress updates
258
+ */
259
+ onProgress(callback: ProgressCallback): void {
260
+ this.callbacks.push(callback);
261
+ } /**
262
+ * Remove a progress callback
263
+ */
264
+
265
+ removeCallback(callback: ProgressCallback): boolean {
266
+ const index = this.callbacks.indexOf(callback);
267
+ if (index >= 0) {
268
+ this.callbacks.splice(index, 1);
269
+ return true;
270
+ }
271
+ return false;
272
+ }
273
+
274
+ /**
275
+ * Set the current file being processed
276
+ */
277
+ setCurrentFile(file: string): void {
278
+ this.update({ currentFile: file });
279
+ }
280
+
281
+ /**
282
+ * Set the current suite being processed
283
+ */
284
+ setCurrentSuite(suite: string): void {
285
+ this.update({ currentSuite: suite });
286
+ }
287
+
288
+ /**
289
+ * Set the current task being processed
290
+ */
291
+ setCurrentTask(task: string): void {
292
+ this.update({ currentTask: task });
293
+ }
294
+
295
+ /**
296
+ * Update progress state
297
+ */
298
+ update(updates: Partial<ProgressState>): void {
299
+ const now = Date.now();
300
+
301
+ // Throttle updates to avoid excessive callbacks
302
+ if (now - this.lastUpdate < this.updateThrottleMs) {
303
+ return;
304
+ }
305
+
306
+ // Calculate elapsed time
307
+ const elapsed = this.metrics ? now - this.metrics.startTime : 0;
308
+
309
+ // Apply updates
310
+ this.state = {
311
+ ...this.state,
312
+ ...updates,
313
+ elapsed,
314
+ percentage: this.calculatePercentage(updates),
315
+ };
316
+
317
+ // Update metrics for completion estimation
318
+ this.updateMetrics(now);
319
+
320
+ this.lastUpdate = now;
321
+ this.notifyCallbacks();
322
+ }
323
+
324
+ /**
325
+ * Calculate progress percentage from current state
326
+ */
327
+ private calculatePercentage(updates: Partial<ProgressState>): number {
328
+ const currentState = { ...this.state, ...updates };
329
+
330
+ if (currentState.totalTasks === 0) {
331
+ return 0;
332
+ }
333
+
334
+ return Math.min(
335
+ 100,
336
+ Math.max(
337
+ 0,
338
+ (currentState.tasksCompleted / currentState.totalTasks) * 100,
339
+ ),
340
+ );
341
+ }
342
+
343
+ /**
344
+ * Calculate average throughput from recent measurements
345
+ */
346
+ private calculateThroughput(): number {
347
+ if (!this.metrics || this.metrics.recentTimings.length === 0) {
348
+ return 0;
349
+ }
350
+
351
+ // Use moving average of recent throughput measurements
352
+ const sum = this.metrics.recentTimings.reduce(
353
+ (acc, timing) => acc + timing,
354
+ 0,
355
+ );
356
+ return sum / this.metrics.recentTimings.length;
357
+ }
358
+
359
+ /**
360
+ * Create initial progress state
361
+ */
362
+ private createInitialState(): ProgressState {
363
+ return {
364
+ elapsed: 0,
365
+ filesCompleted: 0,
366
+ percentage: 0,
367
+ suitesCompleted: 0,
368
+ tasksCompleted: 0,
369
+ totalFiles: 0,
370
+ totalSuites: 0,
371
+ totalTasks: 0,
372
+ };
373
+ }
374
+
375
+ /**
376
+ * Notify all registered callbacks of state changes
377
+ */
378
+ private notifyCallbacks(): void {
379
+ for (const callback of this.callbacks) {
380
+ try {
381
+ callback(this.state);
382
+ } catch (error) {
383
+ console.error('Error in progress callback:', error);
384
+ }
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Update throughput metrics
390
+ */
391
+ private updateMetrics(now: number): void {
392
+ if (!this.metrics) {
393
+ return;
394
+ }
395
+
396
+ // Track timing for throughput calculation
397
+ const elapsed = now - this.metrics.startTime;
398
+ if (elapsed > 0 && this.state.tasksCompleted > 0) {
399
+ const currentThroughput = this.state.tasksCompleted / (elapsed / 1000); // tasks per second
400
+
401
+ // Add to recent timings for moving average
402
+ this.metrics.recentTimings.push(currentThroughput);
403
+
404
+ // Keep only the most recent timings
405
+ if (this.metrics.recentTimings.length > this.maxRecentTimings) {
406
+ this.metrics.recentTimings.shift();
407
+ }
408
+
409
+ this.metrics = {
410
+ ...this.metrics,
411
+ currentThroughput,
412
+ };
413
+ }
414
+ }
415
+ }