Skip to content

Commit

Permalink
feat(benchmark): add very basic benchmarking
Browse files Browse the repository at this point in the history
Signed-off-by: david <[email protected]>
  • Loading branch information
daywiss committed Dec 3, 2024
1 parent fdfe6d3 commit 0c524f5
Show file tree
Hide file tree
Showing 21 changed files with 524 additions and 27 deletions.
10 changes: 10 additions & 0 deletions packages/benchmark/.mocharc.json
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
}
2 changes: 2 additions & 0 deletions packages/benchmark/README.md
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.
6 changes: 6 additions & 0 deletions packages/benchmark/eslint.config.js
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'],
};

43 changes: 43 additions & 0 deletions packages/benchmark/package.json
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.
96 changes: 96 additions & 0 deletions packages/benchmark/src/benchmark.test.ts
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);
});
});
46 changes: 46 additions & 0 deletions packages/benchmark/src/benchmark.ts
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;
}
}
3 changes: 3 additions & 0 deletions packages/benchmark/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./benchmark";
export * from "./stats";
export * from "./types";
86 changes: 86 additions & 0 deletions packages/benchmark/src/stats.ts
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,
};
}
}
19 changes: 19 additions & 0 deletions packages/benchmark/src/types.ts
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;
}
6 changes: 6 additions & 0 deletions packages/benchmark/tsconfig.json
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. */
}
}
1 change: 1 addition & 0 deletions packages/indexer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@across-protocol/constants": "^3.1.20",
"@across-protocol/contracts": "^3.0.16",
"@across-protocol/sdk": "^3.3.23",
"@repo/benchmark": "workspace:*",
"@repo/error-handling": "workspace:*",
"@repo/webhooks": "workspace:*",
"@types/express": "^4.17.21",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import { RedisCache } from "../../redis/redisCache";
import { RetryProvidersFactory } from "../../web3/RetryProvidersFactory";
import { SpokePoolRepository } from "../../database/SpokePoolRepository";
import { IndexerQueuesService } from "../../messaging/service";
import { SpokePoolProcessor } from "../../services/spokePoolProcessor";
import {
SpokePoolProcessor,
SpokePoolEventParams,
} from "../../services/spokePoolProcessor";

import { HubPoolIndexerDataHandler } from "./HubPoolIndexerDataHandler";
import { SpokePoolIndexerDataHandler } from "./SpokePoolIndexerDataHandler";
Expand Down Expand Up @@ -86,7 +89,9 @@ export class AcrossIndexerManager {
return this.hubPoolIndexer.start();
}

private async startSpokePoolIndexers() {
private async startSpokePoolIndexers(
callback?: (chainId: number, events: SpokePoolEventParams) => void,
) {
const spokePoolIndexers = this.config.spokePoolChainsEnabled.map(
(chainId) => {
const spokePoolIndexerDataHandler = new SpokePoolIndexerDataHandler(
Expand Down
Loading

0 comments on commit 0c524f5

Please sign in to comment.