@durable-streams/benchmarks 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ //#region rolldown:runtime
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+
24
+ //#endregion
25
+ const node_fs = __toESM(require("node:fs"));
26
+ const vitest = __toESM(require("vitest"));
27
+ const __durable_streams_client = __toESM(require("@durable-streams/client"));
28
+
29
+ //#region src/index.ts
30
+ const results = new Map();
31
+ function recordResult(name, value, unit) {
32
+ if (!results.has(name)) results.set(name, {
33
+ values: [],
34
+ unit
35
+ });
36
+ results.get(name).values.push(value);
37
+ }
38
+ function calculateStats(values) {
39
+ const sorted = [...values].sort((a, b) => a - b);
40
+ const min = sorted[0];
41
+ const max = sorted[sorted.length - 1];
42
+ const mean = values.reduce((a, b) => a + b, 0) / values.length;
43
+ const p50 = sorted[Math.floor(sorted.length * .5)];
44
+ const p75 = sorted[Math.floor(sorted.length * .75)];
45
+ const p99 = sorted[Math.floor(sorted.length * .99)];
46
+ return {
47
+ min,
48
+ max,
49
+ mean,
50
+ p50,
51
+ p75,
52
+ p99
53
+ };
54
+ }
55
+ function printResults() {
56
+ console.log(`\n=== RESULTS SO FAR ===`);
57
+ const tableData = {};
58
+ for (const [name, data] of results.entries()) {
59
+ if (data.values.length === 0) continue;
60
+ const stats = calculateStats(data.values);
61
+ tableData[name] = {
62
+ Min: `${stats.min.toFixed(2)} ${data.unit}`,
63
+ Max: `${stats.max.toFixed(2)} ${data.unit}`,
64
+ Mean: `${stats.mean.toFixed(2)} ${data.unit}`,
65
+ P50: `${stats.p50.toFixed(2)} ${data.unit}`,
66
+ P75: `${stats.p75.toFixed(2)} ${data.unit}`,
67
+ P99: `${stats.p99.toFixed(2)} ${data.unit}`,
68
+ Iterations: data.values.length
69
+ };
70
+ }
71
+ console.table(tableData);
72
+ }
73
+ /**
74
+ * Run the full benchmark suite against a server
75
+ */
76
+ function runBenchmarks(options) {
77
+ const { baseUrl, environment = `unknown` } = options;
78
+ (0, vitest.afterAll)(() => {
79
+ const statsOutput = {};
80
+ for (const [name, data] of results.entries()) {
81
+ const stats = calculateStats(data.values);
82
+ statsOutput[name] = {
83
+ ...stats,
84
+ unit: data.unit,
85
+ iterations: data.values.length
86
+ };
87
+ }
88
+ const output = {
89
+ environment,
90
+ baseUrl,
91
+ timestamp: new Date().toISOString(),
92
+ results: statsOutput
93
+ };
94
+ (0, node_fs.writeFileSync)(`benchmark-results.json`, JSON.stringify(output, null, 2), `utf-8`);
95
+ console.log(`\n\n=== BENCHMARK RESULTS ===`);
96
+ console.log(`Environment: ${output.environment}`);
97
+ console.log(`Base URL: ${output.baseUrl}`);
98
+ console.log(``);
99
+ const finalTableData = {};
100
+ for (const [name, stats] of Object.entries(statsOutput)) finalTableData[name] = {
101
+ Min: `${stats.min.toFixed(2)} ${stats.unit}`,
102
+ Max: `${stats.max.toFixed(2)} ${stats.unit}`,
103
+ Mean: `${stats.mean.toFixed(2)} ${stats.unit}`,
104
+ P50: `${stats.p50.toFixed(2)} ${stats.unit}`,
105
+ P75: `${stats.p75.toFixed(2)} ${stats.unit}`,
106
+ P99: `${stats.p99.toFixed(2)} ${stats.unit}`,
107
+ Iterations: stats.iterations
108
+ };
109
+ console.table(finalTableData);
110
+ console.log(`\n\nResults saved to benchmark-results.json`);
111
+ });
112
+ (0, vitest.describe)(`Latency - Round-trip Time`, () => {
113
+ (0, vitest.bench)(`baseline ping (round-trip network latency)`, async () => {
114
+ const startTime = performance.now();
115
+ await fetch(`${baseUrl}/health`);
116
+ const endTime = performance.now();
117
+ const pingTime = endTime - startTime;
118
+ recordResult(`Baseline Ping`, pingTime, `ms`);
119
+ }, {
120
+ iterations: 5,
121
+ time: 5e3
122
+ });
123
+ (0, vitest.bench)(`append and receive via long-poll (100 bytes)`, async () => {
124
+ const streamPath = `/v1/stream/latency-bench-${Date.now()}-${Math.random()}`;
125
+ const stream = await __durable_streams_client.DurableStream.create({
126
+ url: `${baseUrl}${streamPath}`,
127
+ contentType: `application/octet-stream`
128
+ });
129
+ const message = new Uint8Array(100).fill(42);
130
+ let offset = (await stream.head()).offset;
131
+ const pingStart = performance.now();
132
+ await fetch(`${baseUrl}/health`);
133
+ const pingEnd = performance.now();
134
+ const pingTime = pingEnd - pingStart;
135
+ const warmupPromise = (async () => {
136
+ const res = await stream.stream({
137
+ offset,
138
+ live: `long-poll`
139
+ });
140
+ await new Promise((resolve) => {
141
+ const unsubscribe = res.subscribeBytes(async (chunk) => {
142
+ if (chunk.data.length > 0) {
143
+ offset = chunk.offset;
144
+ unsubscribe();
145
+ res.cancel();
146
+ resolve();
147
+ }
148
+ });
149
+ });
150
+ })();
151
+ await stream.append(message);
152
+ await warmupPromise;
153
+ const readPromise = (async () => {
154
+ const res = await stream.stream({
155
+ offset,
156
+ live: `long-poll`
157
+ });
158
+ await new Promise((resolve) => {
159
+ const unsubscribe = res.subscribeBytes(async (chunk) => {
160
+ if (chunk.data.length > 0) {
161
+ unsubscribe();
162
+ res.cancel();
163
+ resolve();
164
+ }
165
+ });
166
+ });
167
+ })();
168
+ const startTime = performance.now();
169
+ await stream.append(message);
170
+ await readPromise;
171
+ const endTime = performance.now();
172
+ await stream.delete();
173
+ const totalLatency = endTime - startTime;
174
+ const overhead = totalLatency - pingTime;
175
+ recordResult(`Latency - Total RTT`, totalLatency, `ms`);
176
+ recordResult(`Latency - Ping`, pingTime, `ms`);
177
+ recordResult(`Latency - Overhead`, overhead, `ms`);
178
+ }, {
179
+ iterations: 10,
180
+ time: 15e3
181
+ });
182
+ (0, vitest.afterAll)(() => {
183
+ printResults();
184
+ });
185
+ });
186
+ (0, vitest.describe)(`Message Throughput`, () => {
187
+ (0, vitest.bench)(`small messages (100 bytes)`, async () => {
188
+ const streamPath = `/v1/stream/msg-small-${Date.now()}-${Math.random()}`;
189
+ const stream = await __durable_streams_client.DurableStream.create({
190
+ url: `${baseUrl}${streamPath}`,
191
+ contentType: `application/octet-stream`
192
+ });
193
+ const message = new Uint8Array(100).fill(42);
194
+ const messageCount = 1e3;
195
+ const concurrency = 75;
196
+ const startTime = performance.now();
197
+ for (let batch = 0; batch < messageCount / concurrency; batch++) await Promise.all(Array.from({ length: concurrency }, () => stream.append(message)));
198
+ const endTime = performance.now();
199
+ const elapsedSeconds = (endTime - startTime) / 1e3;
200
+ const messagesPerSecond = messageCount / elapsedSeconds;
201
+ await stream.delete();
202
+ recordResult(`Throughput - Small Messages`, messagesPerSecond, `msg/sec`);
203
+ }, {
204
+ iterations: 3,
205
+ time: 1e4
206
+ });
207
+ (0, vitest.bench)(`large messages (1MB)`, async () => {
208
+ const streamPath = `/v1/stream/msg-large-${Date.now()}-${Math.random()}`;
209
+ const stream = await __durable_streams_client.DurableStream.create({
210
+ url: `${baseUrl}${streamPath}`,
211
+ contentType: `application/octet-stream`
212
+ });
213
+ const message = new Uint8Array(1024 * 1024).fill(42);
214
+ const messageCount = 50;
215
+ const concurrency = 15;
216
+ const startTime = performance.now();
217
+ for (let batch = 0; batch < messageCount / concurrency; batch++) await Promise.all(Array.from({ length: concurrency }, () => stream.append(message)));
218
+ const endTime = performance.now();
219
+ const elapsedSeconds = (endTime - startTime) / 1e3;
220
+ const messagesPerSecond = messageCount / elapsedSeconds;
221
+ await stream.delete();
222
+ recordResult(`Throughput - Large Messages`, messagesPerSecond, `msg/sec`);
223
+ }, {
224
+ iterations: 2,
225
+ time: 1e4
226
+ });
227
+ (0, vitest.afterAll)(() => {
228
+ printResults();
229
+ });
230
+ });
231
+ vitest.describe.skip(`Byte Throughput`, () => {
232
+ (0, vitest.bench)(`streaming throughput - appendStream`, async () => {
233
+ const streamPath = `/v1/stream/byte-stream-${Date.now()}-${Math.random()}`;
234
+ const stream = await __durable_streams_client.DurableStream.create({
235
+ url: `${baseUrl}${streamPath}`,
236
+ contentType: `application/octet-stream`
237
+ });
238
+ const chunkSize = 64 * 1024;
239
+ const chunk = new Uint8Array(chunkSize).fill(42);
240
+ const totalChunks = 100;
241
+ const startTime = performance.now();
242
+ const appends = [];
243
+ for (let i = 0; i < totalChunks; i++) appends.push(stream.append(chunk));
244
+ await Promise.all(appends);
245
+ const endTime = performance.now();
246
+ let bytesRead = 0;
247
+ const readRes = await stream.stream({ live: false });
248
+ const reader = readRes.bodyStream().getReader();
249
+ let result = await reader.read();
250
+ while (!result.done) {
251
+ bytesRead += result.value.length;
252
+ result = await reader.read();
253
+ }
254
+ const elapsedSeconds = (endTime - startTime) / 1e3;
255
+ const mbPerSecond = bytesRead / (1024 * 1024) / elapsedSeconds;
256
+ await stream.delete();
257
+ recordResult(`Throughput - Streaming (appendStream)`, mbPerSecond, `MB/sec`);
258
+ }, {
259
+ iterations: 3,
260
+ time: 1e4
261
+ });
262
+ (0, vitest.afterAll)(() => {
263
+ printResults();
264
+ });
265
+ });
266
+ }
267
+
268
+ //#endregion
269
+ exports.runBenchmarks = runBenchmarks
@@ -0,0 +1,12 @@
1
+ //#region src/index.d.ts
2
+ interface BenchmarkOptions {
3
+ /** Base URL of the server to benchmark */
4
+ baseUrl: string;
5
+ /** Environment name (e.g., "production", "local") */
6
+ environment?: string;
7
+ }
8
+ /**
9
+ * Run the full benchmark suite against a server
10
+ */
11
+ declare function runBenchmarks(options: BenchmarkOptions): void; //#endregion
12
+ export { BenchmarkOptions, runBenchmarks };
package/package.json CHANGED
@@ -1,48 +1,57 @@
1
1
  {
2
2
  "name": "@durable-streams/benchmarks",
3
- "version": "0.1.1",
4
3
  "description": "Performance benchmark suite for Durable Streams server implementations",
4
+ "version": "0.1.2",
5
5
  "author": "Durable Stream contributors",
6
- "license": "Apache-2.0",
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/durable-streams/durable-streams.git",
10
- "directory": "packages/benchmarks"
11
- },
12
6
  "bugs": {
13
7
  "url": "https://github.com/durable-streams/durable-streams/issues"
14
8
  },
