-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(benchmark): add very basic benchmarking
Signed-off-by: david <[email protected]>
- Loading branch information
Showing
20 changed files
with
533 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"extension": [ | ||
"ts" | ||
], | ||
"spec": "**/*.test.ts", | ||
"require": [ | ||
"ts-node/register" | ||
], | ||
"recursive": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Benchmark Package | ||
General benchmarking library to return the time in milleseconds between start and stop. Includes stats package to get various analytics. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// .eslintrc.js in the new package | ||
module.exports = { | ||
root:true, | ||
extends: ['@repo/eslint-config/library.js'], | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
{ | ||
"name": "@repo/benchmark", | ||
"version": "0.0.1", | ||
"description": "", | ||
"main": "index.js", | ||
"scripts": { | ||
"build": "tsc -b", | ||
"build:check": "tsc --noEmit", | ||
"watch": "tsc -b --watch", | ||
"fix": "pnpm format && pnpm lint", | ||
"format": "prettier --write src", | ||
"format:check": "prettier src --check", | ||
"lint": "eslint --fix", | ||
"lint:check": "eslint", | ||
"check": "pnpm format:check && pnpm lint:check && pnpm build:check", | ||
"test": "mocha", | ||
"coverage": "nyc mocha", | ||
"test:watch": "mocha --watch" | ||
}, | ||
"keywords": [], | ||
"author": "", | ||
"license": "ISC", | ||
"dependencies": { | ||
}, | ||
"exports": { | ||
".": "./dist/index.js" | ||
}, | ||
"devDependencies": { | ||
"@istanbuljs/nyc-config-typescript": "^1.0.2", | ||
"@repo/eslint-config": "workspace:*", | ||
"@repo/typescript-config": "workspace:*", | ||
"@types/chai": "^4.3.17", | ||
"@types/mocha": "^10.0.7", | ||
"chai": "^4.5.0", | ||
"eslint": "^8.57.0", | ||
"mocha": "^10.7.0", | ||
"nyc": "^17.0.0", | ||
"prettier": "^3.3.3", | ||
"source-map-support": "^0.5.21", | ||
"ts-node": "^10.9.2", | ||
"typescript": "^5.5.4" | ||
} | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { expect } from "chai"; | ||
import { Benchmark } from "./benchmark"; | ||
import { BenchmarkStats } from "./stats"; | ||
|
||
describe("Benchmark", () => { | ||
let benchmark: Benchmark; | ||
|
||
beforeEach(() => { | ||
benchmark = new Benchmark(); | ||
}); | ||
|
||
it("should start and end a benchmark event correctly", async () => { | ||
benchmark.start("testEvent", 0); | ||
const duration = benchmark.end("testEvent", 1); | ||
expect(duration).to.be.a("number"); | ||
expect(duration).to.be.greaterThan(0); | ||
}); | ||
|
||
it("should throw an error if end is called without start", () => { | ||
expect(() => benchmark.end("nonExistentEvent")).to.throw( | ||
Error, | ||
'Benchmark for event "nonExistentEvent" not started. Call start() before end().', | ||
); | ||
}); | ||
|
||
it("should handle multiple events independently", () => { | ||
benchmark.start("event1", 0); | ||
benchmark.start("event2", 0); | ||
|
||
const duration1 = benchmark.end("event1", 1); | ||
expect(duration1).to.be.a("number"); | ||
expect(duration1).to.be.greaterThan(0); | ||
|
||
const duration2 = benchmark.end("event2", 1); | ||
expect(duration2).to.be.a("number"); | ||
expect(duration2).to.be.greaterThan(0); | ||
}); | ||
|
||
it("should throw an error if the same event is started twice without ending", () => { | ||
benchmark.start("duplicateEvent"); | ||
expect(() => benchmark.start("duplicateEvent")).to.not.throw(); | ||
expect(() => benchmark.end("duplicateEvent")).to.not.throw(); | ||
}); | ||
}); | ||
|
||
describe("BenchmarkStats", () => { | ||
let benchmarkStats: BenchmarkStats; | ||
|
||
beforeEach(() => { | ||
benchmarkStats = new BenchmarkStats(); | ||
}); | ||
|
||
it("should start and end a benchmark event correctly", () => { | ||
benchmarkStats.start("testEvent", 0); | ||
const duration = benchmarkStats.end("testEvent", 1); | ||
expect(duration).to.be.a("number"); | ||
expect(duration).to.be.greaterThan(0); | ||
}); | ||
|
||
it("should return correct stats for events", () => { | ||
benchmarkStats.start("event1"); | ||
benchmarkStats.end("event1"); | ||
benchmarkStats.start("event2"); | ||
benchmarkStats.end("event2"); | ||
|
||
const stats = benchmarkStats.getStats(); | ||
expect(stats.total).to.equal(2); | ||
expect(stats.oldest).to.be.a("number"); | ||
expect(stats.newest).to.be.a("number"); | ||
expect(stats.average).to.be.a("number"); | ||
expect(stats.fastest).to.be.a("number"); | ||
expect(stats.slowest).to.be.a("number"); | ||
}); | ||
|
||
it("should handle events with specific integer timestamps correctly", () => { | ||
const startTime1 = 1000; | ||
const endTime1 = 2000; | ||
const startTime2 = 3000; | ||
const endTime2 = 4000; | ||
|
||
benchmarkStats.start("event1", startTime1); | ||
benchmarkStats.end("event1", endTime1); | ||
benchmarkStats.start("event2", startTime2); | ||
benchmarkStats.end("event2", endTime2); | ||
|
||
const stats = benchmarkStats.getStats(); | ||
expect(stats.total).to.equal(2); | ||
expect(stats.oldest).to.equal(endTime1 - startTime1); | ||
expect(stats.newest).to.equal(endTime2 - startTime2); | ||
expect(stats.average).to.equal( | ||
(endTime1 - startTime1 + endTime2 - startTime2) / 2, | ||
); | ||
expect(stats.fastest).to.equal(endTime1 - startTime1); | ||
expect(stats.slowest).to.equal(endTime2 - startTime2); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/** | ||
* A class to benchmark events by tracking their start and end times. | ||
*/ | ||
import { IBenchmark } from "./types"; | ||
|
||
export class Benchmark implements IBenchmark { | ||
private events: Map<string, number>; | ||
|
||
/** | ||
* Initializes a new instance of the Benchmark class. | ||
*/ | ||
constructor() { | ||
this.events = new Map(); | ||
} | ||
|
||
/** | ||
* Starts tracking an event by storing its start time. | ||
* | ||
* @param {string} eventName - The name of the event to start tracking. | ||
* @param {number} [now=Date.now()] - The current time in milliseconds. Defaults to the current time. | ||
*/ | ||
start(eventName: string, now: number = Date.now()): void { | ||
this.events.set(eventName, now); | ||
} | ||
|
||
/** | ||
* Ends tracking an event and calculates its duration. | ||
* | ||
* @param {string} eventName - The name of the event to end tracking. | ||
* @param {number} [now=Date.now()] - The current time in milliseconds. Defaults to the current time. | ||
* @returns {number | undefined} The duration of the event in milliseconds, or undefined if the event was not started. | ||
* @throws Will throw an error if the event was not started before calling this method. | ||
*/ | ||
end(eventName: string, now: number = Date.now()): number | undefined { | ||
const startTime = this.events.get(eventName); | ||
if (startTime === undefined) { | ||
throw new Error( | ||
`Benchmark for event "${eventName}" not started. Call start() before end().`, | ||
); | ||
} | ||
const endTime = now; | ||
const duration = endTime - startTime; | ||
this.events.delete(eventName); | ||
return duration; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from "./benchmark"; | ||
export * from "./stats"; | ||
export * from "./types"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import { Benchmark } from "./benchmark"; | ||
|
||
import { IBenchmark } from "./types"; | ||
|
||
export class BenchmarkStats implements IBenchmark { | ||
private benchmark: Benchmark; | ||
private eventDurations: Map<string, number>; | ||
|
||
constructor(benchmark: Benchmark = new Benchmark()) { | ||
this.benchmark = benchmark; | ||
this.eventDurations = new Map(); | ||
} | ||
|
||
/** | ||
* Starts a new benchmark event. | ||
* @param {string} eventName - The name of the event to start. | ||
*/ | ||
start(eventName: string, now: number = Date.now()): void { | ||
this.benchmark.start(eventName, now); | ||
} | ||
|
||
/** | ||
* Ends a benchmark event and records its duration. | ||
* @param {string} eventName - The name of the event to stop. | ||
* @returns {number | undefined} The duration of the event in milliseconds, or undefined if the event was not started. | ||
*/ | ||
end(eventName: string, now: number = Date.now()): number | undefined { | ||
const duration = this.benchmark.end(eventName, now); | ||
if (duration !== undefined) { | ||
this.eventDurations.set(eventName, duration); | ||
} | ||
return duration; | ||
} | ||
|
||
/** | ||
* Provides statistics about the currently tracked events. | ||
* | ||
* @returns {object} An object containing statistics about the events. | ||
*/ | ||
getStats(): { | ||
total: number; | ||
oldest: number | null; | ||
newest: number | null; | ||
average: number | null; | ||
fastest: number | null; | ||
slowest: number | null; | ||
} { | ||
const total = this.eventDurations.size; | ||
|
||
if (total === 0) { | ||
return { | ||
total, | ||
oldest: null, | ||
newest: null, | ||
average: null, | ||
fastest: null, | ||
slowest: null, | ||
}; | ||
} | ||
|
||
let oldest = Number.MAX_VALUE; | ||
let newest = Number.MIN_VALUE; | ||
let totalDuration = 0; | ||
let fastest = Number.MAX_VALUE; | ||
let slowest = Number.MIN_VALUE; | ||
|
||
for (const duration of this.eventDurations.values()) { | ||
totalDuration += duration; | ||
if (duration < fastest) fastest = duration; | ||
if (duration > slowest) slowest = duration; | ||
if (duration < oldest) oldest = duration; | ||
if (duration > newest) newest = duration; | ||
} | ||
|
||
const average = totalDuration / total; | ||
|
||
return { | ||
total, | ||
oldest, | ||
newest, | ||
average, | ||
fastest, | ||
slowest, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
export interface IBenchmark { | ||
/** | ||
* Starts tracking an event by storing its start time. | ||
* | ||
* @param {string} eventName - The name of the event to start tracking. | ||
* @param {number} [now=Date.now()] - The current time in milliseconds. Defaults to the current time. | ||
*/ | ||
start(eventName: string, now?: number): void; | ||
|
||
/** | ||
* Ends tracking an event and calculates its duration. | ||
* | ||
* @param {string} eventName - The name of the event to end tracking. | ||
* @param {number} [now=Date.now()] - The current time in milliseconds. Defaults to the current time. | ||
* @returns {number | undefined} The duration of the event in milliseconds, or undefined if the event was not started. | ||
* @throws Will throw an error if the event was not started before calling this method. | ||
*/ | ||
end(eventName: string, now?: number): number | undefined; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"extends":"@repo/typescript-config/base.json", | ||
"compilerOptions": { | ||
"outDir": "./dist" /* Specify an output folder for all emitted files. */ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.