diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..d2c4143 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,35 @@ +{ + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "tsconfig.json" + }, + "plugins": [ + "@typescript-eslint/eslint-plugin" + ], + "extends": [ + "airbnb-base", + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/eslint-recommended" + ], + "root": true, + "env": { + "node": true, + "commonjs": true + }, + "rules": { + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/ban-types": "off", + "import/no-unresolved": "off", + "import/extensions": "off", + "no-underscore-dangle": "off", + "indent": [ + "error", + 2 + ], + "max-len": 0 + } +} \ No newline at end of file diff --git a/@types/index.d.ts b/@types/index.d.ts index 75049b2..8e5bbd3 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -1,4 +1,4 @@ -import Task from "../src/task"; +import Task from '../src/task'; declare global { /** @@ -14,12 +14,12 @@ declare global { * the last error that was thrown while running the task */ error?: unknown; - + /** * The amount of time in milliseconds to run the benchmark task (cycle). */ totalTime: number; - + /** * the minimum value in the samples */ @@ -28,159 +28,159 @@ declare global { * the maximum value in the samples */ max: number; - + /** * the number of operations per second */ hz: number; - + /** * how long each operation takes (ms) */ period: number; - + /** * task samples of each task iteration time (ms) */ samples: number[]; - + /** * samples mean/average (estimate of the population mean) */ mean: number; - + /** * samples variance (estimate of the population variance) */ variance: number; - + /** * samples standard deviation (estimate of the population standard deviation) */ sd: number; - + /** * standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean) */ sem: number; - + /** * degrees of freedom */ df: number; - + /** * critical value of the samples */ critical: number; - + /** * margin of error */ moe: number; - + /** * relative margin of error */ rme: number; - + /** * p75 percentile */ p75: number; - + /** * p99 percentile */ p99: number; - + /** * p995 percentile */ p995: number; - + /** * p999 percentile */ p999: number; }; - + /** * Both the `Task` and `Bench` objects extend the `EventTarget` object, * so you can attach a listeners to different types of events * to each class instance using the universal `addEventListener` and * `removeEventListener` */ - + /** * Bench events */ export type IBenchEvents = - | "abort" // when a signal aborts - | "complete" // when running a benchmark finishes - | "error" // when the benchmark task throws - | "reset" // when the reset function gets called - | "start" // when running the benchmarks gets started - | "warmup" // when the benchmarks start getting warmed up (before start) - | "cycle" // when running each benchmark task gets done (cycle) - | "add" // when a Task gets added to the Bench - | "remove"; // when a Task gets removed of the Bench - + | 'abort' // when a signal aborts + | 'complete' // when running a benchmark finishes + | 'error' // when the benchmark task throws + | 'reset' // when the reset function gets called + | 'start' // when running the benchmarks gets started + | 'warmup' // when the benchmarks start getting warmed up (before start) + | 'cycle' // when running each benchmark task gets done (cycle) + | 'add' // when a Task gets added to the Bench + | 'remove'; // when a Task gets removed of the Bench + + export type IHook = (task: Task, mode: 'warmup' | 'run') => void | Promise; + /** * task events */ export type ITaskEvents = - | "abort" - | "complete" - | "error" - | "reset" - | "start" - | "warmup" - | "cycle"; - + | 'abort' + | 'complete' + | 'error' + | 'reset' + | 'start' + | 'warmup' + | 'cycle'; + export type IOptions = { /** * time needed for running a benchmark task (milliseconds) @default 500 */ time?: number; - + /** * number of times that a task should run if even the time option is finished @default 10 */ iterations?: number; - + /** * function to get the current timestamp in milliseconds */ now?: () => number; - + /** * An AbortSignal for aborting the benchmark */ signal?: AbortSignal; - + /** * warmup time (milliseconds) @default 100ms */ warmupTime?: number; - + /** * warmup iterations @default 5 */ warmupIterations?: number; - + /** * setup function to run before each benchmark task (cycle) */ setup?: IHook; - + /** * teardown function to run after each benchmark task (cycle) */ teardown?: IHook; }; - - export type IHook = (task: Task, mode: "warmup" | "run") => void | Promise; export type IBenchEvent = Event & { task: Task | null; diff --git a/README.md b/README.md index 12ddc12..951a27e 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ completely based on the Web APIs with proper timing using `process.hrtime` or _In case you need more tiny libraries like tinypool or tinyspy, please consider submitting an [RFC](https://github.com/tinylibs/rfcs)_ ## Requirements -- node version >= v14 +- node version >= v16 ## Installing diff --git a/package.json b/package.json index 337c828..a691686 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,14 @@ "@size-limit/preset-small-lib": "^7.0.4", "@size-limit/time": "^7.0.4", "@types/node": "^18.7.13", + "@typescript-eslint/eslint-plugin": "^5.35.1", + "@typescript-eslint/parser": "^5.35.1", "bumpp": "^8.2.0", + "changelogithub": "^0.12.4", "clean-publish": "^3.4.4", + "eslint": "^8.22.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-plugin-import": "^2.26.0", "happy-dom": "^2.25.1", "husky": "^7.0.4", "nano-staged": "^0.5.0", @@ -47,8 +53,7 @@ "tsup": "^5.11.7", "typescript": "^4.5.4", "vite": "^2.9.12", - "vitest": "^0.14.2", - "changelogithub": "^0.6.5" + "vitest": "^0.14.2" }, "keywords": [ "spy", diff --git a/src/bench.ts b/src/bench.ts index 6afe034..0f322d7 100644 --- a/src/bench.ts +++ b/src/bench.ts @@ -1,6 +1,6 @@ -import { createBenchEvent } from "./event"; -import Task from "./task"; -import { now } from "./utils"; +import { createBenchEvent } from './event'; +import Task from './task'; +import { now } from './utils'; /** * The Benchmark instance for keeping track of the benchmark tasks and controlling @@ -20,8 +20,10 @@ export default class Bench extends EventTarget { iterations = 10; now = now; - setup: IHook = () => {}; - teardown: IHook = () => {}; + + setup: IHook; + + teardown: IHook; constructor(options: IOptions = {}) { super(); @@ -31,8 +33,10 @@ export default class Bench extends EventTarget { this.time = options.time ?? this.time; this.iterations = options.iterations ?? this.iterations; this.signal = options.signal; - this.setup = options.setup ?? this.setup; - this.teardown = options.teardown ?? this.teardown; + // eslint-disable-next-line @typescript-eslint/no-empty-function + this.setup = options.setup ?? (() => {}); + // eslint-disable-next-line @typescript-eslint/no-empty-function + this.teardown = options.teardown ?? (() => {}); if (this.signal) { this.signal.addEventListener( diff --git a/src/event.ts b/src/event.ts index c764596..6f9c8d8 100644 --- a/src/event.ts +++ b/src/event.ts @@ -1,8 +1,8 @@ -import Task from "./task"; +import Task from './task'; -export function createBenchEvent( +function createBenchEvent( eventType: IBenchEvents, - target: Task | null = null + target: Task | null = null, ) { const event = new Event(eventType); Object.defineProperty(event, 'task', { @@ -13,3 +13,8 @@ export function createBenchEvent( }); return event; } + +export default createBenchEvent; +export { + createBenchEvent, +}; diff --git a/src/index.ts b/src/index.ts index a5cf3d2..515eac7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ -import Bench from "./bench"; -import Task from "./task"; +import Bench from './bench'; +import Task from './task'; -export { now } from "./utils"; +export { now } from './utils'; export { Bench, Task }; export default Bench; diff --git a/src/task.ts b/src/task.ts index a245f49..2e4b2ea 100644 --- a/src/task.ts +++ b/src/task.ts @@ -1,7 +1,7 @@ -import Bench from "./bench"; -import tTable from "./constants"; -import { createBenchEvent } from "./event"; -import { getMean, getVariance } from "./utils"; +import Bench from './bench'; +import tTable from './constants'; +import { createBenchEvent } from './event'; +import { getMean, getVariance } from './utils'; /** * A class that represents each benchmark task in Tinybench. It keeps track of the @@ -46,7 +46,7 @@ export default class Task extends EventTarget { let totalTime = 0; // ms const samples: number[] = []; - await this.bench.setup(this, "run"); + await this.bench.setup(this, 'run'); while ( (totalTime < this.bench.time || this.runs < this.bench.iterations) && !this.bench.signal?.aborted @@ -54,6 +54,7 @@ export default class Task extends EventTarget { const taskStart = this.bench.now(); try { + // eslint-disable-next-line no-await-in-loop await Promise.resolve().then(this.fn); } catch (e) { this.setResult({ error: e }); @@ -64,7 +65,7 @@ export default class Task extends EventTarget { samples.push(taskTime); totalTime = this.bench.now() - startTime; } - await this.bench.teardown(this, "run"); + await this.bench.teardown(this, 'run'); samples.sort(); @@ -116,15 +117,15 @@ export default class Task extends EventTarget { }); } + // eslint-disable-next-line no-lone-blocks { if (this.result?.error) { this.dispatchEvent(createBenchEvent('error', this)); this.bench.dispatchEvent(createBenchEvent('error', this)); } - - this.dispatchEvent(createBenchEvent("cycle", this)); - this.bench.dispatchEvent(createBenchEvent("cycle", this)); + this.dispatchEvent(createBenchEvent('cycle', this)); + this.bench.dispatchEvent(createBenchEvent('cycle', this)); // cycle and complete are equal in Task this.dispatchEvent(createBenchEvent('complete', this)); } @@ -140,13 +141,14 @@ export default class Task extends EventTarget { const startTime = this.bench.now(); let totalTime = 0; - this.bench.setup(this, "warmup"); + this.bench.setup(this, 'warmup'); while ( (totalTime < this.bench.warmupTime || this.runs < this.bench.warmupIterations) && !this.bench.signal?.aborted ) { try { + // eslint-disable-next-line no-await-in-loop await Promise.resolve().then(this.fn); } catch { // todo @@ -155,7 +157,7 @@ export default class Task extends EventTarget { this.runs += 1; totalTime = this.bench.now() - startTime; } - this.bench.teardown(this, "warmup"); + this.bench.teardown(this, 'warmup'); this.runs = 0; } diff --git a/src/utils.ts b/src/utils.ts index 24d4569..5f49ba2 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,22 +1,21 @@ +export const nanoToMs = (nano: number) => nano / 1e6; + export const now = () => { - // @ts-ignore - if (globalThis.process?.hrtime) { + if (typeof globalThis.process?.hrtime === 'function') { return nanoToMs(Number(process.hrtime.bigint())); } return performance.now(); }; -export const nanoToMs = (nano: number) => nano / 1e6; - /** * Computes the arithmetic mean of a sample. */ -export const getMean = (samples: number[]) => - samples.reduce((sum, n) => sum + n, 0) / samples.length || 0; +export const getMean = (samples: number[]) => samples.reduce((sum, n) => sum + n, 0) / samples.length || 0; /** * Computes the variance of a sample. */ -export const getVariance = (samples: number[], mean: number) => - samples.reduce((sum, n) => sum + Math.pow(n - mean, 2)) / - (samples.length - 1) || 0; +export const getVariance = (samples: number[], mean: number) => { + const result = samples.reduce((sum, n) => sum + ((n - mean) ** 2)); + return result / (samples.length - 1) || 0; +}; diff --git a/test/index.test.ts b/test/index.test.ts index a5cc9ae..36cc307 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,46 +1,46 @@ -import { test, expect, vi } from "vitest"; -import { Bench, Task } from "../src"; +import { test, expect, vi } from 'vitest'; +import { Bench, Task } from '../src'; -test("basic", async () => { +test('basic', async () => { const bench = new Bench({ time: 100 }); bench - .add("foo", async () => { - await new Promise((resolve) => setTimeout(resolve, 50)); + .add('foo', async () => { + await new Promise((resolve) => setTimeout((resolve), 50)); }) - .add("bar", async () => { + .add('bar', async () => { await new Promise((resolve) => setTimeout(resolve, 100)); }); await bench.run(); - const tasks = bench.tasks; + const tasks = bench.tasks as Task[]; expect(tasks.length).toEqual(2); - expect(tasks[0].name).toEqual("foo"); + expect(tasks[0].name).toEqual('foo'); expect(tasks[0].result.totalTime).toBeGreaterThan(50); - expect(tasks[1].name).toEqual("bar"); + expect(tasks[1].name).toEqual('bar'); expect(tasks[1].result.totalTime).toBeGreaterThan(100); expect(tasks[0].result.hz * tasks[0].result.period).toBeCloseTo(1); }); -test("runs should be equal-more than time and iterations", async () => { +test('runs should be equal-more than time and iterations', async () => { const bench = new Bench({ time: 100, iterations: 15 }); - bench.add("foo", async () => { + bench.add('foo', async () => { await new Promise((resolve) => setTimeout(resolve, 50)); }); await bench.run(); - const fooTask = bench.getTask("foo")!; + const fooTask = bench.getTask('foo')! as Task; expect(fooTask.runs).toBeGreaterThanOrEqual(bench.iterations); expect(fooTask.result.totalTime).toBeGreaterThanOrEqual(bench.time); }); -test("events order", async () => { +test('events order', async () => { const controller = new AbortController(); const bench = new Bench({ signal: controller.signal, @@ -48,73 +48,73 @@ test("events order", async () => { warmupTime: 0, }); bench - .add("foo", async () => {}) - .add("bar", async () => {}) - .add("error", async () => { - throw new Error("fake"); + .add('foo', async () => {}) + .add('bar', async () => {}) + .add('error', async () => { + throw new Error('fake'); }) - .add("abort", async () => { + .add('abort', async () => { await new Promise((resolve) => setTimeout(resolve, 1000)); }); const events: string[] = []; - const error = bench.getTask("error")!; + const error = bench.getTask('error')!; - error.addEventListener("start", () => { - events.push("error-start"); + error.addEventListener('start', () => { + events.push('error-start'); }); - error.addEventListener("error", () => { - events.push("error-error"); + error.addEventListener('error', () => { + events.push('error-error'); }); - error.addEventListener("cycle", () => { - events.push("error-cycle"); + error.addEventListener('cycle', () => { + events.push('error-cycle'); }); - error.addEventListener("complete", () => { - events.push("error-complete"); + error.addEventListener('complete', () => { + events.push('error-complete'); }); - bench.addEventListener("warmup", () => { - events.push("warmup"); + bench.addEventListener('warmup', () => { + events.push('warmup'); }); - bench.addEventListener("start", () => { - events.push("start"); + bench.addEventListener('start', () => { + events.push('start'); }); - bench.addEventListener("error", () => { - events.push("error"); + bench.addEventListener('error', () => { + events.push('error'); }); - bench.addEventListener("reset", () => { - events.push("reset"); + bench.addEventListener('reset', () => { + events.push('reset'); }); - bench.addEventListener("cycle", () => { - events.push("cycle"); + bench.addEventListener('cycle', () => { + events.push('cycle'); }); - bench.addEventListener("abort", () => { - events.push("abort"); + bench.addEventListener('abort', () => { + events.push('abort'); }); - bench.addEventListener("add", () => { - events.push("add"); + bench.addEventListener('add', () => { + events.push('add'); }); - bench.addEventListener("remove", () => { - events.push("remove"); + bench.addEventListener('remove', () => { + events.push('remove'); }); - bench.addEventListener("complete", () => { - events.push("complete"); + bench.addEventListener('complete', () => { + events.push('complete'); }); - bench.add("temporary", () => {}); - bench.remove("temporary"); + bench.add('temporary', () => {}); + bench.remove('temporary'); await bench.warmup(); @@ -126,38 +126,38 @@ test("events order", async () => { bench.reset(); expect(events).toEqual([ - "add", - "remove", - "warmup", - "start", - "error-start", - "cycle", - "cycle", - "error-error", - "error", - "error-cycle", - "cycle", - "error-complete", - "abort", - "complete", - "reset", + 'add', + 'remove', + 'warmup', + 'start', + 'error-start', + 'cycle', + 'cycle', + 'error-error', + 'error', + 'error-cycle', + 'cycle', + 'error-complete', + 'abort', + 'complete', + 'reset', ]); - const abortTask = bench.getTask("abort") as Task; + const abortTask = bench.getTask('abort') as Task; // aborted has no results expect(abortTask.result).toBeUndefined(); }, 10000); -test("error event", async () => { +test('error event', async () => { const bench = new Bench({ time: 50 }); const err = new Error(); - bench.add("error", () => { + bench.add('error', () => { throw err; }); let taskErr: Error; - bench.addEventListener("error", (e: IBenchEvent) => { + bench.addEventListener('error', (e: IBenchEvent) => { const task = e.task!; taskErr = task.result.error as Error; }); @@ -167,20 +167,20 @@ test("error event", async () => { expect(taskErr).toBe(err); }); -test("detect faster task", async () => { +test('detect faster task', async () => { const bench = new Bench({ time: 200 }); bench - .add("faster", async () => { + .add('faster', async () => { await new Promise((resolve) => setTimeout(resolve, 0)); }) - .add("slower", async () => { + .add('slower', async () => { await new Promise((resolve) => setTimeout(resolve, 50)); }); await bench.run(); - const fasterTask = bench.getTask("faster") as Task; - const slowerTask = bench.getTask("slower") as Task; + const fasterTask = bench.getTask('faster') as Task; + const slowerTask = bench.getTask('slower') as Task; expect(fasterTask.result!.mean).toBeLessThan(slowerTask.result!.mean); expect(fasterTask.result!.min).toBeLessThan(slowerTask.result!.min); @@ -190,38 +190,38 @@ test("detect faster task", async () => { expect(fasterTask.result!.moe).toBeLessThan(slowerTask.result!.moe); }); -test("statistics", async () => { +test('statistics', async () => { const bench = new Bench({ time: 200 }); - bench.add("foo", async () => { + bench.add('foo', async () => { await new Promise((resolve) => setTimeout(resolve, 0)); }); await bench.run(); - const fooTask = bench.getTask("foo") as Task; + const fooTask = bench.getTask('foo') as Task; - expect(fooTask.result!.variance).toBeTypeOf("number"); - expect(fooTask.result!.sd).toBeTypeOf("number"); - expect(fooTask.result!.sem).toBeTypeOf("number"); - expect(fooTask.result!.df).toBeTypeOf("number"); - expect(fooTask.result!.critical).toBeTypeOf("number"); - expect(fooTask.result!.moe).toBeTypeOf("number"); - expect(fooTask.result!.rme).toBeTypeOf("number"); + expect(fooTask.result!.variance).toBeTypeOf('number'); + expect(fooTask.result!.sd).toBeTypeOf('number'); + expect(fooTask.result!.sem).toBeTypeOf('number'); + expect(fooTask.result!.df).toBeTypeOf('number'); + expect(fooTask.result!.critical).toBeTypeOf('number'); + expect(fooTask.result!.moe).toBeTypeOf('number'); + expect(fooTask.result!.rme).toBeTypeOf('number'); }); -test("setup and teardown", async () => { +test('setup and teardown', async () => { const setup = vi.fn(); const teardown = vi.fn(); const bench = new Bench({ time: 200, setup, teardown }); - bench.add("foo", async () => { + bench.add('foo', async () => { await new Promise((resolve) => setTimeout(resolve, 0)); }); - const fooTask = bench.getTask("foo"); + const fooTask = bench.getTask('foo'); await bench.warmup(); await bench.run(); - expect(setup).toBeCalledWith(fooTask, "warmup"); - expect(setup).toBeCalledWith(fooTask, "run"); - expect(teardown).toBeCalledWith(fooTask, "warmup"); - expect(teardown).toBeCalledWith(fooTask, "run"); + expect(setup).toBeCalledWith(fooTask, 'warmup'); + expect(setup).toBeCalledWith(fooTask, 'run'); + expect(teardown).toBeCalledWith(fooTask, 'warmup'); + expect(teardown).toBeCalledWith(fooTask, 'run'); }); diff --git a/tsconfig.json b/tsconfig.json index 69d1527..a28df73 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,5 @@ { + "strict": true, "compilerOptions": { "target": "esnext", "strict": true, @@ -7,6 +8,8 @@ "noUncheckedIndexedAccess": true, "baseUrl": "." }, - "include": ["src/**/*", "tests/**/*", "@types/index.d.ts"], - "exclude": ["node_modules", "dist"] + "include": ["src", "test", "@types"], + "exclude": ["node_modules", "dist"], + "removeComments": true, + "newLine": "lf", }