Skip to content

Commit 839e423

Browse files
authored
test: Benchmark (#90)
1 parent a0b6f4d commit 839e423

File tree

13 files changed

+461
-0
lines changed

13 files changed

+461
-0
lines changed

.github/workflows/benchmark.yml

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
name: Benchmark
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
types: [opened, synchronize, reopened]
9+
branches:
10+
- main
11+
12+
env:
13+
K6_VERSION: v0.44.1
14+
PORT: 50000
15+
16+
jobs:
17+
http:
18+
name: http
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v3
23+
- name: Set up node
24+
uses: actions/setup-node@v3
25+
with:
26+
node-version: 18
27+
cache: yarn
28+
- name: Install
29+
run: yarn install --immutable
30+
- name: Download k6
31+
run: |
32+
curl https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
33+
- name: Build
34+
run: yarn run build:esm
35+
- name: Run
36+
run: node benchmark/servers/http.mjs &
37+
- name: Benchmark
38+
run: ./k6 run benchmark/k6.mjs --summary-export summary.json
39+
- name: Summary
40+
run: |
41+
echo '```json' >> $GITHUB_STEP_SUMMARY
42+
cat summary.json >> $GITHUB_STEP_SUMMARY
43+
echo -e '\n```' >> $GITHUB_STEP_SUMMARY
44+
45+
express:
46+
name: express
47+
runs-on: ubuntu-latest
48+
steps:
49+
- name: Checkout
50+
uses: actions/checkout@v3
51+
- name: Set up node
52+
uses: actions/setup-node@v3
53+
with:
54+
node-version: 18
55+
cache: yarn
56+
- name: Install
57+
run: yarn install --immutable
58+
- name: Download k6
59+
run: |
60+
curl https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
61+
- name: Build
62+
run: yarn run build:esm
63+
- name: Run
64+
run: node benchmark/servers/express.mjs &
65+
- name: Benchmark
66+
run: ./k6 run benchmark/k6.mjs --summary-export summary.json
67+
- name: Summary
68+
run: |
69+
echo '```json' >> $GITHUB_STEP_SUMMARY
70+
cat summary.json >> $GITHUB_STEP_SUMMARY
71+
echo -e '\n```' >> $GITHUB_STEP_SUMMARY
72+
73+
fastify:
74+
name: fastify
75+
runs-on: ubuntu-latest
76+
steps:
77+
- name: Checkout
78+
uses: actions/checkout@v3
79+
- name: Set up node
80+
uses: actions/setup-node@v3
81+
with:
82+
node-version: 18
83+
cache: yarn
84+
- name: Install
85+
run: yarn install --immutable
86+
- name: Download k6
87+
run: |
88+
curl https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
89+
- name: Build
90+
run: yarn run build:esm
91+
- name: Run
92+
run: node benchmark/servers/fastify.mjs &
93+
- name: Benchmark
94+
run: ./k6 run benchmark/k6.mjs --summary-export summary.json
95+
- name: Summary
96+
run: |
97+
echo '```json' >> $GITHUB_STEP_SUMMARY
98+
cat summary.json >> $GITHUB_STEP_SUMMARY
99+
echo -e '\n```' >> $GITHUB_STEP_SUMMARY
100+
101+
koa:
102+
name: koa
103+
runs-on: ubuntu-latest
104+
steps:
105+
- name: Checkout
106+
uses: actions/checkout@v3
107+
- name: Set up node
108+
uses: actions/setup-node@v3
109+
with:
110+
node-version: 18
111+
cache: yarn
112+
- name: Install
113+
run: yarn install --immutable
114+
- name: Download k6
115+
run: |
116+
curl https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
117+
- name: Build
118+
run: yarn run build:esm
119+
- name: Run
120+
run: node benchmark/servers/koa.mjs &
121+
- name: Benchmark
122+
run: ./k6 run benchmark/k6.mjs --summary-export summary.json
123+
- name: Summary
124+
run: |
125+
echo '```json' >> $GITHUB_STEP_SUMMARY
126+
cat summary.json >> $GITHUB_STEP_SUMMARY
127+
echo -e '\n```' >> $GITHUB_STEP_SUMMARY
128+
129+
uWebSockets:
130+
name: uWebSockets
131+
runs-on: ubuntu-latest
132+
steps:
133+
- name: Checkout
134+
uses: actions/checkout@v3
135+
- name: Set up node
136+
uses: actions/setup-node@v3
137+
with:
138+
node-version: 18
139+
cache: yarn
140+
- name: Install
141+
run: yarn install --immutable
142+
- name: Download k6
143+
run: |
144+
curl https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
145+
- name: Build
146+
run: yarn run build:esm
147+
- name: Run
148+
run: node benchmark/servers/uWebSockets.mjs &
149+
- name: Benchmark
150+
run: ./k6 run benchmark/k6.mjs --summary-export summary.json
151+
- name: Summary
152+
run: |
153+
echo '```json' >> $GITHUB_STEP_SUMMARY
154+
cat summary.json >> $GITHUB_STEP_SUMMARY
155+
echo -e '\n```' >> $GITHUB_STEP_SUMMARY
156+
157+
deno:
158+
name: deno
159+
runs-on: ubuntu-latest
160+
steps:
161+
- name: Checkout
162+
uses: actions/checkout@v3
163+
- name: Set up node
164+
uses: actions/setup-node@v3
165+
with:
166+
node-version: 18
167+
cache: yarn
168+
- name: Set up deno
169+
uses: denoland/setup-deno@v1
170+
with:
171+
deno-version: 1.x
172+
- name: Install
173+
run: yarn install --immutable
174+
- name: Download k6
175+
run: |
176+
curl https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
177+
- name: Build
178+
run: yarn run build:esm
179+
- name: Cache
180+
run: deno cache benchmark/servers/deno.ts
181+
- name: Run
182+
run: deno run -A benchmark/servers/deno.ts &
183+
- name: Benchmark
184+
run: ./k6 run benchmark/k6.mjs --summary-export summary.json
185+
- name: Summary
186+
run: |
187+
echo '```json' >> $GITHUB_STEP_SUMMARY
188+
cat summary.json >> $GITHUB_STEP_SUMMARY
189+
echo -e '\n```' >> $GITHUB_STEP_SUMMARY
190+
191+
bun:
192+
name: bun
193+
runs-on: ubuntu-latest
194+
steps:
195+
- name: Checkout
196+
uses: actions/checkout@v3
197+
- name: Set up node
198+
uses: actions/setup-node@v3
199+
with:
200+
node-version: 18
201+
cache: yarn
202+
- name: Set up bun
203+
uses: oven-sh/setup-bun@v1
204+
with:
205+
bun-version: latest
206+
- name: Install
207+
run: yarn install --immutable
208+
- name: Download k6
209+
run: |
210+
curl https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
211+
- name: Build
212+
run: yarn run build:esm
213+
- name: Run
214+
run: bun run benchmark/servers/bun.mjs &
215+
- name: Benchmark
216+
run: ./k6 run benchmark/k6.mjs --summary-export summary.json
217+
- name: Summary
218+
run: |
219+
echo '```json' >> $GITHUB_STEP_SUMMARY
220+
cat summary.json >> $GITHUB_STEP_SUMMARY
221+
echo -e '\n```' >> $GITHUB_STEP_SUMMARY

benchmark/k6.mjs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { check, fail } from 'k6';
2+
import { Counter, Trend } from 'k6/metrics';
3+
import http from 'k6/http';
4+
5+
const port = parseInt(__ENV.PORT || '');
6+
if (isNaN(port)) {
7+
throw new Error('Missing PORT environment variable!');
8+
}
9+
10+
/** @type {import("k6/options").Options} */
11+
export const options = {
12+
scenarios: {
13+
get: {
14+
executor: 'constant-vus',
15+
vus: 20,
16+
duration: '10s',
17+
gracefulStop: '3s',
18+
env: { SCENARIO: 'get' },
19+
},
20+
post: {
21+
executor: 'constant-vus',
22+
vus: 20,
23+
duration: '10s',
24+
gracefulStop: '3s',
25+
env: { SCENARIO: 'post' },
26+
},
27+
},
28+
};
29+
30+
/** @type {Record<string, { runs: Counter, oks: Counter, duration: Trend }>} */
31+
const scenarioMetrics = {};
32+
for (const scenario of Object.keys(options.scenarios || {})) {
33+
scenarioMetrics[scenario] = {
34+
runs: new Counter(`query_runs(${scenario})`),
35+
oks: new Counter(`query_oks(${scenario})`),
36+
duration: new Trend(`query_duration(${scenario})`, true),
37+
};
38+
}
39+
40+
export default function () {
41+
const scenario = __ENV.SCENARIO;
42+
const metrics = scenarioMetrics[scenario];
43+
if (!metrics) {
44+
fail(`unavailable metrics for scenario ${scenario}`);
45+
}
46+
47+
const begin = Date.now();
48+
metrics.runs.add(1);
49+
50+
/** @type {import("k6/http").RefinedResponse<'text'>} */
51+
let res;
52+
switch (scenario) {
53+
case 'get':
54+
res = http.get(`http://localhost:${port}/graphql?query={hello}`);
55+
break;
56+
case 'post':
57+
res = http.post(
58+
`http://localhost:${port}/graphql`,
59+
JSON.stringify({ query: '{hello}' }),
60+
{ headers: { 'Content-Type': 'application/json' } },
61+
);
62+
break;
63+
default:
64+
fail(`unexpected scenario ${scenario}`);
65+
}
66+
const ok = check(res, {
67+
'status is 200': (res) => res.status === 200,
68+
'headers contain application/json content-type': (res) =>
69+
String(res.headers['Content-Type']).includes('application/json'),
70+
'response is {"data":{"hello":"world"}}': (res) =>
71+
res.body === '{"data":{"hello":"world"}}',
72+
});
73+
74+
metrics.oks.add(ok ? 1 : 0);
75+
76+
metrics.duration.add(Date.now() - begin);
77+
}

benchmark/schema.mjs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {
2+
GraphQLSchema,
3+
GraphQLObjectType,
4+
GraphQLString,
5+
GraphQLNonNull,
6+
} from 'graphql';
7+
8+
export const schema = new GraphQLSchema({
9+
query: new GraphQLObjectType({
10+
name: 'Query',
11+
fields: {
12+
hello: {
13+
type: new GraphQLNonNull(GraphQLString),
14+
resolve: () => 'world',
15+
},
16+
},
17+
}),
18+
});

benchmark/servers/bun.mjs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { createHandler } from '../../lib/use/fetch.mjs';
2+
import { schema } from '../schema.mjs';
3+
4+
const port = parseInt(process.env.PORT || '');
5+
if (isNaN(port)) {
6+
throw new Error('Missing PORT environment variable!');
7+
}
8+
9+
const handler = createHandler({ schema });
10+
11+
export default {
12+
port,
13+
/** @param {Request} req */
14+
fetch(req) {
15+
const [path, _search] = req.url.split('?');
16+
if (path.endsWith('/graphql')) {
17+
return handler(req);
18+
} else {
19+
return new Response(null, { status: 404 });
20+
}
21+
},
22+
};
23+
24+
console.log(`Listening to port ${port}`);

benchmark/servers/deno.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// @ts-expect-error this is deno
2+
import { serve } from 'https://deno.land/[email protected]/http/server.ts';
3+
import { createHandler } from '../../lib/use/fetch.mjs';
4+
import { schema } from '../schema.mjs';
5+
6+
const port = parseInt(
7+
// @ts-expect-error this is deno
8+
Deno.env.get('PORT'),
9+
);
10+
if (isNaN(port)) {
11+
throw new Error('Missing PORT environment variable!');
12+
}
13+
14+
const handler = createHandler({ schema });
15+
16+
// @ts-expect-error this is deno
17+
await serve(
18+
(req: Request) => {
19+
const [path, _search] = req.url.split('?');
20+
if (path.endsWith('/graphql')) {
21+
return handler(req);
22+
} else {
23+
return new Response(null, { status: 404 });
24+
}
25+
},
26+
{
27+
port, // Listening to port ${port}
28+
},
29+
);

benchmark/servers/express.mjs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import express from 'express';
2+
import { createHandler } from '../../lib/use/express.mjs';
3+
import { schema } from '../schema.mjs';
4+
5+
const port = parseInt(process.env.PORT || '');
6+
if (isNaN(port)) {
7+
throw new Error('Missing PORT environment variable!');
8+
}
9+
10+
const app = express();
11+
app.all('/graphql', createHandler({ schema }));
12+
13+
app.listen({ port });
14+
15+
console.log(`Listening to port ${port}`);

0 commit comments

Comments
 (0)