-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
π chore(.eslintignore): ignore README.md file in eslint checks
π docs(README.md): add documentation for radix-rapid package and its usage π chore(benchmark): add benchmarking scripts and results for radix-rapid package The first commit adds an ignore rule for README.md file in eslint checks. The second commit adds documentation for the radix-rapid package and its usage. The third commit adds benchmarking scripts and results for the radix-rapid package. π feat(http.mjs, utils.mjs): add benchmarking functionality to test server performance This commit adds benchmarking functionality to the server to test its performance. The benchmarking script uses autocannon to send requests to the server and measure its performance. The benchmarking script is located in `http.mjs`, and the utility functions used by the script are located in `utils.mjs`. The `createServer` function creates a server instance and returns a listener and stats object. The `printEnv` function prints the environment details, and the `printStats` function prints the statistics of the benchmark. The `router` object is used to create routes for the server, and `benchSets` is an array of objects that contain the title of the benchmark and the requests to be sent to the server.
- Loading branch information
nyxb
committed
May 19, 2023
1 parent
251c8ef
commit 96f7d94
Showing
6 changed files
with
390 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
dist | ||
node_modules | ||
README.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
[![cover][cover-src]][cover-href] | ||
[![npm version][npm-version-src]][npm-version-href] | ||
[![npm downloads][npm-downloads-src]][npm-downloads-href] | ||
[![bundle][bundle-src]][bundle-href] [![JSDocs][jsdocs-src]][jsdocs-href] | ||
[![License][license-src]][license-href] | ||
|
||
# π³ radix-rapid | ||
|
||
> β¨ Lightweight and fast router for JavaScript based on [Radix Tree](https://en.wikipedia.org/wiki/Radix_tree)π±. | ||
## π Usage | ||
|
||
**Install:** | ||
|
||
```sh | ||
# nyxi | ||
nyxi radix-rapid | ||
|
||
# pnpm | ||
pnpm i radix-rapid | ||
|
||
# npm | ||
npm i radix-rapid | ||
|
||
# yarn | ||
yarn add radix-rapid | ||
``` | ||
|
||
**Import:** | ||
|
||
```js | ||
// ESM | ||
import { createRouter } from 'radix-rapid' | ||
|
||
// CJS | ||
const { createRouter } = require('radix-rapid') | ||
``` | ||
|
||
**Create a router instance and insert routes:** | ||
|
||
```js | ||
const router = createRouter(/* options */) | ||
|
||
router.insert('/path', { payload: 'this path' }) | ||
router.insert('/path/:name', { payload: 'named route' }) | ||
router.insert('/path/foo/**', { payload: 'wildcard route' }) | ||
router.insert('/path/foo/**:name', { payload: 'named wildcard route' }) | ||
``` | ||
|
||
**Match route to access matched data:** | ||
|
||
```js | ||
router.lookup('/path') | ||
// { payload: 'this path' } | ||
|
||
router.lookup('/path/fooval') | ||
// { payload: 'named route', params: { name: 'fooval' } } | ||
|
||
router.lookup('/path/foo/bar/baz') | ||
// { payload: 'wildcard route' } | ||
|
||
router.lookup('/') | ||
// null (no route matched for/) | ||
``` | ||
|
||
## β‘οΈ Methods | ||
|
||
### β `router.insert(path, data)` | ||
|
||
`path` can be static or using `:placeholder` or `**` for wildcard paths. | ||
|
||
The `data` object will be returned on matching params. It should be an object like `{ handler }` and not containing reserved keyword `params`. | ||
|
||
### π `router.lookup(path)` | ||
|
||
Returns matched data for `path` with optional `params` key if mached route using placeholders. | ||
|
||
### β `router.remove(path)` | ||
|
||
Remove route matching `path`. | ||
|
||
## βοΈ Options | ||
|
||
You can initialize router instance with options: | ||
|
||
```ts | ||
const router = createRouter({ | ||
strictTrailingSlash: true, | ||
routes: { | ||
'/foo': {} | ||
} | ||
}) | ||
``` | ||
|
||
- π£οΈ `routes`: An object specifying initial routes to add | ||
- π¦ `strictTrailingSlash`: By default, the router ignores trailing slashes for matching and adding routes. When set to `true`, matching with trailing slashes is handled differently. | ||
|
||
### π Route Matcher | ||
|
||
Creates a multi matcher from router tree that can match **all routes** matching path: | ||
|
||
```ts | ||
import { createRouter, toRouteMatcher } from 'radix-rapid' | ||
|
||
const router = createRouter({ | ||
routes: { | ||
'/foo': { m: 'foo' }, // Matches /foo only | ||
'/foo/**': { m: 'foo/**' }, // Matches /foo/<any> | ||
'/foo/bar': { m: 'foo/bar' }, // Matches /foo/bar only | ||
'/foo/bar/baz': { m: 'foo/bar/baz' }, // Matches /foo/bar/baz only | ||
'/foo/*/baz': { m: 'foo/*/baz' } // Matches /foo/<any>/baz | ||
} | ||
}) | ||
|
||
const matcher = toRouteMatcher(router) | ||
|
||
const matches = matcher.matchAll('/foo/bar/baz') | ||
|
||
// [ | ||
// { | ||
// "m": "foo/**", | ||
// }, | ||
// { | ||
// "m": "foo/*/baz", | ||
// }, | ||
// { | ||
// "m": "foo/bar/baz", | ||
// }, | ||
// ] | ||
``` | ||
|
||
## β‘οΈ Performance | ||
|
||
See [benchmark](./benchmark). | ||
|
||
|
||
## π License | ||
|
||
[MIT](./LICENSE) - Made with π | ||
|
||
<!-- Badges --> | ||
|
||
[npm-version-src]: https://img.shields.io/npm/v/radix-rapid?style=flat&colorA=18181B&colorB=14F195 | ||
[npm-version-href]: https://npmjs.com/package/radix-rapid | ||
[npm-downloads-src]: https://img.shields.io/npm/dm/radix-rapid?style=flat&colorA=18181B&colorB=14F195 | ||
[npm-downloads-href]: https://npmjs.com/package/radix-rapid | ||
[bundle-src]: https://img.shields.io/bundlephobia/minzip/radix-rapid?style=flat&colorA=18181B&colorB=14F195 | ||
[bundle-href]: https://bundlephobia.com/result?p=radix-rapid | ||
[jsdocs-src]: https://img.shields.io/badge/jsDocs.io-reference-18181B?style=flat&colorA=18181B&colorB=14F195 | ||
[jsdocs-href]: https://www.jsdocs.io/package/radix-rapid | ||
[license-src]: https://img.shields.io/github/license/nyxblabs/radix-rapid.svg?style=flat&colorA=18181B&colorB=14F195 | ||
[license-href]: https://github.com/nyxblabs/radix-rapid/blob/main/LICENSE | ||
|
||
<!-- Cover --> | ||
[cover-src]: https://raw.githubusercontent.com/nyxblabs/radix-rapid/main/.github/assets/cover-github-radix-rapid.png | ||
[cover-href]: https://π»nyxb.ws |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
# π Benchmark Results | ||
|
||
Benchmarks are mainly focusing on benchmarking `lookup` method performance. | ||
|
||
Below results are based on my personal PC using Windows 11. You can use provided scripts to test in your own env. | ||
|
||
## β‘οΈ Direct benchmark | ||
|
||
Directly benchmarking `lookup` performance using [benchmark](https://www.npmjs.com/package/benchmark) | ||
|
||
Scripts: | ||
- ποΈββοΈ `nyxr bench` | ||
- ποΈββοΈ `nyxr bench:profile` (using [0x](https://www.npmjs.com/package/0x) to generate flamegraph) | ||
|
||
|
||
``` | ||
--- π§ͺ Test environment --- | ||
Node.js version: 18.16.0 | ||
radix-rapid version: 0.0.1 | ||
OS: win32 | ||
CPU count: 8 | ||
Current load: [ 0, 0, 0 ] | ||
--- π§ static route --- | ||
lookup x 18,670,265 ops/sec Β±4.69% (76 runs sampled) | ||
Stats: | ||
- /choot: 96837315 | ||
--- π§ dynamic route --- | ||
lookup x 403,374 ops/sec Β±3.18% (63 runs sampled) | ||
Stats: | ||
- /choot/123: 2065943 | ||
``` | ||
|
||
## β‘οΈ HTTP Benchmark | ||
|
||
|
||
Using [`autocannon`](https://github.com/mcollina/autocannon) and a simple http listener using lookup for realworld performance. | ||
|
||
Scripts: | ||
- π `nyxr bench:http` | ||
|
||
``` | ||
--- π§ͺ Test environment --- | ||
Node.js version: 18.16.0 | ||
radix-rapid version: 0.0.1 | ||
OS: win32 | ||
CPU count: 8 | ||
Current load: [ 0, 0, 0 ] | ||
--- π Benchmark: static route --- | ||
Running 10s test @ http://localhost:3000/ | ||
10 connections | ||
βββββββββββ¬βββββββ¬βββββββ¬ββββββββ¬βββββββ¬ββββββββββ¬ββββββββββ¬ββββββββ | ||
β Stat β 2.5% β 50% β 97.5% β 99% β Avg β Stdev β Max β | ||
βββββββββββΌβββββββΌβββββββΌββββββββΌβββββββΌββββββββββΌββββββββββΌββββββββ€ | ||
β Latency β 0 ms β 0 ms β 1 ms β 2 ms β 0.13 ms β 0.65 ms β 30 ms β | ||
βββββββββββ΄βββββββ΄βββββββ΄ββββββββ΄βββββββ΄ββββββββββ΄ββββββββββ΄ββββββββ | ||
βββββββββββββ¬ββββββββββ¬ββββββββββ¬ββββββββββ¬ββββββββββ¬ββββββββββ¬ββββββββββ¬ββββββββββ | ||
β Stat β 1% β 2.5% β 50% β 97.5% β Avg β Stdev β Min β | ||
βββββββββββββΌββββββββββΌββββββββββΌββββββββββΌββββββββββΌββββββββββΌββββββββββΌββββββββββ€ | ||
β Req/Sec β 9663 β 9663 β 17183 β 21935 β 15848.8 β 4391.92 β 9660 β | ||
βββββββββββββΌββββββββββΌββββββββββΌββββββββββΌββββββββββΌββββββββββΌββββββββββΌββββββββββ€ | ||
β Bytes/Sec β 1.35 MB β 1.35 MB β 2.41 MB β 3.07 MB β 2.22 MB β 615 kB β 1.35 MB β | ||
βββββββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββββ | ||
Req/Bytes counts sampled once per second. | ||
# of samples: 10 | ||
159k requests in 10.02s, 22.2 MB read | ||
Stats: | ||
- /choot: 158510 | ||
--- π Benchmark: dynamic route --- | ||
Running 10s test @ http://localhost:3000/ | ||
10 connections | ||
βββββββββββ¬βββββββ¬βββββββ¬ββββββββ¬βββββββ¬ββββββββββ¬ββββββββββ¬ββββββββ | ||
β Stat β 2.5% β 50% β 97.5% β 99% β Avg β Stdev β Max β | ||
βββββββββββΌβββββββΌβββββββΌββββββββΌβββββββΌββββββββββΌββββββββββΌββββββββ€ | ||
β Latency β 0 ms β 0 ms β 1 ms β 2 ms β 0.14 ms β 0.56 ms β 17 ms β | ||
βββββββββββ΄βββββββ΄βββββββ΄ββββββββ΄βββββββ΄ββββββββββ΄ββββββββββ΄ββββββββ | ||
βββββββββββββ¬ββββββββββ¬ββββββββββ¬ββββββββββ¬ββββββββ¬ββββββββββ¬ββββββββββ¬ββββββββββ | ||
β Stat β 1% β 2.5% β 50% β 97.5% β Avg β Stdev β Min β | ||
βββββββββββββΌββββββββββΌββββββββββΌββββββββββΌββββββββΌββββββββββΌββββββββββΌββββββββββ€ | ||
β Req/Sec β 9663 β 9663 β 14935 β 17631 β 14243.6 β 2791.28 β 9660 β | ||
βββββββββββββΌββββββββββΌββββββββββΌββββββββββΌββββββββΌββββββββββΌββββββββββΌββββββββββ€ | ||
β Bytes/Sec β 1.64 MB β 1.64 MB β 2.54 MB β 3 MB β 2.42 MB β 475 kB β 1.64 MB β | ||
βββββββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββββ | ||
Req/Bytes counts sampled once per second. | ||
# of samples: 10 | ||
142k requests in 10.01s, 24.2 MB read | ||
Stats: | ||
- /choot/123: 142410 | ||
``` | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* eslint-disable no-console */ | ||
import Benchmark from 'benchmark' // https://www.npmjs.com/package/benchmark' | ||
import { benchSets, logSection, printEnv, printStats, router } from './utils.mjs' | ||
|
||
async function main() { | ||
printEnv() | ||
|
||
for (const bench of benchSets) { | ||
logSection(bench.title) | ||
const suite = new Benchmark.Suite() | ||
const stats = {} | ||
suite.add('lookup', () => { | ||
for (const req of bench.requests) { | ||
const match = router.lookup(req.path) | ||
if (!match) | ||
stats[match] = (stats[match] || 0) + 1 | ||
stats[req.path] = (stats[req.path] || 0) + 1 | ||
} | ||
}) | ||
// eslint-disable-next-line max-statements-per-line | ||
suite.on('cycle', (event) => { console.log(String(event.target)) }) | ||
const promise = new Promise(resolve => suite.on('complete', () => resolve())) | ||
suite.run({ async: true }) | ||
await promise | ||
printStats(stats) | ||
} | ||
} | ||
|
||
main().catch(console.error) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import autocannon from 'autocannon' // https://github.com/mcollina/autocannon | ||
import { listen } from 'earlist' | ||
import { benchSets, logSection, printEnv, printStats, router } from './utils.mjs' | ||
|
||
async function main() { | ||
printEnv() | ||
|
||
for (const bench of benchSets) { | ||
logSection(`Benchmark: ${bench.title}`) | ||
const { listener, stats } = await createServer() | ||
const instance = autocannon({ | ||
url: listener.url, | ||
requests: bench.requests, | ||
}) | ||
autocannon.track(instance) | ||
process.once('SIGINT', () => { | ||
instance.stop() | ||
listener.close() | ||
process.exit(1) | ||
}) | ||
await instance // Resolves to details results | ||
printStats(stats) | ||
await listener.close() | ||
} | ||
} | ||
|
||
main().catch(console.error) | ||
|
||
async function createServer() { | ||
const stats = {} | ||
const listener = await listen((req, res) => { | ||
stats[req.url] = (stats[req.url] || 0) + 1 | ||
const match = router.lookup(req.url) | ||
if (!match) | ||
stats[match] = (stats[match] || 0) + 1 | ||
|
||
res.end(JSON.stringify((match || { error: 404 }))) | ||
}, { showURL: false }) | ||
|
||
return { listener, stats } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* eslint-disable no-console */ | ||
import { readFileSync } from 'node:fs' | ||
import os from 'node:os' | ||
import { createRouter } from 'radix-rapid' | ||
|
||
// eslint-disable-next-line max-statements-per-line | ||
export function logSection(title) { console.log(`\n--- ${title} ---\n`) } | ||
|
||
const pkgVersion = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version | ||
|
||
export function printEnv() { | ||
logSection('Test environment') | ||
console.log('Node.js version:', process.versions.node) | ||
console.log('radix-rapid version:', pkgVersion) | ||
console.log('OS:', os.platform()) | ||
console.log('CPU count:', os.cpus().length) | ||
console.log('Current load:', os.loadavg()) | ||
console.log('') | ||
} | ||
|
||
export function printStats(stats) { | ||
console.log(`Stats:\n${Object.entries(stats).map(([path, hits]) => ` - ${path}: ${hits}`).join('\n')}`) | ||
} | ||
|
||
export const router = createRouter({ | ||
routes: Object.fromEntries([ | ||
'/hello', | ||
'/cool', | ||
'/hi', | ||
'/helium', | ||
'/coooool', | ||
'/chrome', | ||
'/choot', | ||
'/choot/:choo', | ||
'/ui/**', | ||
'/ui/components/**', | ||
].map(path => [path, { path }])), | ||
}) | ||
|
||
export const benchSets = [ | ||
{ | ||
title: 'static route', | ||
requests: [ | ||
{ path: '/choot' }, | ||
], | ||
}, | ||
{ | ||
title: 'dynamic route', | ||
requests: [ | ||
{ path: '/choot/123' }, | ||
], | ||
}, | ||
] |