Skip to content

spike: convert all scenarios to new perf format #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 0 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
node_modules/
build/*.js
build/*.txt
build/node/*.js
build/node/*.txt
build/web/*.js
build/web/*.txt
build/hermes/*.js
results/*.txt
results/*.csv
puppeteer/index.bundle.js
Expand Down
228 changes: 228 additions & 0 deletions build-test-bundles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
const fs = require("fs");
const path = require("path");
const webpack = require("webpack");

function readPerfFiles(dirPath) {
const files = fs.readdirSync(dirPath);
const perfFiles = [];

files.forEach((file) => {
const filePath = path.join(dirPath, file);
const stat = fs.statSync(filePath);

if (stat.isDirectory()) {
perfFiles.push(...readPerfFiles(filePath));
} else if (filePath.includes(".perf.md")) {
perfFiles.push(filePath);
}
});

return perfFiles;
}

function readPerfFile(filePath) {
const fileContent = fs.readFileSync(filePath, "utf-8");
const metadata = {};
let imports = "";
let source = "";

// Regular expression for the metadata block
const metadataRegex = /---([\s\S]*?)---/;
const metadataMatch = fileContent.match(metadataRegex);
if (metadataMatch) {
const metadataString = metadataMatch[1].trim();
metadataString.split("\n").forEach((line) => {
const [key, value] = line.split(":").map((str) => str.trim());
metadata[key] = value;
});
}

// Regular expression for the import statements block
const importsRegex = /```js\n(import [\s\S]*?)\n```/;
const importsMatch = fileContent.match(importsRegex);
if (importsMatch) {
imports = importsMatch[1].trim();
}

// Regular expression for the JavaScript code block
const sourceRegex = /```js\n([\s\S]*?)\n```/g;
let sourceMatch;
while ((sourceMatch = sourceRegex.exec(fileContent)) !== null) {
// This ensures that we don't capture the import block again
if (!sourceMatch[1].startsWith("import ")) {
source += sourceMatch[1].trim() + "\n";
}
}

return { metadata, imports, source };
}

function createBundleSource(metadata, imports, source) {
return `
${imports}
import Benchmark from "benchmark"; // This is technically a no-op in the web bundle, but included for cross-compatibility

var suite = new Benchmark.Suite();

/**
* getStartMemory is an isomorphic way to get the memory used, in bytes,
* either from the browser or from Node.
*/
const getStartMemory = () => {
if (typeof performance.memory !== "undefined") {
return performance.memory.usedJSHeapSize;
} else {
const used = process.memoryUsage();
return used.heapUsed;
}
};

/**
* getEndMemory is an isomorphic way to get the memory used, in bytes,
* either from the browser or from Node.
*/
const getEndMemory = () => {
if (typeof performance.memory !== "undefined") {
return performance.memory.usedJSHeapSize;
} else {
const used = process.memoryUsage();
return used.heapUsed;
}
};

/**
* trackMaxMemory maintains a map of memory usage for each scenario.
*
* It uses keys that correspond to the title of each scenario, and the
* value is the maximum memory used for that scenario.
*/
const memoryUsage = {};
const trackMaxMemory = (title, memoryUsed) => {
if (!memoryUsage[title]) {
memoryUsage[title] = memoryUsed;
} else {
memoryUsage[title] = Math.max(memoryUsage[title], memoryUsed);
}
};

suite.on("complete", function () {
const headers = [
"scenario",
"ops_per_sec",
"margin_of_error",
"runs",
"max_memory_used_kb",
];
const results = suite.filter("successful").map((benchmark) => {
return {
scenario: benchmark.name,
opsSec: benchmark.hz,
plusMinus: benchmark.stats.rme,
runs: benchmark.stats.sample.length,
maxMemory: memoryUsage[benchmark.name],
};
});

console.log(headers.join(","));
results.forEach((result) => {
console.log(result.scenario + "," + result.opsSec + "," + result.plusMinus + "," + result.runs + "," + result.maxMemory);
});
});

suite.add(${metadata.title}, () => {
const startMemory = getStartMemory();
${source}
const endMemory = getEndMemory();
const memoryUsed = endMemory - startMemory;
trackMaxMemory(${metadata.title}, memoryUsed);
});

suite.on("cycle", function (event) {
console.log(String(event.target));
});