15
- "keywords": [
16
- "durable-streams",
17
- "benchmarks",
18
- "performance",
19
- "typescript"
20
- ],
21
- "type": "module",
22
- "main": "./dist/index.js",
23
- "types": "./dist/index.d.ts",
24
- "exports": {
25
- ".": {
26
- "import": "./dist/index.js",
27
- "types": "./dist/index.d.ts"
28
- }
29
- },
30
9
  "dependencies": {
31
10
  "vitest": "^3.2.4",
32
- "@durable-streams/client": "0.1.1"
11
+ "@durable-streams/client": "0.1.2"
33
12
  },
34
13
  "devDependencies": {
35
14
  "@types/node": "^22.15.21",
36
15
  "tsdown": "^0.9.0",
37
16
  "typescript": "^5.0.0"
38
17
  },
18
+ "engines": {
19
+ "node": ">=18.0.0"
20
+ },
21
+ "exports": {
22
+ ".": {
23
+ "import": {
24
+ "types": "./dist/index.d.ts",
25
+ "default": "./dist/index.js"
26
+ },
27
+ "require": {
28
+ "types": "./dist/index.d.cts",
29
+ "default": "./dist/index.cjs"
30
+ }
31
+ },
32
+ "./package.json": "./package.json"
33
+ },
39
34
  "files": [
40
35
  "dist",
41
36
  "src"
42
37
  ],
43
- "engines": {
44
- "node": ">=18.0.0"
38
+ "keywords": [
39
+ "benchmarks",
40
+ "durable-streams",
41
+ "performance",
42
+ "typescript"
43
+ ],
44
+ "license": "Apache-2.0",
45
+ "main": "./dist/index.cjs",
46
+ "module": "./dist/index.js",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/durable-streams/durable-streams.git",
50
+ "directory": "packages/benchmarks"
45
51
  },
52
+ "sideEffects": false,
53
+ "type": "module",
54
+ "types": "./dist/index.d.ts",
46
55
  "scripts": {
47
56
  "build": "tsdown",
48
57
  "dev": "tsdown --watch",