@@ -12,7 +12,7 @@ import type {
1212} from './types'
1313
1414import { createBenchEvent , createErrorEvent } from './event'
15- import { getStatisticsSorted , isFnAsyncResource } from './utils'
15+ import { getStatisticsSorted , invariant , isFnAsyncResource , isPromiseLike } from './utils'
1616
1717/**
1818 * A class that represents each benchmark task in Tinybench. It keeps track of the
@@ -109,66 +109,38 @@ export class Task extends EventTarget {
109109 ) ) as { error ?: Error ; samples ?: number [ ] }
110110 await this . bench . opts . teardown ?.( this , 'run' )
111111
112- if ( latencySamples ) {
113- this . runs = latencySamples . length
114- const totalTime = latencySamples . reduce ( ( a , b ) => a + b , 0 )
112+ this . processRunResult ( { error, latencySamples } )
115113
116- // Latency statistics
117- const latencyStatistics = getStatisticsSorted (
118- latencySamples . sort ( ( a , b ) => a - b )
119- )
114+ return this
115+ }
120116
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+ }
128126
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 ) )
132129
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()`' )
158132
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 [ ] }
167139
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 } )
172144
173145 return this
174146 }
@@ -191,14 +163,34 @@ export class Task extends EventTarget {
191163 ) ) as { error ?: Error }
192164 await this . bench . opts . teardown ?.( this , 'warmup' )
193165
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
201176 }
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 )
202194 }
203195
204196 private async benchmark (
@@ -278,6 +270,69 @@ export class Task extends EventTarget {
278270 return { samples }
279271 }
280272
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+
281336 /**
282337 * merge into the result object values
283338 * @param result - the task result object to merge with the current result object values
@@ -288,4 +343,78 @@ export class Task extends EventTarget {
288343 ...result ,
289344 } ) as Readonly < TaskResult >
290345 }
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+ }
291420}
0 commit comments