suite.run();
`;
}

function goodFileName(str) {
// Replace spaces with dashes
let fileSafeStr = str.replace(/\s+/g, "-");

// Convert to lowercase
fileSafeStr = fileSafeStr.toLowerCase();

// Remove special characters
fileSafeStr = fileSafeStr.replace(/[^\w-]+/g, "");

return fileSafeStr;
}

function bundleWithWebpack(entryPath, outputPath, platform) {
return new Promise((resolve, reject) => {
const config = {
mode: "production",
entry: entryPath,
output: {
filename: path.basename(outputPath),
path: path.dirname(outputPath),
libraryTarget: platform === "node" ? "commonjs2" : "var",
library: "suite",
},
target: platform === "node" ? "node" : "web",
externals: platform === "node" ? ["benchmark"] : [],
};

webpack(config, (err, stats) => {
if (err || stats.hasErrors()) {
console.error("Webpack build error:", err || stats.toJson().errors);
reject(err);
return;
}
console.log(`Bundle created at ${outputPath}`);
resolve();
});
});
}

// Driver code
(async () => {
const perfFiles = readPerfFiles(__dirname);
console.log(`Found ${perfFiles.length} performance files`);

for (let i = 0; i < perfFiles.length; i++) {
const filePath = perfFiles[i];
console.log(`Processing ${filePath}`);
const { metadata, imports, source } = readPerfFile(filePath);
console.log(`Generating bundles for ${metadata.title}`);

const bundleSource = createBundleSource(metadata, imports, source);
const bundleFileName = goodFileName(metadata.title);
// Write bundle source to file
const bundleSourcePath = path.join(
__dirname,
"build",
`${bundleFileName}-bundle-source.js`
);

fs.writeFileSync(bundleSourcePath, bundleSource);

// Define the output file paths
const webBundlePath = path.join(
__dirname,
"build",
`${bundleFileName}-web-bundle.js`
);
const nodeBundlePath = path.join(
__dirname,
"build",
`${bundleFileName}-node-bundle.js`
);

// Bundle for the web
await bundleWithWebpack(bundleSourcePath, webBundlePath, "web");

// Bundle for Node
await bundleWithWebpack(bundleSourcePath, nodeBundlePath, "node");
}
})();
104 changes: 104 additions & 0 deletions build/add-1-object-to-an-array-bundle-source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@

import { types } from "mobx-state-tree";
import Benchmark from "benchmark"; // This is technically a no-op in the web bundle, but included for cross-compatibility

var suite = new Benchmark.Suite();

/**
* getStartMemory is an isomorphic way to get the memory used, in bytes,
* either from the browser or from Node.
*/
const getStartMemory = () => {
if (typeof performance.memory !== "undefined") {
return performance.memory.usedJSHeapSize;
} else {
const used = process.memoryUsage();
return used.heapUsed;
}
};

/**
* getEndMemory is an isomorphic way to get the memory used, in bytes,
* either from the browser or from Node.
*/
const getEndMemory = () => {
if (typeof performance.memory !== "undefined") {
return performance.memory.usedJSHeapSize;
} else {
const used = process.memoryUsage();
return used.heapUsed;
}
};

/**
* trackMaxMemory maintains a map of memory usage for each scenario.
*
* It uses keys that correspond to the title of each scenario, and the
* value is the maximum memory used for that scenario.
*/
const memoryUsage = {};
const trackMaxMemory = (title, memoryUsed) => {
if (!memoryUsage[title]) {
memoryUsage[title] = memoryUsed;
} else {
memoryUsage[title] = Math.max(memoryUsage[title], memoryUsed);
}
};

suite.on("complete", function () {
const headers = [
"scenario",
"ops_per_sec",
"margin_of_error",
"runs",
"max_memory_used_kb",
];
const results = suite.filter("successful").map((benchmark) => {
return {
scenario: benchmark.name,
opsSec: benchmark.hz,
plusMinus: benchmark.stats.rme,
runs: benchmark.stats.sample.length,
maxMemory: memoryUsage[benchmark.name],
};
});

console.log(headers.join(","));
results.forEach((result) => {
console.log(result.scenario + "," + result.opsSec + "," + result.plusMinus + "," + result.runs + "," + result.maxMemory);
});
});

suite.add("Add 1 object to an array", () => {
const startMemory = getStartMemory();
const SampleModel = types.model({
name: types.string,
id: types.string,
});

const SampleStore = types
.model({
items: types.array(SampleModel),
})
.actions((self) => ({
add(name, id) {
self.items.push({ name, id });
},
}));

/**
* Requested by the community in https://github.com/coolsoftwaretyler/mst-performance-testing/issues/26
*/
const store = SampleStore.create({ items: [] });
store.add(`item-1`, `id-1`);

const endMemory = getEndMemory();
const memoryUsed = endMemory - startMemory;
trackMaxMemory("Add 1 object to an array", memoryUsed);
});

suite.on("cycle", function (event) {
console.log(String(event.target));
});

suite.run();
2 changes: 2 additions & 0 deletions build/add-1-object-to-an-array-node-bundle.js

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions build/add-1-object-to-an-array-node-bundle.js.LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
2 changes: 2 additions & 0 deletions build/add-1-object-to-an-array-web-bundle.js

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions build/add-1-object-to-an-array-web-bundle.js.LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*!
* Benchmark.js <https://benchmarkjs.com/>
* Copyright 2010-2016 Mathias Bynens <https://mths.be/>
* Based on JSLitmus.js, copyright Robert Kieffer <http://broofa.com/>
* Modified by John-David Dalton <http://allyoucanleet.com/>
* Available under MIT license <https://mths.be/mit>
*/

/*!
* Platform.js v1.3.6
* Copyright 2014-2020 Benjamin Tan
* Copyright 2011-2013 John-David Dalton
* Available under MIT license
*/

/*! *****************************************************************************
Copyright (c) Microsoft Corporation.

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */

/**
* @license
* Lodash <https://lodash.com/>
* Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
* Released under MIT license <https://lodash.com/license>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/
Loading