Skip to content

Commit 7686758

Browse files
committed
chore(3.0): simplify and split and create helpers functions for benchmarking
1 parent 1c68174 commit 7686758

File tree

4 files changed

+189
-157
lines changed

4 files changed

+189
-157
lines changed

benchmarks/README.md

+28-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,36 @@
11
# BENCHMARKS
22

3-
- Tested on Nodejs 22.10.0
3+
- Benchmark basic
4+
```bash
5+
node ./benchmarks/basic.js
6+
```
7+
8+
Tested on Nodejs 22.10.0
49
```bash
510
┌─────────┬──────────────────────┬───────────┐
611
│ (index) │ library │ ops/sec │
712
├─────────┼──────────────────────┼───────────┤
8-
│ 0 │ 'Pino''124,828'
9-
│ 1 │ '@ekino/logger v3.x''118,385'
10-
│ 2 │ '@ekino/logger v2.x''104,004'
11-
│ 3 │ 'Winston''67,536'
13+
│ 0 │ 'Pino''188,005'
14+
│ 1 │ '@ekino/logger v3.x''162,575'
15+
│ 2 │ '@ekino/logger v2.x''143,041'
16+
│ 3 │ 'Winston''96,834'
1217
└─────────┴──────────────────────┴───────────┘
18+
```
19+
20+
- Benchmark with complex object
21+
22+
```bash
23+
node ./benchmarks/complex.js
24+
```
25+
26+
Tested on Nodejs 22.10.0
27+
```bash
28+
┌─────────┬──────────────────────┬──────────┐
29+
│ (index) │ library │ ops/sec │
30+
├─────────┼──────────────────────┼──────────┤
31+
│ 0 │ 'Pino''26,506'
32+
│ 1 │ '@ekino/logger v3.x''21,915'
33+
│ 2 │ '@ekino/logger v2.x''18,826'
34+
│ 3 │ 'Winston''4,251'
35+
└─────────┴──────────────────────┴──────────┘
1336
```

benchmarks/basic.js

+28-64
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,10 @@
11
#!/usr/bin/env node
22

3-
import * as logger2 from '@ekino/logger'
4-
import { Suite } from '@jonahsnider/benchmark'
5-
import pino from 'pino'
6-
import * as winston from 'winston'
7-
import * as logger3 from '../lib/esm/index.js'
3+
import { createBenchmarkSuite, initializeLoggers, runSuiteWithDynamicTimeout } from './utils.js'
84

9-
const suite = new Suite('Logger Benchmark', {
10-
warmup: { trials: 3000 }, // Run benchmark for 3000ms
11-
run: { trials: 10_000 }, // Run 1000 warmup trials
12-
})
5+
const { ekinoLoggerV2, ekinoLoggerV3, pinoLogger, winstonLogger } = initializeLoggers()
136

