Skip to content

Commit

Permalink
feat: add cli options
Browse files Browse the repository at this point in the history
  • Loading branch information
sxzz committed Aug 6, 2024
1 parent 11e43bb commit 5f9b4d3
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 79 deletions.
1 change: 1 addition & 0 deletions benchmark/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
benchmark-result.json
2 changes: 1 addition & 1 deletion benchmark/client/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ async function bench() {
>
<button @click="bench">Benchmark mounting</button>
<button id="run" @click="run">Create 1,000 rows</button>
<button id="runlots" @click="runLots">Create 10,000 rows</button>
<button id="runLots" @click="runLots">Create 10,000 rows</button>
<button id="add" @click="add">Append 1,000 rows</button>
<button id="update" @click="update">Update every 10th row</button>
<button id="clear" @click="clear">Clear</button>
Expand Down
49 changes: 26 additions & 23 deletions benchmark/client/profiling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,53 +17,56 @@ export function wrap(
fn: (...args: any[]) => any,
): (...args: any[]) => Promise<void> {
return async (...args) => {
const btns = Array.from(
document.querySelectorAll<HTMLButtonElement>('#control button'),
)
const timeEl = document.getElementById('time')!
timeEl.classList.remove('done')
for (const node of btns) {
node.disabled = true
}
document.body.classList.remove('done')

const { doProfile } = globalThis
await defer()

doProfile && console.profile(id)
const start = performance.now()
fn(...args)

await defer()
const time = performance.now() - start
const prevTimes = times[id] || (times[id] = [])
prevTimes.push(time)
const median = prevTimes.slice().sort((a, b) => a - b)[
Math.floor(prevTimes.length / 2)
]
const mean = prevTimes.reduce((a, b) => a + b, 0) / prevTimes.length

const { min, max, median, mean, std } = compute(prevTimes)
const msg =
`${id}: min: ${Math.min(...prevTimes).toFixed(2)} / ` +
`max: ${Math.max(...prevTimes).toFixed(2)} / ` +
`median: ${median.toFixed(2)}ms / ` +
`mean: ${mean.toFixed(2)}ms / ` +
`${id}: min: ${min} / ` +
`max: ${max} / ` +
`median: ${median}ms / ` +
`mean: ${mean}ms / ` +
`time: ${time.toFixed(2)}ms / ` +
`std: ${getStandardDeviation(prevTimes).toFixed(2)} ` +
`std: ${std} ` +
`over ${prevTimes.length} runs`
doProfile && console.profileEnd(id)
console.log(msg)
const timeEl = document.getElementById('time')!
timeEl.textContent = msg
timeEl.classList.add('done')

for (const node of btns) {
node.disabled = false
}
document.body.classList.add('done')
}
}

function getStandardDeviation(array: number[]) {
function compute(array: number[]) {
const n = array.length
const max = Math.max(...array)
const min = Math.min(...array)
const mean = array.reduce((a, b) => a + b) / n
return Math.sqrt(
const std = Math.sqrt(
array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n,
)
const median = array.slice().sort((a, b) => a - b)[Math.floor(n / 2)]
return {
max: round(max),
min: round(min),
mean: round(mean),
std: round(std),
median: round(median),
}
}

function round(n: number) {
return +n.toFixed(2)
}
138 changes: 83 additions & 55 deletions benchmark/index.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
/* eslint-disable no-restricted-syntax */

// @ts-check
import path from 'node:path'
import { parseArgs } from 'node:util'
import connect from 'connect'
import { writeFile } from 'node:fs/promises'
import Vue from '@vitejs/plugin-vue'
import { build } from 'vite'
import { exec } from '../scripts/utils.js'
import connect from 'connect'
import sirv from 'sirv'
import { launch } from 'puppeteer'
import colors from 'picocolors'
import { exec } from '../scripts/utils.js'

// Thanks to https://github.com/krausest/js-framework-benchmark (Apache-2.0 license)

const {
values: { skipVapor, skipApp, port: portStr, count: countStr, headless },
values: {
skipVapor,
skipApp,
skipBench,
port: portStr,
count: countStr,
'no-headless': noHeadless,
},
} = parseArgs({
allowNegative: true,
allowPositionals: true,
options: {
skipVapor: {
Expand All @@ -24,6 +33,10 @@ const {
type: 'boolean',
short: 'a',
},
skipBench: {
type: 'boolean',
short: 'a',
},
port: {
type: 'string',
short: 'p',
Expand All @@ -32,12 +45,10 @@ const {
count: {
type: 'string',
short: 'c',
default: '100',
default: '50',
},
headless: {
'no-headless': {
type: 'boolean',
short: 'h',
default: true,
},
},
})
Expand All @@ -51,14 +62,12 @@ if (!skipVapor) {
if (!skipApp) {
await buildApp()
}

const server = startServer()
await bench()
server.close()

process.on('SIGTERM', () => {
if (!skipBench) {
await bench()
server.close()
})
}

async function buildVapor() {
console.info(colors.blue('Building Vapor...'))
Expand Down Expand Up @@ -99,7 +108,6 @@ async function buildApp() {
const CompilerVapor = await import(
'../packages/compiler-vapor/dist/compiler-vapor.cjs.prod.js'
)

const vaporRuntime = path.resolve(
import.meta.dirname,
'../packages/vue-vapor/dist/vue-vapor.esm-browser.prod.js',
Expand All @@ -108,13 +116,20 @@ async function buildApp() {
root: './client',
build: {
minify: 'terser',
rollupOptions: {
onwarn(log, handler) {
if (log.code === 'INVALID_ANNOTATION') return
handler(log)
},
},
},
resolve: {
alias: {
'vue/vapor': vaporRuntime,
'@vue/vapor': vaporRuntime,
},
},
clearScreen: false,
plugins: [
Vue({
compiler: CompilerSFC,
Expand All @@ -129,10 +144,12 @@ async function buildApp() {
function startServer() {
const server = connect().use(sirv('./client/dist')).listen(port)
console.info(`\n\nServer started at`, colors.blue(`http://localhost:${port}`))
process.on('SIGTERM', () => server.close())
return server
}

async function bench() {
console.info(colors.blue('\nStarting benchmark...'))
const disableFeatures = [
'Translate', // avoid translation popups
'PrivacySandboxSettings4', // avoid privacy popup
Expand All @@ -149,10 +166,13 @@ async function bench() {
`--disable-features=${disableFeatures.join(',')}`,
]

const headless = !noHeadless
console.info('headless:', headless)
const browser = await launch({
headless,
headless: headless,
args,
})
console.log('browser version:', colors.blue(await browser.version()))
const page = await browser.newPage()
await page.goto(`http://localhost:${port}/`, {
waitUntil: 'networkidle0',
Expand All @@ -162,34 +182,45 @@ async function bench() {

const t = performance.now()
for (let i = 0; i < count; i++) {
await doAction('run')
await doAction('add')
await doAction('update')
await doAction('swaprows')
await doAction('clear')
await clickButton('run')
await clickButton('add')
await select()
await clickButton('update')
await clickButton('swaprows')
await clickButton('runLots')
await clickButton('clear')
}
console.info('Total time:', performance.now() - t, 'ms')
console.info('Total time:', ((performance.now() - t) / 1000).toFixed(2), 's')

const times = await getTimes()
/** @type {Record<string, { mean: string, std: string }>} */
const result = {}
for (const key in times) {
const mean = getMean(times[key])
const std = getStandardDeviation(times[key])
result[key] = { mean: mean.toFixed(2), std: std.toFixed(2) }
}

// eslint-disable-next-line no-console
/** @type {Record<string, { mean: number, std: number }>} */
const result = Object.fromEntries(
Object.entries(times).map(([k, v]) => [k, compute(v)]),
)
console.table(result)
writeFile('benchmark-result.json', JSON.stringify(result))

await page.close()
await browser.close()

/**
* @param {string} id
*/
async function doAction(id) {
async function clickButton(id) {
await page.click(`#${id}`)
await wait()
}

async function select() {
for (let i = 1; i <= 10; i++) {
await page.click(`tbody > tr:nth-child(2) > td:nth-child(2) > a`)
await page.waitForSelector(`tbody > tr:nth-child(2).danger`)
await page.click(`tbody > tr:nth-child(2) > td:nth-child(3) > button`)
await wait()
}
}

async function wait() {
await page.waitForSelector('.done')
}

Expand All @@ -199,38 +230,35 @@ async function bench() {

async function forceGC() {
await page.evaluate(
"window.gc({type:'major',execution:'sync',flavor:'last-resort'})",
`window.gc({type:'major',execution:'sync',flavor:'last-resort'})`,
)
}
}

/**
* @param {number[]} nums
* @returns {number}
*/
function getMean(nums) {
return (
nums.reduce(
/**
* @param {number} a
* @param {number} b
* @returns {number}
*/
(a, b) => a + b,
0,
) / nums.length
)
}

/**
*
* @param {number[]} array
* @returns
*/
function getStandardDeviation(array) {
function compute(array) {
const n = array.length
const max = Math.max(...array)
const min = Math.min(...array)
const mean = array.reduce((a, b) => a + b) / n
return Math.sqrt(
const std = Math.sqrt(
array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n,
)
const median = array.slice().sort((a, b) => a - b)[Math.floor(n / 2)]
return {
max: round(max),
min: round(min),
mean: round(mean),
std: round(std),
median: round(median),
}
}

/**
* @param {number} n
*/
function round(n) {
return +n.toFixed(2)
}
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export default tseslint.config(
'eslint.config.js',
'rollup*.config.js',
'scripts/**',
'benchmark/*',
'./*.{js,ts}',
'packages/*/*.js',
'packages/vue/*/*.js',
Expand Down

0 comments on commit 5f9b4d3

Please sign in to comment.