diff --git a/packages/web-components/fast-benchmarks/README.md b/packages/web-components/fast-benchmarks/README.md index b4cd5d3f1f3..57f33feda33 100644 --- a/packages/web-components/fast-benchmarks/README.md +++ b/packages/web-components/fast-benchmarks/README.md @@ -112,3 +112,7 @@ example: `yarn run benchmark --library=fast-element --benchmark=binding --versions=1.9.0 local` `yarn run benchmark --library=fast-element --benchmark=binding --versions=1.9.0 master` `yarn run benchmark --library=fast-foundation --benchmark=form-associated -v 2.34.0 2.42.1` + +`yarn run benchmark --library=fast-element --benchmark=test --versions=1.9.0 local` + +- this should run all default test suite diff --git a/packages/web-components/fast-benchmarks/benchmarks/fast-components/button/index.ts b/packages/web-components/fast-benchmarks/benchmarks/fast-components/button/index.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/web-components/fast-benchmarks/benchmarks/fast-element/render/index.ts b/packages/web-components/fast-benchmarks/benchmarks/fast-element/render/index.ts index ca055d4a2ee..540896b1853 100644 --- a/packages/web-components/fast-benchmarks/benchmarks/fast-element/render/index.ts +++ b/packages/web-components/fast-benchmarks/benchmarks/fast-element/render/index.ts @@ -8,7 +8,7 @@ import { repeat, } from "@microsoft/fast-element"; -import { _random, adjectives, colours, nouns } from "../../../utils/constants.js"; +import { _random, adjectives, colours, nouns } from "../../../utils/index.js"; import runBenchmark from "./shared.js"; const itemCount = 250; diff --git a/packages/web-components/fast-benchmarks/benchmarks/fast-element/render/index2.ts b/packages/web-components/fast-benchmarks/benchmarks/fast-element/render/index2.ts index d3d8e7fed36..dd651ae95b5 100644 --- a/packages/web-components/fast-benchmarks/benchmarks/fast-element/render/index2.ts +++ b/packages/web-components/fast-benchmarks/benchmarks/fast-element/render/index2.ts @@ -8,7 +8,7 @@ import { oneTime, repeat, } from "@microsoft/fast-element"; -import { _random, adjectives, colours, nouns } from "../../../utils/constants.js"; +import { _random, adjectives, colours, nouns } from "../../../utils/index.js"; import runBenchmark from "./shared.js"; const itemCount = 250; diff --git a/packages/web-components/fast-benchmarks/benchmarks/fast-element/render/shared.ts b/packages/web-components/fast-benchmarks/benchmarks/fast-element/render/shared.ts index c4d792cb69d..bc774c5545a 100644 --- a/packages/web-components/fast-benchmarks/benchmarks/fast-element/render/shared.ts +++ b/packages/web-components/fast-benchmarks/benchmarks/fast-element/render/shared.ts @@ -1,13 +1,3 @@ -declare global { - interface Window { - usedJSHeapSize: any; - gc: any; - } - interface Performance { - memory: any; - } -} - function measureMemory() { if (window && performance && performance.memory) { // Report results in MBs\ diff --git a/packages/web-components/fast-benchmarks/benchmarks/fast-element/test/index.ts b/packages/web-components/fast-benchmarks/benchmarks/fast-element/test/index.ts new file mode 100644 index 00000000000..5b13d2e7a5e --- /dev/null +++ b/packages/web-components/fast-benchmarks/benchmarks/fast-element/test/index.ts @@ -0,0 +1,47 @@ +import { + attr, + css, + customElement, + FASTElement, + html, + observable, + repeat, +} from "@microsoft/fast-element"; +import { data, RandomItem } from "../../../utils/index.js"; + +@customElement({ + name: "x-item", + template: html` +
+ ${x => x.value} +
+ `, + styles: css` + .item { + display: flex; + } + `, +}) +class XItem extends FASTElement { + @attr value: string | undefined; + onClick(e: MouseEvent) { + console.log(e.type); + } +} + +@customElement({ + name: "x-app", + template: html` +
+ ${repeat( + x => x.items, + html` + + ` + )} +
+ `, +}) +class XApp extends FASTElement { + @observable items: RandomItem[] = data; +} diff --git a/packages/web-components/fast-benchmarks/benchmarks/fast-element/test/index2.ts b/packages/web-components/fast-benchmarks/benchmarks/fast-element/test/index2.ts new file mode 100644 index 00000000000..ed715e3eec1 --- /dev/null +++ b/packages/web-components/fast-benchmarks/benchmarks/fast-element/test/index2.ts @@ -0,0 +1,55 @@ +import { + attr, + bind, + css, + customElement, + FASTElement, + html, + oneTime, + repeat, +} from "@microsoft/fast-element"; +import { data, RandomItem } from "../../../utils/index.js"; + +const xItemTemplate = html` +
+ ${x => x.value} +
+`; + +const styles = css` + .item { + display: flex; + } +`; +@customElement({ + name: "x-item", + template: xItemTemplate, + styles, +}) +class XItem extends FASTElement { + @attr value: string | undefined; + + onClick(e: MouseEvent) { + console.log(e.type); + } +} + +const xAppTemplate = html` +
+ ${repeat( + x => x.items, + html` + + ` + )} +
+`; +@customElement({ + name: "x-app", + template: xAppTemplate, +}) +class XApp extends FASTElement { + items: RandomItem[] = data; +} diff --git a/packages/web-components/fast-benchmarks/benchmarks/fast-foundation/form-associated/index.ts b/packages/web-components/fast-benchmarks/benchmarks/fast-foundation/form-associated/index.ts index 19fb7b46ee2..c033bf3f9d1 100644 --- a/packages/web-components/fast-benchmarks/benchmarks/fast-foundation/form-associated/index.ts +++ b/packages/web-components/fast-benchmarks/benchmarks/fast-foundation/form-associated/index.ts @@ -7,7 +7,7 @@ import { repeat, } from "@microsoft/fast-element"; import { FormAssociated, FoundationElement } from "@microsoft/fast-foundation"; -import { _random, adjectives, colours, nouns } from "../../../utils/constants.js"; +import { _random, adjectives, colours, nouns } from "../../../utils/index.js"; const itemCount = 250; let id = 0; diff --git a/packages/web-components/fast-benchmarks/package.json b/packages/web-components/fast-benchmarks/package.json index fea8536d207..ccf0fa7ee34 100644 --- a/packages/web-components/fast-benchmarks/package.json +++ b/packages/web-components/fast-benchmarks/package.json @@ -2,6 +2,7 @@ "name": "fast-benchmarks", "version": "1.0.0", "description": "", + "type": "module", "scripts": { "build": "tsc && tsc --build", "benchmark": "node scripts/index.js", diff --git a/packages/web-components/fast-benchmarks/scripts/index.js b/packages/web-components/fast-benchmarks/scripts/index.js index 3c50e13ea8c..9eaaef26d19 100644 --- a/packages/web-components/fast-benchmarks/scripts/index.js +++ b/packages/web-components/fast-benchmarks/scripts/index.js @@ -1,13 +1,12 @@ -const { exec } = require("child_process"); -const { writeFile, mkdir } = require("fs/promises"); -const { join } = require("path"); -const { program } = require("commander"); -const { spawn } = require("cross-spawn"); +import { exec } from "child_process"; +import { program } from "commander"; +import { spawn } from "cross-spawn"; +import { generateTemplates } from "./template.js"; program .option("-l, --library ", "run benchmarks in library") .option("-b, --benchmark ", "run the benchmark: ") - .option("-m, --memory", "check memory metrics") + // .option("-m, --memory", "check memory metrics") .option( "-v, --versions [versions...]", "specify available versions, you can also use 'local' or 'master' that would point to github branches" @@ -16,159 +15,14 @@ program "-lb, --localBenchFile ", "specify the html file you want your local version to use, only valid if 'local' is one of the versions you passed in" ) + .option( + "-o, --operations [versions...]", + "specify the operations you want the benchmarks to run, if none are passed all available operations will be run" + ) .parse(process.argv); const options = program.opts(); -//TODO: add defaults -const { library, benchmark: benchmarkName, versions, localBenchFile, memory } = options; -console.log("options", options); - -const TACH_SCHEMA = - "https://raw.githubusercontent.com/Polymer/tachometer/master/config.schema.json"; - -const MEMORY_METRIC = "memory"; -const LOCAL = "local"; -const MASTER = "master"; - -//create tachometer schema based on options -const defaultBenchOptions = { - // Tachometer default is 50, but locally let's only do 10 - sampleSize: 30, - // Tachometer default is 3 minutes, but let's shrink it to 1 here to save some - timeout: 1, -}; - -//TODO: customize measurement types -const isMemoryBench = benchmarkName.toLowerCase() === MEMORY_METRIC || memory; - -const measurement = isMemoryBench - ? [ - { - name: "usedJSHeapSize", - mode: "expression", - expression: "window.usedJSHeapSize", - }, - ] - : [ - { - mode: "performance", - entryName: benchmarkName, - }, - ]; - -/** - * Generates the benchmarks array expected by the tachometer config file. - * @returns {ConfigFile["benchmarks"], []} an array of benchmarks - */ -async function generateBenchmarks() { - const localBranchName = versions.includes(LOCAL) - ? await getLocalGitBranchName() - : undefined; - - const browser = { - name: "chrome", - headless: true, - }; - - if (isMemoryBench) - browser.addArguments = ["--js-flags=--expose-gc", "--enable-precise-memory-info"]; - - /** @type {ConfigFile["benchmarks"]} */ - const benchmarks = []; - versions.forEach(version => { - const isLocalBranch = version === LOCAL; - const isBranch = isLocalBranch || version === MASTER; - const htmlFile = isLocalBranch && localBenchFile ? localBenchFile : "index.html"; - const url = `benchmarks/${library}/${benchmarkName}/${htmlFile}`; - - const benchmark = { url, browser, name: benchmarkName, measurement }; - const package = `@microsoft/${library}`; - if (isBranch) { - const ref = version === LOCAL ? localBranchName : MASTER; - benchmark.packageVersions = { - label: version, - dependencies: { - [package]: { - kind: "git", - repo: "https://github.com/microsoft/fast.git", - ref, - subdir: `packages/web-components/${library}`, - setupCommands: [ - "yarn install", - `yarn --cwd ./packages/web-components/${library} build`, - ], - }, - }, - }; - } else { - benchmark.packageVersions = { - label: version, - dependencies: { [package]: version }, - }; - } - benchmarks.push(benchmark); - }); - return benchmarks; -} - -/** - * Creates a dist folder to hold the generated config file. - * If file already exists, it will replace the existing file with new config. - * @typedef {import('tachometer/lib/configfile').ConfigFile} ConfigFile Expected - * See https://www.npmjs.com/package/tachometer#config-file - * @param {string} name - * @param {ConfigFile} config - * @param {string} dest destination folder where config file will be generated, if empty string means it's rootDir - * @returns {string} path location of newly generated config json file - */ - -const ROOT_DIR = ""; -async function writeConfig(name, config, dest = ROOT_DIR) { - /** @type {boolean} check if des string contains any characters matching a letter, if it does, it is not a rootDir */ - const isRootDir = !dest.match("[a-zA-Z]+") && !ROOT_DIR; - const configName = name + ".json"; - const configPath = join(__dirname, "../" + dest, configName); - - if (!isRootDir) await mkdir(join(__dirname, "../" + dest), { recursive: true }); - await writeFile(configPath, JSON.stringify(config, null, 2), "utf8"); - - return isRootDir ? join(process.cwd(), configName) : join(dest, configName); -} - -/** - * Get current local git branch name - * @returns {Promise} - */ -//TODO: add in Documentation, 'local' & 'master' as one of the versions options only works in a git repo context -async function getLocalGitBranchName() { - return new Promise((resolve, reject) => { - const res = exec("git branch --show-current"); - res.stdout.on("data", data => - data ? resolve(data.trim()) : reject("Error in getting local branch name.") - ); - }).catch(error => { - throw new Error( - `Unable to retrieve local branch name: ${error}, make sure you have "git" installed.` - ); - }); -} - -/** - * Generate tachometer config file - * @returns {string} path location of newly generated config json file - */ -async function generateConfig() { - try { - const benchmarks = await generateBenchmarks(); - /** @type {ConfigFile} */ - const config = { $schema: TACH_SCHEMA, ...defaultBenchOptions, benchmarks }; - return await writeConfig(benchmarkName + ".config", config, "dist"); - } catch (error) { - throw new Error(error); - } -} - /** * Check to see if we can reach the npm repository within a timeout * @returns {Promise} @@ -185,7 +39,7 @@ async function checkNpmRegistryIsAvailable() { }) ); }).catch(error => { - throw error; + return error; }); } @@ -208,7 +62,7 @@ async function buildBenchmark(configPath) { resolve(void 0); }); }).catch(error => { - throw error; + return error; }); } @@ -217,38 +71,40 @@ async function buildBenchmark(configPath) { * @param {string} configPath * @returns {Promise} */ -async function runBenchmark(configPath) { - return new Promise((resolve, reject) => { - const args = ["tach", "--config", configPath]; - const child = spawn("npx", args, { stdio: "inherit" }); - child.on("close", code => { - if (code !== 0) { - reject({ - command: `npx tach --config ${configPath}`, - }); - return; +async function runBenchmark(configPaths) { + // can be run synchronously + configPaths.forEach(configPath => { + // for (let configPath of configPaths) { + return new Promise((resolve, reject) => { + const args = ["tach", "--config", configPath]; + const child = spawn("npx", args, { stdio: "inherit" }); + child.on("close", code => { + if (code !== 0) { + reject({ + command: `npx tach --config ${configPath}`, + }); + return; + } + resolve(void 0); + }); + }).catch(error => { + if (error.command) { + throw new Error( + `failed at ${error.command}: Make sure your local branch is pushed to git to use the 'local' keyword in versions.` + ); + } else { + return error; } - resolve(void 0); }); - }).catch(error => { - return error; }); } -// create tsconfig.{my-library}.json file and add include path to it -const tsConfig = { - extends: "./tsconfig.json", - include: ["utils/**/*.ts", `benchmarks/${library}/**/*.ts`], -}; - const run = async () => { try { - const configPath = await generateConfig(); - console.log("configPath", configPath); await checkNpmRegistryIsAvailable(); - const tsConfigPath = await writeConfig(`tsconfig.${library}`, tsConfig); + const { tsConfigPath, tachoConfigPaths } = await generateTemplates(options); await buildBenchmark(tsConfigPath); - await runBenchmark(configPath); + await runBenchmark(tachoConfigPaths); } catch (error) { return error; } diff --git a/packages/web-components/fast-benchmarks/scripts/template.js b/packages/web-components/fast-benchmarks/scripts/template.js new file mode 100644 index 00000000000..66e9dfc863c --- /dev/null +++ b/packages/web-components/fast-benchmarks/scripts/template.js @@ -0,0 +1,318 @@ +import { mkdir, writeFile } from "fs/promises"; +import { readdir, readFileSync } from "fs"; +import { exec } from "child_process"; +import { basename, dirname, extname, join, resolve } from "path"; + +/** + * Creates a dist folder to hold the generated config file. + * If file already exists, it will replace the existing file with new config. + * @typedef {import('tachometer/lib/configfile').ConfigFile} ConfigFile Expected + * See https://www.npmjs.com/package/tachometer#config-file + * @param {string} name + * @param {ConfigFile} config will be stringified if not already + * @param {string} ext extension configuration for the file + * @param {string} dest destination folder where config file will be generated, if empty string means it's rootDir + * @returns {string} path location of newly generated config json file + */ +const ROOT_DIR = ""; +const JSON_EXT = ".json"; +async function writeConfig(name, config, ext = JSON_EXT, dest = ROOT_DIR) { + /** @type {boolean} check if des string contains any characters matching a letter, if it does, it is not a rootDir */ + const isRootDir = !dest.match("[a-zA-Z]+") && !ROOT_DIR; + const configName = name + ext; + const __dirname = resolve(dirname(isRootDir ? ROOT_DIR : `${dest}`)); + const configPath = join(__dirname, dest, configName); + if (!isRootDir) await mkdir(join(__dirname, dest), { recursive: true }); + + // if ext is JSON, stringify, otherwise leave as is + const str = ext === JSON_EXT ? JSON.stringify(config, null, 2) : config; + await writeFile(configPath, str, "utf8"); + return isRootDir ? join(process.cwd(), configName) : join(dest, configName); +} +// create tsconfig.{my-library}.json file and add include path to it +async function generateTsConfig({ library, benchmark }) { + const tsConfig = { + extends: "./tsconfig.json", + include: ["utils/**/*.ts", `benchmarks/${library}/${benchmark}/*.ts`], + }; + return await writeConfig(`tsconfig.${library}`, tsConfig); +} + +/** + * Generates the html template for benchmark, inject compiled js script in header, spit out output in dist + */ + +export function getTestName(filename) { + const extension = extname(filename); + const res = basename(filename, extension); + return res; +} + +async function generateHtmlTemplate(operationFile, compiledJsBench, fileName) { + const name = getTestName(operationFile); + const benchScript = readFileSync(`./src/${operationFile}`, "utf8"); + const defaultHtml = ` + + + +
+ + + + `; + // generate html template of all default suite for benchmark + const path = await writeConfig(fileName + "-" + name, defaultHtml, ".html", "dist"); + return { name, path }; +} +async function generateHtmlTemplates( + { library, benchmark, operations }, + fileName, + benchFile = "index" +) { + const compiledJsBench = `../benchmarks/${library}/${benchmark}/${benchFile}.js`; + console.log("compiledJsBench", compiledJsBench, fileName); + // any operation listed under 'src' folder is eligible + return new Promise((resolve, reject) => { + readdir("src", async (err, files) => { + //handling error + if (err) reject("Unable to scan directory: " + err); + + const operationProps = { names: [], htmlPaths: [] }; + + // handle if specific operations are passed in + if (operations) { + const fileNames = files.map(f => getTestName(f)); + const match = operations.some(f => fileNames.includes(f)); + if (!match) { + reject( + "The operation name you passed does not exist, please check spelling or make sure you added the operation test under /src folder." + ); + } + for (let i = 0; i < files.length; i++) { + const operationFile = files[i]; + const operationName = getTestName(operationFile); + + if (operations.includes(operationName)) { + const { name, path } = await generateHtmlTemplate( + operationFile, + compiledJsBench, + fileName + ); + operationProps.names.push(name); + operationProps.htmlPaths.push(path); + } + } + } else { + // run all possible operations + // TODO: reduce dup code + for (let i = 0; i < files.length; i++) { + const operationFile = files[i]; + const { name, path } = await generateHtmlTemplate( + operationFile, + compiledJsBench, + fileName + ); + operationProps.names.push(name); + operationProps.htmlPaths.push(path); + } + } + + resolve(operationProps); + }); + }).catch(error => { + console.log("error", error); + if (error) { + throw new Error(error); + } else { + return error; + } + }); +} + +/** + * Get current local git branch name + * @returns {Promise} + */ +//TODO: add in Documentation, 'local' & 'master' as one of the versions options only works in a git repo context +async function getLocalGitBranchName() { + return new Promise((resolve, reject) => { + const res = exec("git branch --show-current"); + res.stdout.on("data", data => + data ? resolve(data.trim()) : reject("Error in getting local branch name.") + ); + }).catch(error => { + throw new Error( + `Unable to retrieve local branch name: ${error}, make sure you have "git" installed.` + ); + }); +} + +/** + * Generates the benchmarks array expected by the tachometer config file. + * @returns {{operationName: ConfigFile["benchmarks"]}, {}} returns benchmarkHash, where operation name is key and benchmarks array is value + */ + +async function generateBenchmarks( + { library, benchmark, versions }, + operationProps, + localProps +) { + const tachoData = {}; + operationProps.names.forEach((operation, idx) => { + /** @type {ConfigFile["benchmarks"]} */ + + const benchmarks = []; + const browser = { + name: "chrome", + headless: true, + addArguments: ["--js-flags=--expose-gc", "--enable-precise-memory-info"], + }; + const measurement = [ + { + name: "usedJSHeapSize", + mode: "expression", + expression: "window.usedJSHeapSize", + }, + { + mode: "performance", + entryName: operation, + }, + ]; + + versions.forEach(version => { + const isLocalBranch = localProps.branchName && version === LOCAL; + const isBranch = isLocalBranch || version === MASTER; + const url = + isLocalBranch && localProps.operationProps.htmlPaths + ? localProps.operationProps.htmlPaths[idx] + : operationProps.htmlPaths[idx]; + + // TODO: the name can be extracted out from benchmark array and added to the tacho config prop + const bench = { + url, + browser, + name: `${benchmark}-${operation}`, + measurement, + }; + const dep = `@microsoft/${library}`; + if (isBranch) { + const ref = isLocalBranch ? localProps.branchName : MASTER; + bench.packageVersions = { + label: version, + dependencies: { + [dep]: { + kind: "git", + repo: "https://github.com/microsoft/fast.git", + ref, + subdir: `packages/web-components/${library}`, + setupCommands: [ + "yarn install", + `yarn --cwd ./packages/web-components/${library} build`, + ], + }, + }, + }; + } else { + bench.packageVersions = { + label: version, + dependencies: { [dep]: version }, + }; + } + benchmarks.push(bench); + }); + tachoData[operation] = benchmarks; + }); + + return tachoData; +} + +/** + * Generate tachometer config file + * @param {string} fileName + * @param {{operationName: ConfigFile["benchmarks"]}, {}} benchmarksHash + * @returns {string[]} array of the paths' location of newly generated config json file + */ +async function generateConfig(fileName, benchmarksHash) { + try { + const TACH_SCHEMA = + "https://raw.githubusercontent.com/Polymer/tachometer/master/config.schema.json"; + + const defaultBenchOptions = { + // Tachometer default is 50, but locally let's only do 10 + sampleSize: 20, + // Tachometer default is 3 minutes, but let's shrink it to 1 here to save some + timeout: 0, + }; + + // TODO: add name here + const pathsPromises = []; + for (const benchmark in benchmarksHash) { + const config = { + $schema: TACH_SCHEMA, + ...defaultBenchOptions, + // name: `${benchmark}-${operation}`, + benchmarks: benchmarksHash[benchmark], + }; + + const path = await writeConfig( + `${fileName}-${benchmark}` + ".config", + config, + ".json", + "dist" + ); + + pathsPromises.push(path); + } + /** @type {ConfigFile[]} promises resolves to array of config file paths*/ + return await Promise.all(pathsPromises); + } catch (error) { + console.log("error", error); + } +} + +/** + * Creates a dist folder to hold the generated config file. + * If file already exists, it will replace the existing file with new config. + * @typedef {import('tachometer/lib/configfile').ConfigFile} ConfigFile Expected + * See https://www.npmjs.com/package/tachometer#config-file + * @param {import('commander').OptionValues } options + * @returns {string} path location of newly generated config json file + */ +const LOCAL = "local"; +const MASTER = "master"; +export async function generateTemplates(options) { + try { + const tsConfigPath = await generateTsConfig(options); + const fileName = `${options.library}_${options.benchmark}`; + + //special handling if 'local' version was passed in as an option + const localProps = { branchName: "", operationProps: {} }; + if (options.versions.includes(LOCAL)) { + localProps.branchName = await getLocalGitBranchName(); + // check if user passed in localBenchFile for different implementation of local + if (options.localBenchFile) + localProps.operationProps = await generateHtmlTemplates( + options, + `${fileName}_${LOCAL}`, + options.localBenchFile + ); + } + + const operationProps = await generateHtmlTemplates(options, fileName); + const benchmarksHash = await generateBenchmarks( + options, + operationProps, + localProps + ); + const tachoConfigPaths = await generateConfig(fileName, benchmarksHash); + return { + tsConfigPath, + tachoConfigPaths, + }; + } catch (error) { + console.error(error); + } +} diff --git a/packages/web-components/fast-benchmarks/src/create10k.js b/packages/web-components/fast-benchmarks/src/create10k.js new file mode 100644 index 00000000000..720bb91957a --- /dev/null +++ b/packages/web-components/fast-benchmarks/src/create10k.js @@ -0,0 +1,24 @@ +/* eslint-disable no-undef */ +import { + destroy, + getTestStartName, + measureMemory, + updateComplete, +} from "../utils/index.js"; +(async () => { + const container = document.getElementById("container"); + const create = () => { + const el = document.createElement("x-app"); + return container.appendChild(el); + }; + const render = async () => { + const start = getTestStartName(test); + performance.mark(start); + create(); + await updateComplete(); + performance.measure(test, start); + destroy(container); + }; + await render(); + measureMemory(); +})(); diff --git a/packages/web-components/fast-benchmarks/src/createDelete10x.js b/packages/web-components/fast-benchmarks/src/createDelete10x.js new file mode 100644 index 00000000000..b55315da061 --- /dev/null +++ b/packages/web-components/fast-benchmarks/src/createDelete10x.js @@ -0,0 +1,26 @@ +/* eslint-disable no-undef */ +import { + destroy, + getTestStartName, + measureMemory, + updateComplete, +} from "../utils/index.js"; +(async () => { + const container = document.getElementById("container"); + const create = () => { + const el = document.createElement("x-app"); + return container.appendChild(el); + }; + const createDelete = async () => { + const start = getTestStartName(test); + performance.mark(start); + for (let i = 0; i < 10; i++) { + create(); + destroy(container); + } + await updateComplete(); + performance.measure(test, start); + }; + await createDelete(); + measureMemory(); +})(); diff --git a/packages/web-components/fast-benchmarks/src/update10th.js b/packages/web-components/fast-benchmarks/src/update10th.js new file mode 100644 index 00000000000..000868988a1 --- /dev/null +++ b/packages/web-components/fast-benchmarks/src/update10th.js @@ -0,0 +1,28 @@ +/* eslint-disable no-undef */ +import { + destroy, + getTestStartName, + measureMemory, + updateComplete, +} from "../utils/index.js"; +(async () => { + const container = document.getElementById("container"); + const create = () => { + const el = document.createElement("x-app"); + return container.appendChild(el); + }; + const update = async () => { + let el = create(); + + const start = getTestStartName(test); + performance.mark(start); + for (let i = 0; i < el.items.length; i += 10) { + el.items[i].label += "!!!"; + } + await updateComplete(); + performance.measure(test, start); + destroy(container); + }; + await update(); + measureMemory(); +})(); diff --git a/packages/web-components/fast-benchmarks/tsconfig.json b/packages/web-components/fast-benchmarks/tsconfig.json index f95c5aca51f..05f398170c6 100644 --- a/packages/web-components/fast-benchmarks/tsconfig.json +++ b/packages/web-components/fast-benchmarks/tsconfig.json @@ -5,9 +5,6 @@ "module": "esnext", "lib": ["es2020", "DOM", "DOM.Iterable"], "declaration": true, - "declarationMap": true, - "sourceMap": true, - "inlineSources": true, "strict": true, "noUnusedParameters": true, "noImplicitReturns": true, @@ -18,5 +15,6 @@ "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "skipLibCheck": true - } + }, + "include": ["utils/**/*.ts"] } diff --git a/packages/web-components/fast-benchmarks/utils/constants.ts b/packages/web-components/fast-benchmarks/utils/constants.ts deleted file mode 100644 index 92f044e7f36..00000000000 --- a/packages/web-components/fast-benchmarks/utils/constants.ts +++ /dev/null @@ -1,59 +0,0 @@ -export const _random = (max: number) => { - return Math.round(Math.random() * 1000) % max; -}; - -export const adjectives = [ - "pretty", - "large", - "big", - "small", - "tall", - "short", - "long", - "handsome", - "plain", - "quaint", - "clean", - "elegant", - "easy", - "angry", - "crazy", - "helpful", - "mushy", - "odd", - "unsightly", - "adorable", - "important", - "inexpensive", - "cheap", - "expensive", - "fancy", -]; -export const colours = [ - "red", - "yellow", - "blue", - "green", - "pink", - "brown", - "purple", - "brown", - "white", - "black", - "orange", -]; -export const nouns = [ - "table", - "chair", - "house", - "bbq", - "desk", - "car", - "pony", - "cookie", - "sandwich", - "burger", - "pizza", - "mouse", - "keyboard", -]; diff --git a/packages/web-components/fast-benchmarks/utils/index.ts b/packages/web-components/fast-benchmarks/utils/index.ts new file mode 100644 index 00000000000..66ad5ce4220 --- /dev/null +++ b/packages/web-components/fast-benchmarks/utils/index.ts @@ -0,0 +1,114 @@ +export const _random = (max: number) => { + return Math.round(Math.random() * 1000) % max; +}; + +//generate 10k, pass this value in +const itemCount = 10000; +let id = 0; + +export const adjectives = [ + "pretty", + "large", + "big", + "small", + "tall", + "short", + "long", + "handsome", + "plain", + "quaint", + "clean", + "elegant", + "easy", + "angry", + "crazy", + "helpful", + "mushy", + "odd", + "unsightly", + "adorable", + "important", + "inexpensive", + "cheap", + "expensive", + "fancy", +]; +export const colours = [ + "red", + "yellow", + "blue", + "green", + "pink", + "brown", + "purple", + "brown", + "white", + "black", + "orange", +]; +export const nouns = [ + "table", + "chair", + "house", + "bbq", + "desk", + "car", + "pony", + "cookie", + "sandwich", + "burger", + "pizza", + "mouse", + "keyboard", +]; + +export class RandomItem { + label: string; + + constructor(public readonly id: number) { + this.label = + adjectives[_random(adjectives.length)] + + " " + + colours[_random(colours.length)] + + " " + + nouns[_random(nouns.length)]; + } +} + +function generateData(count: number) { + const data = []; + + for (let i = 0; i < count; i++) { + data.push(new RandomItem(++id)); + } + + return data; +} +export const data: RandomItem[] = generateData(itemCount); + +export const destroy = (container: { innerHTML: string }) => { + container.innerHTML = ""; +}; +export const getTestStartName = (name: any) => `${name}-start`; +export const updateComplete = () => new Promise(r => requestAnimationFrame(r)); + +declare global { + interface Window { + gc: () => void; + usedJSHeapSize: number; + } + interface Performance { + memory: { + usedJSHeapSize: number; + }; + } +} + +export function measureMemory() { + if (window && performance && performance.memory) { + // Report results in MBs\ + window.usedJSHeapSize = performance.memory.usedJSHeapSize / 1e6; + } else { + window.usedJSHeapSize = 0; + } +} diff --git a/packages/web-components/fast-benchmarks/utils/queryParams.ts b/packages/web-components/fast-benchmarks/utils/queryParams.ts deleted file mode 100644 index 47935cb2a2c..00000000000 --- a/packages/web-components/fast-benchmarks/utils/queryParams.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ - -/** - * Object storing query params parsed into their likely intended types. - * - * Note this avoids using URLSearchParams for compatibility with IE11. - * - * Examples: - * - * ?foo=true // boolean: false - * ?foo=false // boolean: true - * ?foo // boolean: true - * ?foo=5 // number: 5 - * ?foo=mode1 // string: "mode1" - */ -export const queryParams: { - [index: string]: string | boolean | number; -} = document.location.search - .slice(1) - .split("&") - .filter(s => s) - .map(p => p.split("=")) - .reduce( - (p: { [key: string]: string | boolean }, [k, v]) => ( - (p[k] = (() => { - try { - return JSON.parse(v); - } catch { - return v || true; - } - })()), - p - ), - {} - );