14-
// Initialize loggers
15-
logger2.setLevel('info')
16-
logger3.setLevel('info')
17-
18-
const ekinoLoggerV2 = logger2.createLogger('benchmark')
19-
const ekinoLoggerV3 = logger3.createLogger('benchmark')
20-
const pinoLogger = pino({
21-
level: 'info',
22-
prettyPrint: false,
23-
destination: pino.destination({ sync: true }), // Synced JSON output
24-
})
25-
const winstonLogger = winston.createLogger({
26-
level: 'info',
27-
format: winston.format.json(),
28-
transports: [new winston.transports.Console()],
29-
})
30-
31-
const message = 'This is a benchmark log message'
7+
const suite = createBenchmarkSuite('Logger Benchmark - Basic')
328
const logObject = {
339
user: 'John Doe',
3410
action: 'Test Action',
@@ -42,42 +18,30 @@ const logObject = {
4218
}
4319
const contextId = 'a037df3b-dbee-448e-9abb-8024d867ccc8'
4420

45-
async function runBenchmarks() {
46-
suite
47-
.addTest('@ekino/logger v2.x', () => {
48-
ekinoLoggerV2.info(contextId, message, logObject)
49-
})
50-
.addTest('@ekino/logger v3.x', () => {
51-
ekinoLoggerV3.info(contextId, message, logObject)
52-
})
53-
.addTest('Winston', () => {
54-
winstonLogger.info(message, logObject)
55-
})
56-
.addTest('Pino', () => {
57-
pinoLogger.info({
58-
ctxId: contextId,
59-
...logObject,
60-
message,
61-
})
21+
suite
22+
.addTest('@ekino/logger v2.x', () => {
23+
ekinoLoggerV2.info(
24+
contextId,
25+
'This is a benchmark log message for @ekino/logger v2.x',
26+
logObject
27+
)
28+
})
29+
.addTest('@ekino/logger v3.x', () => {
30+
ekinoLoggerV3.info(
31+
contextId,
32+
'This is a benchmark log message for @ekino/logger v3.x',
33+
logObject
34+
)
35+
})
36+
.addTest('Winston', () => {
37+
winstonLogger.info('This is a benchmark log message for Winston', logObject)
38+
})
39+
.addTest('Pino', () => {
40+
pinoLogger.info({
41+
ctxId: contextId,
42+
...logObject,
43+
message: 'This is a benchmark log message for Pino',
6244
})
45+
})
6346

64-
const results = await suite.run()
65-
return results
66-
}
67-
68-
const results = await runBenchmarks()
69-
70-
const table = [...results]
71-
.map(([library, histogram]) => [
72-
library,
73-
Math.round(1e9 / histogram.percentile(50)), // Median to ops/sec
74-
])
75-
.sort(([, a], [, b]) => b - a)
76-
.map(([library, opsPerSec]) => ({
77-
library,
78-
'ops/sec': opsPerSec.toLocaleString(),
79-
}))
80-
81-
setTimeout(() => {
82-
console.table(table)
83-
}, 5000)
47+
await runSuiteWithDynamicTimeout(suite)

benchmarks/complex.js

+36-88
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,41 @@
11
#!/usr/bin/env node
22

3-
import * as logger2 from '@ekino/logger'
4-
import { Suite } from '@jonahsnider/benchmark'
5-
import pino from 'pino'
6-
import * as winston from 'winston'
7-
import * as logger3 from '../lib/esm/index.js'
8-
9-
const suite = new Suite('Logger Benchmark', {
10-
warmup: { trials: 3000 }, // Run benchmark for 3000ms
11-
run: { trials: 10_000 }, // Run 1000 warmup trials
12-
})
13-
14-
// Initialize loggers
15-
logger2.setLevel('info')
16-
logger3.setLevel('info')
17-
18-
const ekinoLoggerV2 = logger2.createLogger('benchmark')
19-
const ekinoLoggerV3 = logger3.createLogger('benchmark')
20-
const pinoLogger = pino({
21-
level: 'info',
22-
prettyPrint: false,
23-
destination: pino.destination({ sync: true }), // Synced JSON output
24-
})
25-
const winstonLogger = winston.createLogger({
26-
level: 'info',
27-
format: winston.format.json(),
28-
transports: [new winston.transports.Console()],
29-
})
30-
31-
const generateNestedObject = (numKeys, depth, currentDepth = 1) => {
32-
if (currentDepth > depth) return null
33-
34-
const obj = {}
35-
for (let i = 0; i < numKeys; i++) {
36-
const key = `key_${currentDepth}_${i}`
37-
if (currentDepth === depth) {
38-
obj[key] = {
39-
stringValue: `string_${currentDepth}_${i}`,
40-
numberValue: i * 10,
41-
boolValue: i % 2 === 0,
42-
arrayValue: Array.from({ length: 3 }, (_, j) => `arrayItem_${j}`),
43-
}
44-
} else {
45-
obj[key] = generateNestedObject(numKeys, depth, currentDepth + 1)
46-
}
47-
}
48-
return obj
49-
}
50-
51-
const message = 'This is a benchmark log message'
3+
import {
4+
createBenchmarkSuite,
5+
generateNestedObject,
6+
initializeLoggers,
7+
runSuiteWithDynamicTimeout,
8+
} from './utils.js'
9+
10+
const { ekinoLoggerV2, ekinoLoggerV3, pinoLogger, winstonLogger } = initializeLoggers()
11+
const suite = createBenchmarkSuite('Logger Benchmark - Complex')
5212
const contextId = 'a037df3b-dbee-448e-9abb-8024d867ccc8'
53-
const logObject = generateNestedObject(5, 2)
54-
55-
async function runBenchmarks() {
56-
suite
57-
.addTest('@ekino/logger v2.x', () => {
58-
ekinoLoggerV2.info(contextId, message, logObject)
13+
const logObject = generateNestedObject(10, 2)
14+
15+
suite
16+
.addTest('@ekino/logger v2.x', () => {
17+
ekinoLoggerV2.info(
18+
contextId,
19+
'This is a benchmark log message for @ekino/logger v2.x',
20+
logObject
21+
)
22+
})
23+
.addTest('@ekino/logger v3.x', () => {
24+
ekinoLoggerV3.info(
25+
contextId,
26+
'This is a benchmark log message for @ekino/logger v3.x',
27+
logObject
28+
)
29+
})
30+
.addTest('Winston', () => {
31+
winstonLogger.info('This is a benchmark log message for Winston', logObject)
32+
})
33+
.addTest('Pino', () => {
34+
pinoLogger.info({
35+
ctxId: contextId,
36+
...logObject,
37+
message: 'This is a benchmark log message for Pino',
5938
})
60-
.addTest('@ekino/logger v3.x', () => {
61-
ekinoLoggerV3.info(contextId, message, logObject)
62-
})
63-
.addTest('Winston', () => {
64-
winstonLogger.info(message, logObject)
65-
})
66-
.addTest('Pino', () => {
67-
pinoLogger.info({
68-
ctxId: contextId,
69-
...logObject,
70-
message,
71-
})
72-
})
73-
74-
const results = await suite.run()
75-
return results
76-
}
77-
78-
const results = await runBenchmarks()
79-
80-
const table = [...results]
81-
.map(([library, histogram]) => [
82-
library,
83-
Math.round(1e9 / histogram.percentile(50)), // Median to ops/sec
84-
])
85-
.sort(([, a], [, b]) => b - a)
86-
.map(([library, opsPerSec]) => ({
87-
library,
88-
'ops/sec': opsPerSec.toLocaleString(),
89-
}))
39+
})
9040

91-
setTimeout(() => {
92-
console.table(table)
93-
}, 25000)
41+
await runSuiteWithDynamicTimeout(suite, 40000)

benchmarks/utils.js

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import * as logger2 from '@ekino/logger'
2+
import { Suite } from '@jonahsnider/benchmark'
3+
import pino from 'pino'
4+
import * as winston from 'winston'
5+
import * as logger3 from '../lib/esm/index.js'
6+
7+
/**
8+
* Initializes loggers for benchmarking.
9+
*/
10+
export const initializeLoggers = () => {
11+
logger2.setLevel('info')
12+
logger3.setLevel('info')
13+
14+
return {
15+
ekinoLoggerV2: logger2.createLogger('benchmark'),
16+
ekinoLoggerV3: logger3.createLogger('benchmark'),
17+
pinoLogger: pino({
18+
level: 'info',
19+
prettyPrint: false,
20+
destination: pino.destination({ sync: true }),
21+
}),
22+
winstonLogger: winston.createLogger({
23+
level: 'info',
24+
format: winston.format.json(),
25+
transports: [new winston.transports.Console()],
26+
}),
27+
}
28+
}
29+
30+
/**
31+
* Sets up the benchmark suite.
32+
* @param {string} name Name of the benchmark suite
33+
*/
34+
export const createBenchmarkSuite = (name) => {
35+
return new Suite(name, {
36+
warmup: { trials: 3000 },
37+
run: { trials: 10_000 },
38+
})
39+
}
40+
41+
/**
42+
* Generates a nested object for complex benchmarks.
43+
* @param {number} numKeys Number of keys in each object
44+
* @param {number} depth Depth of the nested object
45+
* @param {number} [currentDepth=1] Current depth of the object
46+
*/
47+
export const generateNestedObject = (numKeys, depth, currentDepth = 1) => {
48+
if (currentDepth > depth) return null
49+
50+
const obj = {}
51+
for (let i = 0; i < numKeys; i++) {
52+
const key = `key_${currentDepth}_${i}`
53+
obj[key] =
54+
currentDepth === depth
55+
? {
56+
stringValue: `string_${currentDepth}_${i}`,
57+
numberValue: i * 10,
58+
boolValue: i % 2 === 0,
59+
arrayValue: Array.from({ length: 3 }, (_, j) => `arrayItem_${j}`),
60+
}
61+
: generateNestedObject(numKeys, depth, currentDepth + 1)
62+
}
63+
return obj
64+
}
65+
66+
/**
67+
* Formats benchmark results into a console table.
68+
* @param {Array<[string, import('@ekino/logger').Histogram]>} results
69+
*/
70+
export const formatResultsTable = (results) => {
71+
return [...results]
72+
.map(([library, histogram]) => [library, Math.round(1e9 / histogram.percentile(50))])
73+
.sort(([, a], [, b]) => b - a)
74+
.map(([library, opsPerSec]) => ({
75+
library,
76+
'ops/sec': opsPerSec.toLocaleString(),
77+
}))
78+
}
79+
80+
/**
81+
* Runs the benchmark suite and displays results after a dynamic timeout.
82+
* @param {Suite} suite Benchmark suite instance
83+
* @param {number} timeout Timeout value in milliseconds
84+
*/
85+
export const runSuiteWithDynamicTimeout = async (suite, timeout = 1000) => {
86+
const startTime = performance.now()
87+
88+
const results = await suite.run()
89+
90+
const endTime = performance.now()
91+
const executionTime = endTime - startTime
92+
const timeoutValue = executionTime + timeout
93+
94+
const table = formatResultsTable(results)
95+
96+
setTimeout(() => console.table(table), timeoutValue)
97+
}

0 commit comments

Comments
 (0)