@@ -12,7 +12,7 @@ import type {
12
12
} from './types'
13
13
14
14
import { createBenchEvent , createErrorEvent } from './event'
15
- import { getStatisticsSorted , isFnAsyncResource } from './utils'
15
+ import { getStatisticsSorted , invariant , isFnAsyncResource , isPromiseLike } from './utils'
16
16
17
17
/**
18
18
* A class that represents each benchmark task in Tinybench. It keeps track of the
@@ -109,66 +109,38 @@ export class Task extends EventTarget {
109
109
) ) as { error ?: Error ; samples ?: number [ ] }
110
110
await this . bench . opts . teardown ?.( this , 'run' )
111
111
112
- if ( latencySamples ) {
113
- this . runs = latencySamples . length
114
- const totalTime = latencySamples . reduce ( ( a , b ) => a + b , 0 )
112
+ this . processRunResult ( { error, latencySamples } )
115
113
116
- // Latency statistics
117
- const latencyStatistics = getStatisticsSorted (
118
- latencySamples . sort ( ( a , b ) => a - b )
119
- )
114
+ return this
115
+ }
120
116
121
- // Throughput statistics
122
- const throughputSamples = latencySamples
123
- . map ( sample =>
124
- sample !== 0 ? 1000 / sample : 1000 / latencyStatistics . mean
125
- ) // Use latency average as imputed sample
126
- . sort ( ( a , b ) => a - b )
127
- const throughputStatistics = getStatisticsSorted ( throughputSamples )
117
+ /**
118
+ * run the current task and write the results in `Task.result` object property
119
+ * @returns the current task
120
+ * @internal
121
+ */
122
+ runSync ( ) : this {
123
+ if ( this . result ?. error ) {
124
+ return this
125
+ }
128
126
129
- if ( this . bench . opts . signal ?. aborted ) {
130
- return this
131
- }
127
+ invariant ( this . bench . concurrency === null , 'Cannot use `concurrency` option when using `runSync`' )
128
+ this . dispatchEvent ( createBenchEvent ( 'start' , this ) )
132
129
133
- this . mergeTaskResult ( {
134
- critical : latencyStatistics . critical ,
135
- df : latencyStatistics . df ,
136
- hz : throughputStatistics . mean ,
137
- latency : latencyStatistics ,
138
- max : latencyStatistics . max ,
139
- mean : latencyStatistics . mean ,
140
- min : latencyStatistics . min ,
141
- moe : latencyStatistics . moe ,
142
- p75 : latencyStatistics . p75 ,
143
- p99 : latencyStatistics . p99 ,
144
- p995 : latencyStatistics . p995 ,
145
- p999 : latencyStatistics . p999 ,
146
- period : totalTime / this . runs ,
147
- rme : latencyStatistics . rme ,
148
- runtime : this . bench . runtime ,
149
- runtimeVersion : this . bench . runtimeVersion ,
150
- samples : latencyStatistics . samples ,
151
- sd : latencyStatistics . sd ,
152
- sem : latencyStatistics . sem ,
153
- throughput : throughputStatistics ,
154
- totalTime,
155
- variance : latencyStatistics . variance ,
156
- } )
157
- }
130
+ const setupResult = this . bench . opts . setup ?.( this , 'run' )
131
+ invariant ( ! isPromiseLike ( setupResult ) , '`setup` function must be sync when using `runSync()`' )
158
132
159
- if ( error ) {
160
- this . mergeTaskResult ( { error } )
161
- this . dispatchEvent ( createErrorEvent ( this , error ) )
162
- this . bench . dispatchEvent ( createErrorEvent ( this , error ) )
163
- if ( this . bench . opts . throws ) {
164
- throw error
165
- }
166
- }
133
+ const { error, samples : latencySamples } = ( this . benchmarkSync (
134
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
135
+ this . bench . opts . time ! ,
136
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
137
+ this . bench . opts . iterations !
138
+ ) ) as { error ?: Error ; samples ?: number [ ] }
167
139
168
- this . dispatchEvent ( createBenchEvent ( 'cycle' , this ) )
169
- this . bench . dispatchEvent ( createBenchEvent ( 'cycle' , this ) )
170
- // cycle and complete are equal in Task
171
- this . dispatchEvent ( createBenchEvent ( 'complete' , this ) )
140
+ const teardownResult = this . bench . opts . teardown ?. ( this , 'run' )
141
+ invariant ( ! isPromiseLike ( teardownResult ) , '`teardown` function must be sync when using `runSync()`' )
142
+
143
+ this . processRunResult ( { error , latencySamples } )
172
144
173
145
return this
174
146
}
@@ -191,14 +163,34 @@ export class Task extends EventTarget {
191
163
) ) as { error ?: Error }
192
164
await this . bench . opts . teardown ?.( this , 'warmup' )
193
165
194
- if ( error ) {
195
- this . mergeTaskResult ( { error } )
196
- this . dispatchEvent ( createErrorEvent ( this , error ) )
197
- this . bench . dispatchEvent ( createErrorEvent ( this , error ) )
198
- if ( this . bench . opts . throws ) {
199
- throw error
200
- }
166
+ this . postWarmup ( error )
167
+ }
168
+
169
+ /**
170
+ * warmup the current task (sync version)
171
+ * @internal
172
+ */
173
+ warmupSync ( ) : void {
174
+ if ( this . result ?. error ) {
175
+ return
201
176
}
177
+
178
+ this . dispatchEvent ( createBenchEvent ( 'warmup' , this ) )
179
+
180
+ const setupResult = this . bench . opts . setup ?.( this , 'warmup' )
181
+ invariant ( ! isPromiseLike ( setupResult ) , '`setup` function must be sync when using `runSync()`' )
182
+
183
+ const { error } = ( this . benchmarkSync (
184
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
185
+ this . bench . opts . warmupTime ! ,
186
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
187
+ this . bench . opts . warmupIterations !
188
+ ) ) as { error ?: Error }
189
+
190
+ const teardownResult = this . bench . opts . teardown ?.( this , 'warmup' )
191
+ invariant ( ! isPromiseLike ( teardownResult ) , '`teardown` function must be sync when using `runSync()`' )
192
+
193
+ this . postWarmup ( error )
202
194
}
203
195
204
196
private async benchmark (
@@ -278,6 +270,69 @@ export class Task extends EventTarget {
278
270
return { samples }
279
271
}
280
272
273
+ private benchmarkSync (
274
+ time : number ,
275
+ iterations : number
276
+ ) : { error ?: unknown ; samples ?: number [ ] } {
277
+ if ( this . fnOpts . beforeAll != null ) {
278
+ try {
279
+ const beforeAllResult = this . fnOpts . beforeAll . call ( this )
280
+ invariant ( ! isPromiseLike ( beforeAllResult ) , '`beforeAll` function must be sync when using `runSync()`' )
281
+ } catch ( error ) {
282
+ return { error }
283
+ }
284
+ }
285
+
286
+ // TODO: factor out
287
+ let totalTime = 0 // ms
288
+ const samples : number [ ] = [ ]
289
+ const benchmarkTask = ( ) => {
290
+ if ( this . fnOpts . beforeEach != null ) {
291
+ const beforeEachResult = this . fnOpts . beforeEach . call ( this )
292
+ invariant ( ! isPromiseLike ( beforeEachResult ) , '`beforeEach` function must be sync when using `runSync()`' )
293
+ }
294
+
295
+ let taskTime = 0 // ms;
296
+
297
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
298
+ const taskStart = this . bench . opts . now ! ( )
299
+ // eslint-disable-next-line no-useless-call
300
+ const result = this . fn . call ( this )
301
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
302
+ taskTime = this . bench . opts . now ! ( ) - taskStart
303
+
304
+ invariant ( ! isPromiseLike ( result ) , 'task function must be sync when using `runSync()`' )
305
+
306
+ samples . push ( taskTime )
307
+ totalTime += taskTime
308
+
309
+ if ( this . fnOpts . afterEach != null ) {
310
+ const afterEachResult = this . fnOpts . afterEach . call ( this )
311
+ invariant ( ! isPromiseLike ( afterEachResult ) , '`afterEach` function must be sync when using `runSync()`' )
312
+ }
313
+ }
314
+
315
+ try {
316
+ while (
317
+ // eslint-disable-next-line no-unmodified-loop-condition
318
+ ( totalTime < time || samples . length < iterations ) ) {
319
+ benchmarkTask ( )
320
+ }
321
+ } catch ( error ) {
322
+ return { error }
323
+ }
324
+
325
+ if ( this . fnOpts . afterAll != null ) {
326
+ try {
327
+ const afterAllResult = this . fnOpts . afterAll . call ( this )
328
+ invariant ( ! isPromiseLike ( afterAllResult ) , '`afterAll` function must be sync when using `runSync()`' )
329
+ } catch ( error ) {
330
+ return { error }
331
+ }
332
+ }
333
+ return { samples }
334
+ }
335
+
281
336
/**
282
337
* merge into the result object values
283
338
* @param result - the task result object to merge with the current result object values
@@ -288,4 +343,78 @@ export class Task extends EventTarget {
288
343
...result ,
289
344
} ) as Readonly < TaskResult >
290
345
}
346
+
347
+ private postWarmup ( error : Error | undefined ) : void {
348
+ if ( error ) {
349
+ this . mergeTaskResult ( { error } )
350
+ this . dispatchEvent ( createErrorEvent ( this , error ) )
351
+ this . bench . dispatchEvent ( createErrorEvent ( this , error ) )
352
+ if ( this . bench . opts . throws ) {
353
+ throw error
354
+ }
355
+ }
356
+ }
357
+
358
+ private processRunResult ( { error, latencySamples } : { error ?: Error , latencySamples ?: number [ ] } ) : void {
359
+ if ( latencySamples ) {
360
+ this . runs = latencySamples . length
361
+ const totalTime = latencySamples . reduce ( ( a , b ) => a + b , 0 )
362
+
363
+ // Latency statistics
364
+ const latencyStatistics = getStatisticsSorted (
365
+ latencySamples . sort ( ( a , b ) => a - b )
366
+ )
367
+
368
+ // Throughput statistics
369
+ const throughputSamples = latencySamples
370
+ . map ( sample =>
371
+ sample !== 0 ? 1000 / sample : 1000 / latencyStatistics . mean
372
+ ) // Use latency average as imputed sample
373
+ . sort ( ( a , b ) => a - b )
374
+ const throughputStatistics = getStatisticsSorted ( throughputSamples )
375
+
376
+ if ( this . bench . opts . signal ?. aborted ) {
377
+ return
378
+ }
379
+
380
+ this . mergeTaskResult ( {
381
+ critical : latencyStatistics . critical ,
382
+ df : latencyStatistics . df ,
383
+ hz : throughputStatistics . mean ,
384
+ latency : latencyStatistics ,
385
+ max : latencyStatistics . max ,
386
+ mean : latencyStatistics . mean ,
387
+ min : latencyStatistics . min ,
388
+ moe : latencyStatistics . moe ,
389
+ p75 : latencyStatistics . p75 ,
390
+ p99 : latencyStatistics . p99 ,
391
+ p995 : latencyStatistics . p995 ,
392
+ p999 : latencyStatistics . p999 ,
393
+ period : totalTime / this . runs ,
394
+ rme : latencyStatistics . rme ,
395
+ runtime : this . bench . runtime ,
396
+ runtimeVersion : this . bench . runtimeVersion ,
397
+ samples : latencyStatistics . samples ,
398
+ sd : latencyStatistics . sd ,
399
+ sem : latencyStatistics . sem ,
400
+ throughput : throughputStatistics ,
401
+ totalTime,
402
+ variance : latencyStatistics . variance ,
403
+ } )
404
+ }
405
+
406
+ if ( error ) {
407
+ this . mergeTaskResult ( { error } )
408
+ this . dispatchEvent ( createErrorEvent ( this , error ) )
409
+ this . bench . dispatchEvent ( createErrorEvent ( this , error ) )
410
+ if ( this . bench . opts . throws ) {
411
+ throw error
412
+ }
413
+ }
414
+
415
+ this . dispatchEvent ( createBenchEvent ( 'cycle' , this ) )
416
+ this . bench . dispatchEvent ( createBenchEvent ( 'cycle' , this ) )
417
+ // cycle and complete are equal in Task
418
+ this . dispatchEvent ( createBenchEvent ( 'complete' , this ) )
419
+ }
291
420
}
0 commit comments