Compare commits

...

2 commits

Author SHA1 Message Date
ee0326c9f4 remove all the nonsense benchmark stuff 2024-11-10 00:26:27 +00:00
566c59ec95 more benchmark nonsense 2024-11-10 00:24:14 +00:00
8 changed files with 60 additions and 2674 deletions

View file

@ -1,9 +1,10 @@
import { summary, run, bench } from 'mitata'; import { summary, run, bench } from 'mitata';
import { createRouter, insertItem } from '@mapl/router' import { createRouter, insertItem } from '@mapl/router'
import { compileRouter } from './mapl_compiler.mjs' import { compileRouter } from './mapl_compiler.mjs'
import assert from 'assert'
import { compilePaths } from "./router_v2.mjs" import { compilePaths } from "./router_v2.mjs"
import { compilePaths as mainCompiler, compilePathsClosure } from "../router_v2.mjs" import { compilePaths as mainCompiler } from "../router_v2.mjs"
import { FlaskaRouter as FlaskaRouterBuffer } from "../flaska_buffer.mjs"
import { FlaskaRouter as FlaskaRouterFast } from "../flaska_fast.mjs"
import * as consts from './const.js' import * as consts from './const.js'
function printCurrentStatus(fn) { function printCurrentStatus(fn) {
@ -36,13 +37,21 @@ function printStatusHelperText() {
bench('noop', () => { }); bench('noop', () => { });
bench('noop2', () => { }); bench('noop2', () => { });
let paths = consts.allManyRoutes.map(x => ({ path: x })) let paths = consts.allRoutes.map(x => ({ path: x }))
let tests = paths.map(p => ([p.path.replace(/:[^/]+/g, '_'), p])) let tests = paths.map(p => ([p.path.replace(/:[^/]+/g, '_'), p]))
let testStrings = tests.map(x => x[0]) let testStrings = tests.map(x => x[0])
let testStringsMapl = testStrings.map(x => x.slice(1)) let testStringsMapl = testStrings.map(x => x.slice(1))
let func = [[testStrings, mainCompiler(paths)]]
let func = [[testStrings, ...mainCompiler(paths)]] let flaskaRouterBuffer = new FlaskaRouterBuffer()
func.push([testStrings, ...compilePathsClosure(paths)]) flaskaRouterBuffer.paths = paths.slice()
flaskaRouterBuffer.compile()
func.push([testStrings, flaskaRouterBuffer.match])
let flaskaRouterFast = new FlaskaRouterFast()
flaskaRouterFast.paths = paths.slice()
flaskaRouterFast.compile()
func.push([testStrings, flaskaRouterFast.match])
let maplPaths = paths.map(x => x.path.replace(/::[^\/]+/g, '**').replace(/:[^\/]+/g, '*')) let maplPaths = paths.map(x => x.path.replace(/::[^\/]+/g, '**').replace(/:[^\/]+/g, '*'))
const maplRouter = createRouter(); const maplRouter = createRouter();
@ -51,15 +60,15 @@ for (let route of maplPaths) {
} }
let maplMatcher = compileRouter(maplRouter) let maplMatcher = compileRouter(maplRouter)
func.push([testStringsMapl, maplMatcher, maplMatcher]) func.push([testStringsMapl, maplMatcher])
for (let [tests, _, fun] of func) { for (let [tests, fun] of func) {
console.log(`--- warming up ${fun.name || 'mapl'} ---`) console.log(`--- warming up ${fun.name || 'mapl'} ---`)
for (var i = 0; i < 10000; i++) { for (var i = 0; i < 100000; i++) {
tests.forEach(fun) tests.forEach(fun)
} }
} }
for (let [tests, _, fun] of func) { for (let [tests, fun] of func) {
console.log(`--- Sanity checking ${fun.name || 'mapl'} ---`) console.log(`--- Sanity checking ${fun.name || 'mapl'} ---`)
for (let a of tests) { for (let a of tests) {
// console.log(a, fun(a)) // console.log(a, fun(a))
@ -73,9 +82,9 @@ for (let [_, org] of func) {
printStatusHelperText() printStatusHelperText()
summary(() => { summary(() => {
func.forEach(function([tests, _, fun]) { func.forEach(function([tests, fun]) {
// console.log(tests, fun, tests.map(fun)) // console.log(tests, fun, tests.map(fun))
bench((fun.name || 'bound mapl').slice(6), function() { bench(fun.name || 'mapl', function() {
return tests.map(fun) return tests.map(fun)
}) })
}) })

View file

@ -63,8 +63,6 @@ const statuses = {
304: true 304: true
} }
} }
const __paramMapName = '__param'
const __fullParamMapName = '__fullparam'
function assertIsHandler(handler, name) { function assertIsHandler(handler, name) {
if (typeof(handler) !== 'function') { if (typeof(handler) !== 'function') {
@ -428,7 +426,6 @@ const regCleanNonAschii = /(?![a-zA-Z_])./g
const regCleanRest = /_+/g const regCleanRest = /_+/g
const regStarDoubleParam = /::[^:/]+/g const regStarDoubleParam = /::[^:/]+/g
const regStarSingleParam = /:[^:/]+/g const regStarSingleParam = /:[^:/]+/g
const SlashCode = '/'.charCodeAt(0)
const spaces = ' ' const spaces = ' '
export class FlaskaRouter { export class FlaskaRouter {
@ -535,6 +532,7 @@ export class FlaskaRouter {
let staticPaths = new Map() let staticPaths = new Map()
let paramsPaths = [] let paramsPaths = []
let collator = new Intl.Collator('en', { sensitivity: 'accent' }); let collator = new Intl.Collator('en', { sensitivity: 'accent' });
let sealed = Object.seal({})
paths.forEach(function(entry) { paths.forEach(function(entry) {
if (entry.path[0] !== '/') throw new RouterError(entry, null, 'Specified route was missing forward slash at start') if (entry.path[0] !== '/') throw new RouterError(entry, null, 'Specified route was missing forward slash at start')
@ -543,7 +541,7 @@ export class FlaskaRouter {
if (entry.path.indexOf('/:') < 0 && separateStatic) { if (entry.path.indexOf('/:') < 0 && separateStatic) {
return staticPaths.set(entry.path, { return staticPaths.set(entry.path, {
path: entry, path: entry,
params: {} params: sealed,
}) })
} }
@ -597,13 +595,15 @@ export class FlaskaRouter {
: '') : '')
} }
__treeIntoCompiledCodeReturnPath(indentString, paths, branch, params) { __treeIntoCompiledCodeReturnPath(indentString, paths, branch, params, pathParamMapIndex) {
let pathIndex = paths.indexOf(branch.path) let pathIndex = paths.indexOf(branch.path)
if (pathIndex < 0) { if (pathIndex < 0) {
throw new RouterError(branch.path, null, 'InternalError: Specified path was not found in paths') throw new RouterError(branch.path, null, 'InternalError: Specified path was not found in paths')
} }
let mapIndex = pathParamMapIndex.size + 1
pathParamMapIndex.set(mapIndex, pathIndex)
let output = '\n' + indentString + `return {` let output = '\n' + indentString + `return {`
output += '\n' + indentString + ` path: paths[${pathIndex}],` output += '\n' + indentString + ` path: paths_${mapIndex},`
if (params.length) { if (params.length) {
output += '\n' + indentString + ` params: {` output += '\n' + indentString + ` params: {`
for (let param of params) { for (let param of params) {
@ -611,13 +611,13 @@ export class FlaskaRouter {
} }
output += '\n' + indentString + ` },` output += '\n' + indentString + ` },`
} else { } else {
output += '\n' + indentString + ` params: {},` output += '\n' + indentString + ` {},`
} }
output += '\n' + indentString + `}` output += '\n' + indentString + `}`
return output return output
} }
__treeIntoCompiledCodeBranch(paths, branches, indent = 0, params = []) { __treeIntoCompiledCodeBranch(paths, branches, indent = 0, params = [], pathParamMapIndex) {
let output = '' let output = ''
let indentation = spaces.slice(0, (indent - params.length) * 2) let indentation = spaces.slice(0, (indent - params.length) * 2)
let addEndBracket = true let addEndBracket = true
@ -637,8 +637,8 @@ export class FlaskaRouter {
output += `if (str.charCodeAt(${this.__getIndex(indent, 0, params)}) === ${branch.char.charCodeAt(0)}) { // ${branch.char}` output += `if (str.charCodeAt(${this.__getIndex(indent, 0, params)}) === ${branch.char.charCodeAt(0)}) { // ${branch.char}`
if (branch.path) { if (branch.path) {
output += '\n' + indentation + ` if (str.length === ${this.__getIndex(indent, 1, params)}) {` output += '\n' + indentation + ` if (strLength === ${this.__getIndex(indent, 1, params)}) {`
output += this.__treeIntoCompiledCodeReturnPath(indentation + ' ', paths, branch, params) output += this.__treeIntoCompiledCodeReturnPath(indentation + ' ', paths, branch, params, pathParamMapIndex)
output += '\n' + indentation + ` }` output += '\n' + indentation + ` }`
} }
} else { } else {
@ -650,10 +650,10 @@ export class FlaskaRouter {
params.push([branch.isParams || branch.isFullParams, paramVarName]) params.push([branch.isParams || branch.isFullParams, paramVarName])
if (branch.isFullParams) { if (branch.isFullParams) {
output += this.__treeIntoCompiledCodeReturnPath(indentation, paths, branch, params) output += this.__treeIntoCompiledCodeReturnPath(indentation, paths, branch, params, pathParamMapIndex)
} else if (branch.path) { } else if (branch.path) {
output += '\n' + indentation + `if (str.length === ${this.__getIndex(indent, 0, params)}) {` output += '\n' + indentation + `if (strLength === ${this.__getIndex(indent, 0, params)}) {`
output += this.__treeIntoCompiledCodeReturnPath(indentation + ' ', paths, branch, params) output += this.__treeIntoCompiledCodeReturnPath(indentation + ' ', paths, branch, params, pathParamMapIndex)
output += '\n' + indentation + `}` output += '\n' + indentation + `}`
} }
} }
@ -664,7 +664,7 @@ export class FlaskaRouter {
} else { } else {
output += '\n' + indentation + ' ' output += '\n' + indentation + ' '
} }
output += this.__treeIntoCompiledCodeBranch(paths, branch.children, indent + 1, params.slice()) output += this.__treeIntoCompiledCodeBranch(paths, branch.children, indent + 1, params.slice(), pathParamMapIndex)
} }
if (addEndBracket) { if (addEndBracket) {
output += '\n' + indentation + '} ' output += '\n' + indentation + '} '
@ -674,7 +674,9 @@ export class FlaskaRouter {
} }
__treeIntoCompiledCodeClosure(paths, tree, staticList) { __treeIntoCompiledCodeClosure(paths, tree, staticList) {
let output = 'return function RBufferStrSliceClosure(str) {' let pathParamMapIndex = new Map()
let output = ''
let prefix = ''
if (staticList.size > 0) { if (staticList.size > 0) {
output += '\n let checkStatic = staticList.get(str)' output += '\n let checkStatic = staticList.get(str)'
output += '\n if(checkStatic) {' output += '\n if(checkStatic) {'
@ -682,11 +684,17 @@ export class FlaskaRouter {
output += '\n }' output += '\n }'
} }
if (tree.length) { if (tree.length) {
output += '\n ' + this.__treeIntoCompiledCodeBranch(paths, tree, 1, []) output += '\n let strLength = str.length'
output += '\n ' + this.__treeIntoCompiledCodeBranch(paths, tree, 1, [], pathParamMapIndex)
} }
output += '\n return null' output += '\n return null'
output += '\n}' output += '\n}'
//console.log(output) pathParamMapIndex.forEach(function (val, key) {
prefix += `let paths_${key} = paths[${val}]\n`
})
output = prefix + 'return function flaskaFastRouter(str) {\n "use strict";' + output
return new Function('paths', 'staticList', output)(paths, staticList) return new Function('paths', 'staticList', output)(paths, staticList)
} }
@ -1223,6 +1231,7 @@ ctx.state.nonce = nonce;
// Waiting 0.1 second for it to close down // Waiting 0.1 second for it to close down
setTimeout(res, 100) setTimeout(res, 100)
}) })
this.server.closeAllConnections()
}) })
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,242 +0,0 @@
class RouterError extends Error {
constructor(route1, route2, ...params) {
// Pass remaining arguments (including vendor specific ones) to parent constructor
super(...params);
// Maintains proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, RouterError);
}
this.name = "RouterError";
this.routeA = route1
this.routeB = route2
}
}
function Child(split, x, i) {
this.path = null
this.isParams = split[x].isParams ? split[x].word : null
this.isFullParams = split[x].isFullParams ? split[x].word : null
this.paramVarName = split[x].paramVarName ?? null
this.char = !this.isParams && !this.isFullParams ? split[x].word[i] || '/' : null
this.count = 0
this.children = []
}
function buildChild(x, i, splitPaths) {
let splitPath = splitPaths[0]
let letter = new Child(splitPath.split, x, i)
let consume = []
if (splitPath.split.length === x + 1
&& (splitPath.split[x].isParams
|| splitPath.split[x].isFullParams
|| splitPath.split[x].word.length === i + 1)) {
letter.path = splitPath.entry
letter.count += 1
} else {
consume = [splitPath]
}
for (let y = 1; y < splitPaths.length; y++) {
let checkPath = splitPaths[y]
if (!checkPath.split[x]
|| checkPath.split[x].isParams !== splitPath.split[x].isParams
|| checkPath.split[x].isFullParams !== splitPath.split[x].isFullParams
|| !checkPath.split[x].isParams
&& !checkPath.split[x].isFullParams
&& (checkPath.split[x].word[i] || '/') !== letter.char) break
consume.push(checkPath)
}
letter.count += consume.length
if (splitPath.split[x].word.length === i || splitPath.split[x].isParams || splitPath.split[x].isFullParams) {
x++
i = -1
}
while (consume.length) {
letter.children.push(buildChild(x, i + 1, consume))
consume.splice(0, letter.children[letter.children.length - 1].count)
}
return letter
}
function buildTree(splitPaths) {
let builder = []
while (splitPaths.length) {
builder.push(buildChild(0, 0, splitPaths))
splitPaths.splice(0, builder[builder.length - 1].count)
}
return builder
}
const regParamPrefix = /^::?/
const regCleanNonAschii = /(?![a-zA-Z_])./g
const regCleanRest = /_+/g
function splitAndSortPaths(paths, separateStatic = true) {
let staticPaths = new Map()
let paramsPaths = []
let collator = new Intl.Collator('en', { sensitivity: 'accent' });
paths.forEach(function(entry) {
if (entry.path[0] !== '/') throw new RouterError(entry, null, 'Specified route was missing forward slash at start')
// Collect static paths separately
if (entry.path.indexOf('/:') < 0 && separateStatic) {
return staticPaths.set(entry.path, {
path: entry,
params: {}
})
}
// Collect params path separately
paramsPaths.push({
split: entry.path.slice(1).split(/\//g).map(function(word) {
let actualWord = word.replace(regParamPrefix, '')
return {
word: actualWord,
isParams: word[0] === ':' && word[1] !== ':',
isFullParams: word[0] === ':' && word[1] === ':',
paramVarName: word[0] === ':'
? actualWord.replace(regCleanNonAschii, '_').replace(regCleanRest, '_')
: null
}
}),
entry,
})
})
paramsPaths.sort(function(aGroup, bGroup) {
let length = Math.max(aGroup.split.length, bGroup.split.length)
for (let x = 0; x < length; x++) {
let a = aGroup.split[x]
let b = bGroup.split[x]
if (!a) return -1
if (!b) return 1
// Full params go last
if (a.isFullParams && b.isFullParams) throw new RouterError(aGroup.entry, bGroup.entry, 'Two full path routes found on same level')
if (a.isFullParams) return 1
if (b.isFullParams) return -1
// Params go second last
if (a.isParams && !b.isParams) return 1
if (!a.isParams && b.isParams) return -1
// otherwise sort alphabetically if not identical
if (a.word !== b.word) return collator.compare(a.word, b.word)
}
throw new RouterError(aGroup, bGroup, 'Two identical paths were found')
})
return {
staticPaths,
paramsPaths,
}
}
const SlashCode = '/'.charCodeAt(0)
function getIndex(offset, additions, params) {
return (offset + additions)
+ (params.length
? ' + ' + params.map(a => `offset${a[1]}`).join(' + ')
: '')
}
function treeIntoCompiledCodeReturnPath(indentString, paths, branch, params) {
let pathIndex = paths.indexOf(branch.path)
if (pathIndex < 0) {
throw new RouterError(branch.path, null, 'InternalError: Specified path was not found in paths')
}
let output = '\n' + indentString + `return {`
output += '\n' + indentString + ` path: paths[${pathIndex}],`
if (params.length) {
output += '\n' + indentString + ` params: {`
for (let param of params) {
output += '\n' + indentString + ` ${param[0]}: s${param[1]},`
}
output += '\n' + indentString + ` },`
} else {
output += '\n' + indentString + ` params: {},`
}
output += '\n' + indentString + `}`
return output
}
const spaces = ' '
function treeIntoCompiledCodeBranch(paths, branches, indent = 0, params = []) {
let output = ''
let indentation = spaces.slice(0, (indent - params.length) * 2)
let addEndBracket = true
for (let i = 0; i < branches.length; i++) {
let branch = branches[i]
if (i > 0) {
if (!branch.isParams && !branch.isFullParams) {
output += ' else '
} else {
// output += '} //'
output += '\n' + indentation
}
}
if (!branch.isParams && !branch.isFullParams) {
output += `if (buf[${getIndex(indent, 0, params)}] === ${branch.char.charCodeAt(0)}) { // ${branch.char}`
if (branch.path) {
output += '\n' + indentation + ` if (buf.length === ${getIndex(indent, 1, params)}) {`
output += treeIntoCompiledCodeReturnPath(indentation + ' ', paths, branch, params)
output += '\n' + indentation + ` }`
}
} else {
addEndBracket = false
let paramVarName = (params.length + 1) + '_' + branch.paramVarName
output += `let s${paramVarName} = str.slice(${getIndex(indent, 0, params)}${branch.isFullParams ? '' : `, buf.indexOf(${SlashCode}, ${getIndex(indent, 0, params)}) >>> 0`})`
output += '\n' + indentation + `let offset${paramVarName} = s${paramVarName}.length`
output += '\n' + indentation
params.push([branch.isParams || branch.isFullParams, paramVarName])
if (branch.isFullParams) {
output += treeIntoCompiledCodeReturnPath(indentation, paths, branch, params)
} else if (branch.path) {
output += '\n' + indentation + `if (buf.length === ${getIndex(indent, 0, params)}) {`
output += treeIntoCompiledCodeReturnPath(indentation + ' ', paths, branch, params)
output += '\n' + indentation + `}`
}
}
if (branch.children.length) {
if (branch.path) {
output += ' else '
} else {
output += '\n' + indentation + ' '
}
output += treeIntoCompiledCodeBranch(paths, branch.children, indent + 1, params.slice())
}
if (addEndBracket) {
output += '\n' + indentation + '} '
}
}
return output
}
function treeIntoCompiledCodeClosure(paths, tree, staticList) {
let output = 'return function RBufferStrSliceClosure(str) {'
output += '\n let checkStatic = staticList.get(str)'
output += '\n if(checkStatic) {'
output += '\n return checkStatic'
output += '\n }'
output += '\n var buf = Buffer.from(str)'
output += '\n ' + treeIntoCompiledCodeBranch(paths, tree, 1, [])
output += '\n return null'
output += '\n}'
return new Function('paths', 'staticList', output)(paths, staticList)
}
export function compilePaths(paths) {
let splitPaths = splitAndSortPaths(paths)
let tree = buildTree(splitPaths.paramsPaths.slice())
return treeIntoCompiledCodeClosure(paths, tree, splitPaths.staticPaths)
}

View file

@ -1,26 +0,0 @@
import { Flaska } from './flaska.mjs'
const port = 51026
const flaska = new Flaska({}, )
flaska.devMode()
flaska.get('/', function(ctx) {
ctx.body = { status: true }
})
flaska.get('/:item/asdf/herp/:derp/bla', function(ctx) {
ctx.body = { item: ctx.params.item }
})
flaska.get('/a', function(ctx) {
ctx.body = { status: true }
})
flaska.get('/error', function(ctx) {
process.exit(1)
})
flaska.listen(port, function() {
console.log('listening on port', port)
})

View file

@ -1072,10 +1072,23 @@ t.describe('#closeAsync()', function() {
assert.strictEqual(err, assertError) assert.strictEqual(err, assertError)
}) })
t.test('it should call closeAllConnections', async function() {
const assertError = new Error('Pirate Fight')
let flaska = new Flaska()
flaska.server = {
close: stub(),
closeAllConnections: stub(),
}
flaska.server.closeAllConnections.throws(assertError)
let err = await assert.isRejected(flaska.closeAsync())
assert.strictEqual(err, assertError)
})
t.test('should otherwise work', async function() { t.test('should otherwise work', async function() {
let flaska = new Flaska() let flaska = new Flaska()
flaska.server = { flaska.server = {
close: stub() close: stub(),
closeAllConnections: stub(),
} }
flaska.server.close.returnWith(function(cb) { flaska.server.close.returnWith(function(cb) {
cb(null, { a: 1 }) cb(null, { a: 1 })

View file

@ -1,20 +0,0 @@
import { Flaska } from './flaska_buffer.mjs'
const port = 51027
const flaska = new Flaska({}, )
flaska.get('/', function(ctx) {
ctx.body = { status: true }
})
flaska.get('/:item/asdf/herp/:derp/bla', function(ctx) {
ctx.body = { item: ctx.params.item }
})
flaska.get('/error', function(ctx) {
process.exit(1)
})
flaska.listen(port, function() {
console.log('listening on port', port)
})