Skip to content

Commit 04e391f

Browse files
authored
feat: automate benchmarking (#1035)
* feat: automate benchmark testing * group averages * add percentages to report.js * fix missing counter reset in bombingQoS1 * add README.md and examples * update bombingQoS1 * remove benchmark examples * improve benchmarking * put summary first
1 parent 16e1886 commit 04e391f

File tree

10 files changed

+538
-150
lines changed

10 files changed

+538
-150
lines changed

benchmarks/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Benchmarks
2+
3+
This folder contains a number of scripts to perform benchmark testing and
4+
reporting on it:
5+
6+
- sender.js publishes messages with a serialnumber as payload (add '-q 1' to get QoS1)
7+
- receiver.js subscribes and validates the serialnumber (add '-q 1' to get QoS1)
8+
- pingpong.js measures latency between sending and receiving the same
9+
message
10+
- server.js starts the Aedes server to use in the test
11+
- runBenchmarks.js starts the server and runs Publish/Subscribe tests
12+
with QoS0 and QoS1 using the scripts above it produces CSV data, this
13+
data includes the current git branch name.
14+
- report.js reads the CSV data from STDIN and turns it into a Markdown
15+
report.
16+
17+
Examples:
18+
19+
```bash
20+
node runBenchmark.js > result.csv
21+
cat result.csv | node report.csv > result.md
22+
```
23+
24+
```bash
25+
node runBenchmark.js > result.branch.csv
26+
cat result.main.csv result.branch.csv | node report.csv > result.combined.md
27+
```
28+
29+
## WARNING
30+
31+
Running benchmarks and especially interpreting results can be misleading
32+
E.g. performance of the benchmark run might depend on the presence (or absence)
33+
of other, unrelated, activity in the system.

benchmarks/bombing.js

Lines changed: 0 additions & 34 deletions
This file was deleted.

benchmarks/bombingQoS1.js

Lines changed: 0 additions & 36 deletions
This file was deleted.

benchmarks/pingpong.js

Lines changed: 94 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,116 @@
11
#! /usr/bin/env node
2-
2+
const { parseArgs } = require('node:util')
3+
const { hrtime } = require('node:process')
34
const mqtt = require('mqtt')
4-
const convertHrtime = require('convert-hrtime')
5-
const mode = require('compute-mode')
6-
const client = mqtt.connect({ port: 1883, host: 'localhost', clean: true, keepalive: 0 })
5+
76
const interval = 5000
87

9-
let sent = 0
10-
const latencies = []
8+
function processsLatencies (latencies, counter) {
9+
let total = 0
10+
let count = 0
11+
let median
12+
let perc95
13+
let perc99
14+
const posMedian = Math.floor(counter / 2)
15+
const pos95 = Math.floor(counter * 0.95)
16+
const pos99 = Math.floor(counter * 0.99)
17+
// sort keys from smallest to largest
18+
const keys = Object.keys(latencies).sort((a, b) => a - b)
19+
for (const key of keys) {
20+
const value = latencies[key]
21+
total += value * key
22+
count += value
23+
24+
if (count >= posMedian && median === undefined) {
25+
median = key
26+
}
27+
if (count >= pos95 && perc95 === undefined) {
28+
perc95 = key
29+
}
30+
if (count >= pos99 && perc99 === undefined) {
31+
perc99 = key
32+
}
33+
}
34+
return {
35+
buckets: keys.length,
36+
mean: Math.floor(total / counter),
37+
minimum: keys[0],
38+
maximum: keys.pop(),
39+
median,
40+
perc95,
41+
perc99,
42+
}
43+
}
44+
45+
let counter = 0
46+
let latencies = {}
47+
48+
const { values } = parseArgs({
49+
options: {
50+
qos: {
51+
type: 'string',
52+
default: '0',
53+
choices: ['0', '1'],
54+
description: 'QoS level to use for publishing messages',
55+
short: 'q'
56+
},
57+
help: {
58+
type: 'boolean',
59+
default: false,
60+
description: 'Show this help message',
61+
short: 'h'
62+
}
63+
}
64+
})
65+
66+
if (values.help) {
67+
console.log('Usage: node pingpong.js [options]')
68+
console.log('Options:')
69+
console.log(' -q, --qos <0|1> QoS level to use for publishing messages (default: 0)')
70+
console.log(' -h, --help Show this help message')
71+
process.exit(0)
72+
}
73+
74+
if (!process.send) {
75+
console.error(`Starting pingpong with options: qos=${values.qos}`)
76+
}
77+
const client = mqtt.connect({ port: 1883, host: 'localhost', clean: true, encoding: 'binary', keepalive: 0 })
78+
79+
const qosOpts = {
80+
qos: parseInt(values.qos, 10),
81+
}
1182

1283
function count () {
13-
console.log('sent/s', sent / interval * 1000)
14-
sent = 0
84+
// reset latencies while keeping the counts
85+
86+
const latencyResult = processsLatencies(latencies, counter)
87+
latencies = {}
88+
if (process.send) {
89+
process.send({ type: 'latency', data: latencyResult })
90+
} else {
91+
console.log('latencies', latencyResult)
92+
}
93+
counter = 0
1594
}
1695

1796
setInterval(count, interval)
1897

1998
function publish () {
20-
sent++
21-
client.publish('test', JSON.stringify(process.hrtime()), { qos: 1 })
99+
counter++
100+
client.publish('test', process.hrtime.bigint().toString(), qosOpts)
22101
}
23102

24103
function subscribe () {
25-
client.subscribe('test', { qos: 1 }, publish)
104+
client.subscribe('test', qosOpts, publish)
26105
}
27106

28107
client.on('connect', subscribe)
29108
client.on('message', publish)
30109
client.on('message', function (topic, payload) {
31-
const sentAt = JSON.parse(payload)
32-
const diff = process.hrtime(sentAt)
33-
latencies.push(convertHrtime(diff).ms)
110+
const receivedAt = hrtime.bigint()
111+
const sentAt = BigInt(payload.toString())
112+
const msDiff = Math.floor(Number(receivedAt - sentAt) / 1e6) // Convert from nanoseconds to milliseconds
113+
latencies[msDiff] = (latencies[msDiff] || 0) + 1
34114
})
35115

36116
client.on('offline', function () {
@@ -41,13 +121,3 @@ client.on('error', function () {
41121
console.log('reconnect!')
42122
client.stream.end()
43123
})
44-
45-
process.on('SIGINT', function () {
46-
const total = latencies.reduce(function (acc, num) {
47-
return acc + num
48-
})
49-
console.log('total', total)
50-
console.log('average', total / latencies.length)
51-
console.log('mode', mode(latencies))
52-
process.exit(0)
53-
})

benchmarks/receiver.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#! /usr/bin/env node
2+
const { parseArgs } = require('node:util')
3+
const mqtt = require('mqtt')
4+
5+
const interval = 5000
6+
7+
let counter = 0
8+
let previousSerial
9+
10+
const { values } = parseArgs({
11+
options: {
12+
qos: {
13+
type: 'string',
14+
default: '0',
15+
choices: ['0', '1'],
16+
description: 'QoS level to use for publishing messages',
17+
short: 'q'
18+
},
19+
help: {
20+
type: 'boolean',
21+
default: false,
22+
description: 'Show this help message',
23+
short: 'h'
24+
}
25+
}
26+
})
27+
28+
if (values.help) {
29+
console.log('Usage: node receiver.js [options]')
30+
console.log('Options:')
31+
console.log(' -q, --qos <0|1> QoS level to use for publishing messages (default: 0)')
32+
console.log(' -h, --help Show this help message')
33+
process.exit(0)
34+
}
35+
36+
const client = mqtt.connect({ port: 1883, host: 'localhost', clean: true, encoding: 'binary', keepalive: 0 })
37+
38+
if (!process.send) {
39+
console.error(`Starting receiver with options: qos=${values.qos}`)
40+
}
41+
const subscribeOpts = {
42+
qos: parseInt(values.qos, 10),
43+
}
44+
45+
function count () {
46+
if (process.send) {
47+
const rate = Math.floor(counter / interval * 1000)
48+
process.send({ type: 'rate', data: rate })
49+
} else {
50+
console.log('received/s', counter / interval * 1000)
51+
}
52+
counter = 0
53+
}
54+
55+
setInterval(count, interval)
56+
57+
client.on('connect', function () {
58+
this.subscribe('test', subscribeOpts)
59+
})
60+
61+
client.handleMessage = function (packet, done) {
62+
const serial = BigInt(packet.payload.toString())
63+
if (previousSerial !== undefined && (serial !== (previousSerial + 1n))) {
64+
console.error(`Received out of order message: expected ${previousSerial}, got ${serial}`)
65+
}
66+
previousSerial = serial
67+
counter++
68+
done()
69+
}
70+
71+
client.on('offline', function () {
72+
console.log('offline')
73+
})
74+
75+
client.on('error', function () {
76+
console.log('reconnect!')
77+
client.stream.end()
78+
})

0 commit comments

Comments
 (0)