Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions benchmark/scenarios/api-endpoint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict';

// The suite had no scenario shaped like an actual API endpoint: `grep -r "res.json\|req.params\|req.query"`
// over benchmark/scenarios returned nothing. That is the most common request shape in an Express
// application, and the one where the framework's own work is a meaningful share of the request
// rather than a rounding error next to zlib or JSON.parse.
module.exports = {
name: 'routing/api-endpoint',
path: '/api/users/42/posts?fields=id,title,author&limit=10',
setup(app, express) {
const apiRouter = express.Router();

apiRouter.get('/users/:userId/posts', (req, res) => {
const fields = String(req.query.fields || '').split(',');
const limit = Number(req.query.limit) || 0;
const items = [];
for (let i = 0; i < limit; i++) {
items.push({ id: i, title: `post ${i}`, author: req.params.userId });
}

res.json({
userId: req.params.userId,
fields,
count: items.length,
items
});
});

app.use('/api', apiRouter);
}
};
29 changes: 29 additions & 0 deletions benchmark/scenarios/body-json-4kb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';

// body-json-512kb is a stress case: at half a megabyte JSON.parse dominates and the framework is
// about 1% of the request, so that row cannot move. A few KB is what an API actually receives, and
// at that size the body plumbing around the parse is visible. Both rows are kept - one measures the
// parser, this one measures getting the bytes to it.
const PAD = 4 * 1024;

module.exports = {
name: 'middlewares/body-json-4kb',
path: '/abc',
wrk: {
script: 'post-json-4kb.lua',
connections: 200
},
verify: {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ n: 1, pad: 'x'.repeat(PAD) })
},
setup(app, express) {
app.use(express.json());
app.post('/abc', (req, res) => {
res.send(`${req.body.pad.length}`);
});
}
};
20 changes: 20 additions & 0 deletions benchmark/scenarios/high-concurrency.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';

// Every other scenario runs at 50-200 connections, where connection handling is free. uWS's
// advantage over node:http at high connection counts is structural - per-connection memory and
// accept handling - rather than anything in the request path, and nothing in the suite exercised it.
// The handler is deliberately trivial so the row measures connections and not work.
//
// Caveat: wrk shares the runner with the server, so at this connection count part of what is
// measured is the generator. Two wrk threads on a 4 vCPU runner leaves the server two.
module.exports = {
name: 'connections/high-concurrency',
path: '/ping',
wrk: {
threads: 2,
connections: 1000
},
setup(app) {
app.get('/ping', (req, res) => res.send('pong'));
}
};
43 changes: 43 additions & 0 deletions benchmark/scenarios/realistic-stack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict';

const helmet = require('helmet');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const morgan = require('morgan');

// middlewares-100 measures 100 no-ops, which no application runs. This is the stack a typical
// Express service actually mounts, at the depth it actually mounts it.
// morgan writes to a sink rather than stdout so the row measures the formatting work every request
// pays, not the runner's terminal. 'combined' is used because that is the production default, and
// it includes the remote address, which both frameworks have to resolve their own way.
module.exports = {
name: 'middlewares/realistic-stack',
path: '/profile',
wrk: {
script: 'realistic-stack.lua',
connections: 200
},
// keep these in sync with realistic-stack.lua: the verify request and the load request are
// separate definitions, so cors would otherwise be exercised only by one of them
verify: {
method: 'GET',
headers: {
'Cookie': 'sid=abc123; theme=dark',
'Origin': 'https://example.com'
}
},
setup(app, express) {
app.use(helmet());
app.use(cors());
app.use(cookieParser());
app.use(express.json());
app.use(morgan('combined', { stream: { write: () => {} } }));

app.get('/profile', (req, res) => {
res.json({
sid: req.cookies.sid || null,
theme: req.cookies.theme || null
});
});
}
};
7 changes: 7 additions & 0 deletions benchmark/wrk-scripts/post-json-4kb.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
wrk.method = "POST"
wrk.path = "/abc"
wrk.headers["Content-Type"] = "application/json"

local kb = 1024
local pad = string.rep("x", 4 * kb)
wrk.body = '{"n":1,"pad":"' .. pad .. '"}'
4 changes: 4 additions & 0 deletions benchmark/wrk-scripts/realistic-stack.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
wrk.method = "GET"
wrk.path = "/profile"
wrk.headers["Cookie"] = "sid=abc123; theme=dark"
wrk.headers["Origin"] = "https://example.com"
Loading