chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as all_benchmarks from "./cross_platform_suite.mjs";
|
||||
import * as perspective_bench from "./src/js/benchmark.mjs";
|
||||
|
||||
import { createRequire } from "node:module";
|
||||
import * as url from "node:url";
|
||||
|
||||
const __dirname = url.fileURLToPath(new URL(".", import.meta.url)).slice(0, -1);
|
||||
|
||||
const _require = createRequire(import.meta.url);
|
||||
|
||||
/**
|
||||
* We use the `dependencies` of this package for the benchmark candidate
|
||||
* module list, so that we only need specify the dependencies and benchmark
|
||||
* candidates in one place.
|
||||
*/
|
||||
const VERSIONS = Object.keys(
|
||||
JSON.parse(fs.readFileSync(_require.resolve(`./package.json`)))
|
||||
.dependencies,
|
||||
);
|
||||
|
||||
fs.mkdirSync(path.join(__dirname, "./dist"), { recursive: true });
|
||||
perspective_bench.suite(
|
||||
// "ws://localhost:8082/websocket",
|
||||
["@perspective-dev/client", ...VERSIONS],
|
||||
path.join(__dirname, "dist/benchmark-js.arrow"),
|
||||
async function (path, version_idx) {
|
||||
let client, metadata;
|
||||
if (path.startsWith("ws://")) {
|
||||
console.log(path);
|
||||
const { default: perspective } = await import(
|
||||
"@perspective-dev/client"
|
||||
);
|
||||
client = await perspective.websocket(path);
|
||||
metadata = {
|
||||
version: "4.5.2",
|
||||
version_idx,
|
||||
};
|
||||
} else {
|
||||
const perspective = await import(path);
|
||||
const pkg_json = JSON.parse(
|
||||
fs.readFileSync(_require.resolve(`${path}/package.json`)),
|
||||
);
|
||||
|
||||
let version = pkg_json.version;
|
||||
console.log(`${path} (${pkg_json.name}@${version})`);
|
||||
if (version === "@perspective-dev/client") {
|
||||
version = `${version} (master)`;
|
||||
}
|
||||
|
||||
client = perspective.default || perspective;
|
||||
metadata = { version, version_idx };
|
||||
}
|
||||
|
||||
globalThis.__SEND__ = (x) => process.send(x);
|
||||
|
||||
await all_benchmarks.table_suite(client, metadata);
|
||||
await all_benchmarks.view_suite(client, metadata);
|
||||
await all_benchmarks.to_data_suite(client, metadata);
|
||||
await all_benchmarks.join_suite(client, metadata);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,111 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
export const CASES = [
|
||||
{
|
||||
name: "Y Line (split)",
|
||||
config: {
|
||||
plugin: "Y Line",
|
||||
columns: ["Sales"],
|
||||
// group_by: ["Category"],
|
||||
split_by: ["Region"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Y Line",
|
||||
config: {
|
||||
plugin: "Y Line",
|
||||
columns: ["Sales"],
|
||||
// group_by: ["Category"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Y Bar",
|
||||
config: {
|
||||
plugin: "Y Bar",
|
||||
columns: ["Sales"],
|
||||
// group_by: ["Category"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "X Bar",
|
||||
config: {
|
||||
plugin: "X Bar",
|
||||
columns: ["Sales"],
|
||||
// group_by: ["Category"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Y Area",
|
||||
config: {
|
||||
plugin: "Y Area",
|
||||
columns: ["Sales"],
|
||||
// group_by: ["Category"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Y Scatter",
|
||||
config: {
|
||||
plugin: "Y Scatter",
|
||||
columns: ["Sales"],
|
||||
// group_by: ["Category"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "X/Y Scatter",
|
||||
config: {
|
||||
plugin: "X/Y Scatter",
|
||||
columns: ["Quantity", "Profit"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Heatmap",
|
||||
config: {
|
||||
plugin: "Heatmap",
|
||||
columns: ["Sales"],
|
||||
group_by: ["Category"],
|
||||
// split_by: ["Region"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Treemap",
|
||||
config: {
|
||||
plugin: "Treemap",
|
||||
columns: ["Sales"],
|
||||
// group_by: ["Category", "Sub-Category"],
|
||||
},
|
||||
},
|
||||
// {
|
||||
// name: "Sunburst",
|
||||
// config: {
|
||||
// plugin: "Sunburst",
|
||||
// columns: ["Sales"],
|
||||
// // group_by: ["State", "City"],
|
||||
// },
|
||||
// },
|
||||
{
|
||||
name: "Candlestick",
|
||||
config: {
|
||||
plugin: "Candlestick",
|
||||
columns: ["Open", "Close", "High", "Low"],
|
||||
// group_by: ["Category"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Y OHLC",
|
||||
config: {
|
||||
plugin: "OHLC",
|
||||
columns: ["Open", "Close", "High", "Low"],
|
||||
// group_by: ["Category"],
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,59 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
// Installed into the page once via `page.addScriptTag`. Exposes:
|
||||
// __BENCH_RESTORE__(config) - restore the viewer with `config`, drain RAFs,
|
||||
// cache the active plugin and a fresh View.
|
||||
// __BENCH_DRAW__() - drive one full plugin.draw(view) and return
|
||||
// its wall-clock duration (ms).
|
||||
//
|
||||
// Important: `viewer.getView()` returns the *session-owned* View, not a clone.
|
||||
// We never call `.delete()` on it — the viewer destroys it on the next
|
||||
// `restore()`. See viewer.rs::getView docs.
|
||||
|
||||
(() => {
|
||||
let _view = null;
|
||||
let _plugin = null;
|
||||
|
||||
async function waitFrames(n) {
|
||||
for (let i = 0; i < n; i++) {
|
||||
await new Promise((r) => requestAnimationFrame(r));
|
||||
}
|
||||
}
|
||||
|
||||
window.__BENCH_RESTORE__ = async function (config) {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.reset();
|
||||
await viewer.restore(config);
|
||||
// Drain the viewer's own scheduled render so the GL context is sized
|
||||
// and warm before we start timing.
|
||||
await viewer.flush();
|
||||
await waitFrames(2);
|
||||
|
||||
_plugin = viewer.getPlugin();
|
||||
_view = await viewer.getView();
|
||||
};
|
||||
|
||||
window.__BENCH_DRAW__ = async function () {
|
||||
if (!_plugin || !_view) {
|
||||
throw new Error("__BENCH_DRAW__: call __BENCH_RESTORE__ first");
|
||||
}
|
||||
|
||||
// `plugin.draw(view)` performs the full upload + GL submit. The
|
||||
// returned promise resolves once `viewToColumnDataMap` has streamed
|
||||
// every chunk through `_renderChunkData`. That is the unit we want.
|
||||
const t0 = performance.now();
|
||||
await _plugin.draw(_view);
|
||||
const t1 = performance.now();
|
||||
return t1 - t0;
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,135 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as url from "node:url";
|
||||
import * as process from "node:process";
|
||||
|
||||
import { chromium } from "@playwright/test";
|
||||
|
||||
import * as perspective_bench from "./src/js/benchmark.mjs";
|
||||
import { CASES } from "./charts_cases.mjs";
|
||||
|
||||
const __dirname = url.fileURLToPath(new URL(".", import.meta.url)).slice(0, -1);
|
||||
const PAGE_HARNESS = fs.readFileSync(
|
||||
path.join(__dirname, "charts_page_harness.js"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
// Match the SwiftShader / DPR=1 launch flags from
|
||||
// `tools/test/playwright.config.ts` so frame timings come from the
|
||||
// deterministic software GL path, not whatever GPU the host has.
|
||||
const LAUNCH_ARGS = [
|
||||
"--no-sandbox",
|
||||
"--disable-setuid-sandbox",
|
||||
"--disable-dev-shm-usage",
|
||||
'--proxy-server="direct://"',
|
||||
"--proxy-bypass-list=*",
|
||||
"--js-flags=--expose-gc",
|
||||
"--enable-precise-memory-info",
|
||||
"--disable-background-timer-throttling",
|
||||
"--disable-renderer-backgrounding",
|
||||
"--disable-backgrounding-occluded-windows",
|
||||
// "--use-gl=swiftshader",
|
||||
// "--use-angle=swiftshader",
|
||||
];
|
||||
|
||||
fs.mkdirSync(path.join(__dirname, "./dist"), { recursive: true });
|
||||
|
||||
// Single-version run — the workspace build. The `suite()` harness still
|
||||
// expects an array; we only need one entry.
|
||||
const VERSIONS = ["@perspective-dev/viewer-charts"];
|
||||
|
||||
perspective_bench.suite(
|
||||
VERSIONS,
|
||||
path.join(__dirname, "dist/benchmark-charts.arrow"),
|
||||
async function run_charts_bench(_path, version_idx) {
|
||||
const version = "workspace";
|
||||
const browser = await chromium.launch({
|
||||
headless: false,
|
||||
args: LAUNCH_ARGS,
|
||||
});
|
||||
|
||||
try {
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1280, height: 720 },
|
||||
deviceScaleFactor: 1,
|
||||
});
|
||||
const page = await context.newPage();
|
||||
|
||||
page.on("pageerror", (err) =>
|
||||
console.error("page error:", err.message),
|
||||
);
|
||||
page.on("console", (msg) => {
|
||||
if (msg.type() === "error") {
|
||||
console.error("page console.error:", msg.text());
|
||||
}
|
||||
});
|
||||
|
||||
// The bench WebSocketServer (port 8081) mounts
|
||||
// `["src/html/", "../.."]` relative to cwd. Our cwd is
|
||||
// `tools/bench`, so `src/html/charts-bench.html` is served at
|
||||
// `/charts-bench.html` and `/node_modules/...` resolves through
|
||||
// the workspace root.
|
||||
await page.goto("http://localhost:8081/charts-bench.html");
|
||||
|
||||
await page.waitForFunction(
|
||||
() => window["__TEST_PERSPECTIVE_READY__"] === true,
|
||||
null,
|
||||
{ timeout: 60_000 },
|
||||
);
|
||||
|
||||
await page.addScriptTag({ content: PAGE_HARNESS });
|
||||
for (const c of CASES) {
|
||||
// Run one `benchmark()` per case in the page context. The
|
||||
// bench harness's browser branch uses `performance.now()`
|
||||
// around our `test()`, and `__SEND__` collects observations
|
||||
// we forward to the parent over IPC.
|
||||
const items = await page.evaluate(
|
||||
async ({ caseSpec, version, version_idx }) => {
|
||||
const { benchmark } = await import(
|
||||
"/tools/bench/src/js/benchmark.mjs"
|
||||
);
|
||||
|
||||
const total = [];
|
||||
window.__SEND__ = (x) => total.push(x);
|
||||
await window.__BENCH_RESTORE__(caseSpec.config);
|
||||
await benchmark({
|
||||
name: `${caseSpec.name}`,
|
||||
metadata: { version, version_idx },
|
||||
async after() {
|
||||
await new Promise((x) =>
|
||||
requestAnimationFrame(x),
|
||||
);
|
||||
},
|
||||
async test() {
|
||||
await window.__BENCH_DRAW__();
|
||||
},
|
||||
});
|
||||
|
||||
return total;
|
||||
},
|
||||
{ caseSpec: c, version, version_idx },
|
||||
);
|
||||
|
||||
for (const item of items) {
|
||||
process.send(item);
|
||||
}
|
||||
|
||||
// await page.pause();
|
||||
}
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,344 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { benchmark } from "./src/js/benchmark.mjs";
|
||||
import {
|
||||
check_version_gte,
|
||||
new_superstore_table,
|
||||
} from "./src/js/superstore.mjs";
|
||||
|
||||
export async function join_suite(perspective, metadata) {
|
||||
if (check_version_gte(metadata.version, "4.4.0")) {
|
||||
async function before_all() {
|
||||
const left = await perspective.table(
|
||||
new_superstore_table(metadata),
|
||||
);
|
||||
|
||||
const columns = await left.columns();
|
||||
const expressions = Object.fromEntries(
|
||||
columns
|
||||
.filter((x) => x !== "Row ID")
|
||||
.map((x) => [`${x}_2`, `"${x}"`]),
|
||||
);
|
||||
|
||||
const view = await left.view({
|
||||
columns: ["Row ID", ...Object.keys(expressions)],
|
||||
expressions,
|
||||
});
|
||||
|
||||
const right = await perspective.table(await view.to_arrow());
|
||||
await view.delete();
|
||||
return { left, right };
|
||||
}
|
||||
|
||||
async function after_all({ left, right }) {
|
||||
await left.delete();
|
||||
await right.delete();
|
||||
}
|
||||
|
||||
await benchmark({
|
||||
name: `.join()`,
|
||||
before_all,
|
||||
after_all,
|
||||
metadata,
|
||||
async after(_, joined) {
|
||||
await joined.delete();
|
||||
},
|
||||
async test({ left, right }) {
|
||||
return await perspective.join(left, right, "Row ID");
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function to_data_suite(perspective, metadata) {
|
||||
async function before_all() {
|
||||
const table = await perspective.table(new_superstore_table(metadata));
|
||||
const view = await table.view();
|
||||
return { table, view };
|
||||
}
|
||||
|
||||
async function after_all({ table, view }) {
|
||||
if (check_version_gte(metadata.version, "2.10.9")) {
|
||||
await view.delete();
|
||||
}
|
||||
|
||||
if (check_version_gte(metadata.version, "3.0.0")) {
|
||||
await table.delete();
|
||||
}
|
||||
}
|
||||
|
||||
await benchmark({
|
||||
name: `.to_arrow()`,
|
||||
before_all,
|
||||
after_all,
|
||||
metadata,
|
||||
async test({ view }) {
|
||||
const _arrow = await view.to_arrow();
|
||||
},
|
||||
});
|
||||
|
||||
await benchmark({
|
||||
name: `.to_csv()`,
|
||||
before_all,
|
||||
after_all,
|
||||
metadata,
|
||||
async test({ view }) {
|
||||
const _csv = await view.to_csv();
|
||||
},
|
||||
});
|
||||
|
||||
await benchmark({
|
||||
name: `.to_columns()`,
|
||||
before_all,
|
||||
after_all,
|
||||
metadata,
|
||||
async test({ view }) {
|
||||
const _columns = await view.to_columns();
|
||||
},
|
||||
});
|
||||
|
||||
await benchmark({
|
||||
name: `.to_json()`,
|
||||
before_all,
|
||||
after_all,
|
||||
metadata,
|
||||
async test({ view }) {
|
||||
const _json = await view.to_json();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function view_suite(perspective, metadata) {
|
||||
async function before_all() {
|
||||
const table = await perspective.table(new_superstore_table(metadata));
|
||||
|
||||
const schema = await table.schema();
|
||||
return { table, schema };
|
||||
}
|
||||
|
||||
async function after_all({ table }) {
|
||||
if (check_version_gte(metadata.version, "3.0.0")) {
|
||||
await table.delete();
|
||||
}
|
||||
}
|
||||
|
||||
async function after({ table }, view) {
|
||||
if (check_version_gte(metadata.version, "2.10.9")) {
|
||||
await view.delete();
|
||||
}
|
||||
}
|
||||
|
||||
await benchmark({
|
||||
name: `.view()`,
|
||||
before_all,
|
||||
after_all,
|
||||
after,
|
||||
metadata,
|
||||
async test({ table }) {
|
||||
return await table.view();
|
||||
},
|
||||
});
|
||||
|
||||
await benchmark({
|
||||
name: `.view({group_by})`,
|
||||
before_all,
|
||||
after_all,
|
||||
after,
|
||||
metadata,
|
||||
async test({ table }) {
|
||||
if (check_version_gte(metadata.version, "1.2.0")) {
|
||||
return await table.view({ group_by: ["Product Name"] });
|
||||
} else {
|
||||
return await table.view({ row_pivots: ["Product Name"] });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await benchmark({
|
||||
name: `.view({expressions})`,
|
||||
before_all,
|
||||
after_all,
|
||||
after,
|
||||
metadata,
|
||||
async test({ table }) {
|
||||
if (check_version_gte(metadata.version, "2.7.0")) {
|
||||
return await table.view({
|
||||
columns: ["AAA"],
|
||||
expressions: {
|
||||
AAA: `("Sales" + "Profit") / 2`,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return await table.view({
|
||||
columns: ["AAA"],
|
||||
expressions: [`//AAA\n("Sales" + "Profit") / 2`],
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await benchmark({
|
||||
name: `.view({group_by, aggregates: "median"})`,
|
||||
before_all,
|
||||
after_all,
|
||||
after,
|
||||
metadata,
|
||||
async test({ table, schema }) {
|
||||
const columns = ["Sales", "Quantity", "City"];
|
||||
const aggregates = Object.fromEntries(
|
||||
Object.keys(schema).map((x) => [x, "median"]),
|
||||
);
|
||||
|
||||
if (check_version_gte(metadata.version, "1.2.0")) {
|
||||
return await table.view({
|
||||
group_by: ["State"],
|
||||
aggregates,
|
||||
columns,
|
||||
});
|
||||
} else {
|
||||
return await table.view({
|
||||
row_pivots: ["State"],
|
||||
aggregates,
|
||||
columns,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function table_suite(perspective, metadata) {
|
||||
async function before_all() {
|
||||
try {
|
||||
const table = await perspective.table(
|
||||
new_superstore_table(metadata),
|
||||
);
|
||||
|
||||
const view = await table.view();
|
||||
const csv = await view.to_csv();
|
||||
const arrow = await view.to_arrow();
|
||||
const json = await view.to_json();
|
||||
const columns = await view.to_columns();
|
||||
if (check_version_gte(metadata.version, "2.10.9")) {
|
||||
await view.delete();
|
||||
}
|
||||
|
||||
if (check_version_gte(metadata.version, "3.0.0")) {
|
||||
await table.delete();
|
||||
}
|
||||
|
||||
return { csv, arrow, table, json, columns };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (check_version_gte(metadata.version, "2.3.0")) {
|
||||
await benchmark({
|
||||
name: `.table(arrow, {limit: 1000})`,
|
||||
before_all,
|
||||
metadata,
|
||||
async after(_, table) {
|
||||
if (check_version_gte(metadata.version, "3.0.0")) {
|
||||
await table.delete();
|
||||
}
|
||||
},
|
||||
async test({ arrow }) {
|
||||
return await perspective.table(arrow.slice(), { limit: 1000 });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await benchmark({
|
||||
name: `.table(arrow)`,
|
||||
before_all,
|
||||
metadata,
|
||||
async after(_, table) {
|
||||
if (check_version_gte(metadata.version, "3.0.0")) {
|
||||
await table.delete();
|
||||
}
|
||||
},
|
||||
async test({ table, arrow }) {
|
||||
return await perspective.table(arrow.slice());
|
||||
},
|
||||
});
|
||||
|
||||
if (check_version_gte(metadata.version, "3.0.0")) {
|
||||
await benchmark({
|
||||
name: `table.update(arrow)`,
|
||||
before_all,
|
||||
metadata,
|
||||
async before({ arrow }) {
|
||||
let table2 = await perspective.table(arrow.slice(), {
|
||||
limit: 1000,
|
||||
});
|
||||
return table2;
|
||||
},
|
||||
async after(_, table) {
|
||||
if (!check_version_gte(metadata.version, "3.4.3")) {
|
||||
// Bug with old versions of perspective segfault when you delete
|
||||
// a table with pending updates.
|
||||
await table.size();
|
||||
}
|
||||
await table.delete();
|
||||
},
|
||||
async test({ arrow }, table2) {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await table2.update(arrow.slice());
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await benchmark({
|
||||
name: `.table(csv)`,
|
||||
before_all,
|
||||
metadata,
|
||||
async after(_, table) {
|
||||
if (check_version_gte(metadata.version, "3.0.0")) {
|
||||
await table.delete();
|
||||
}
|
||||
},
|
||||
async test({ csv }) {
|
||||
return await perspective.table(csv);
|
||||
},
|
||||
});
|
||||
|
||||
await benchmark({
|
||||
name: `.table(json)`,
|
||||
before_all,
|
||||
metadata,
|
||||
async after(_, table) {
|
||||
if (check_version_gte(metadata.version, "3.0.0")) {
|
||||
await table.delete();
|
||||
}
|
||||
},
|
||||
|
||||
async test({ table, json }) {
|
||||
return await perspective.table(json);
|
||||
},
|
||||
});
|
||||
|
||||
await benchmark({
|
||||
name: `.table(columns)`,
|
||||
before_all,
|
||||
metadata,
|
||||
async after(_, table) {
|
||||
if (check_version_gte(metadata.version, "3.0.0")) {
|
||||
await table.delete();
|
||||
}
|
||||
},
|
||||
async test({ table, columns }) {
|
||||
return await perspective.table(columns);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"name": "@perspective-dev/bench",
|
||||
"version": "2.0.1",
|
||||
"description": "Benchmark utility based on perspective",
|
||||
"private": true,
|
||||
"main": "src/js/bench.mjs",
|
||||
"files": [
|
||||
"src/**/*",
|
||||
"perspective-bench"
|
||||
],
|
||||
"bin": "perspective-bench",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/perspective-dev/perspective/packages/bench"
|
||||
},
|
||||
"scripts": {
|
||||
"bench_js": "node --experimental-wasm-memory64 basic_suite.mjs",
|
||||
"bench_python": "node --experimental-wasm-memory64 python_suite.mjs",
|
||||
"bench_charts": "node --experimental-wasm-memory64 charts_suite.mjs"
|
||||
},
|
||||
"author": "",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@perspective-dev/client": "workspace:",
|
||||
"@perspective-dev/viewer-charts": "workspace:",
|
||||
"@playwright/test": "catalog:",
|
||||
"microtime": "catalog:",
|
||||
"superstore-arrow": "catalog:",
|
||||
"zx": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"perspective-4-5-1": "npm:@perspective-dev/client@4.5.1",
|
||||
"perspective-4-5-0": "npm:@perspective-dev/client@4.5.0",
|
||||
"perspective-4-4-0": "npm:@perspective-dev/client@4.4.0",
|
||||
"perspective-4-3-0": "npm:@perspective-dev/client@4.3.0",
|
||||
"perspective-4-2-0": "npm:@perspective-dev/client@4.2.0",
|
||||
"perspective-4-1-0": "npm:@perspective-dev/client@4.1.0",
|
||||
"perspective-4-0-0": "npm:@perspective-dev/client@4.0.0",
|
||||
"perspective-3-8-0": "npm:@finos/perspective@3.8.0",
|
||||
"perspective-3-7-0": "npm:@finos/perspective@3.7.0",
|
||||
"perspective-3-6-0": "npm:@finos/perspective@3.6.0",
|
||||
"perspective-3-5-0": "npm:@finos/perspective@3.5.0",
|
||||
"perspective-3-4-0": "npm:@finos/perspective@3.4.0",
|
||||
"perspective-3-3-0": "npm:@finos/perspective@3.3.0",
|
||||
"perspective-3-2-0": "npm:@finos/perspective@3.2.0",
|
||||
"perspective-3-1-0": "npm:@finos/perspective@3.1.0",
|
||||
"perspective-3-0-0": "npm:@finos/perspective@3.0.0",
|
||||
"perspective-2-10-0": "npm:@finos/perspective@2.10.0",
|
||||
"perspective-2-9-0": "npm:@finos/perspective@2.9.0",
|
||||
"perspective-2-8-0": "npm:@finos/perspective@2.8.0",
|
||||
"perspective-2-7-0": "npm:@finos/perspective@2.7.0",
|
||||
"perspective-2-6-0": "npm:@finos/perspective@2.6.0",
|
||||
"perspective-2-5-0": "npm:@finos/perspective@2.5.0",
|
||||
"perspective-2-4-0": "npm:@finos/perspective@2.4.0",
|
||||
"perspective-2-3-0": "npm:@finos/perspective@2.3.0",
|
||||
"perspective-2-2-0": "npm:@finos/perspective@2.2.0",
|
||||
"perspective-2-1-0": "npm:@finos/perspective@2.1.0",
|
||||
"perspective-1-1-0": "npm:@finos/perspective@1.1.0",
|
||||
"perspective-1-0-0": "npm:@finos/perspective@1.0.0",
|
||||
"perspective-0-10-0": "npm:@finos/perspective@0.10.0",
|
||||
"perspective-0-9-0": "npm:@finos/perspective@0.9.0"
|
||||
},
|
||||
"deprecatedDependencies": {
|
||||
"perspective-2-0-0": "npm:@finos/perspective@2.0.0",
|
||||
"perspective-1-9-0": "npm:@finos/perspective@1.9.0",
|
||||
"perspective-1-8-0": "npm:@finos/perspective@1.8.0",
|
||||
"perspective-1-7-0": "npm:@finos/perspective@1.7.0",
|
||||
"perspective-1-6-0": "npm:@finos/perspective@1.6.0",
|
||||
"perspective-1-5-0": "npm:@finos/perspective@1.5.0",
|
||||
"perspective-1-4-0": "npm:@finos/perspective@1.4.0",
|
||||
"perspective-1-3-0": "npm:@finos/perspective@1.3.0",
|
||||
"perspective-1-2-0": "npm:@finos/perspective@1.2.0"
|
||||
}
|
||||
}
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { registerCmd } = require("@perspective-dev/bench");
|
||||
registerCmd();
|
||||
@@ -0,0 +1,85 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import * as all_benchmarks from "./cross_platform_suite.mjs";
|
||||
import * as perspective_bench from "./src/js/benchmark.mjs";
|
||||
import * as puppeteer from "puppeteer";
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as url from "node:url";
|
||||
import * as process from "node:process";
|
||||
|
||||
const __dirname = url.fileURLToPath(new URL(".", import.meta.url)).slice(0, -1);
|
||||
|
||||
/**
|
||||
* We use the `dependencies` of this package for the benchmark candidate
|
||||
* module list, so that we only need specify the dependencies and benchmark
|
||||
* candidates in one place.
|
||||
*/
|
||||
const VERSIONS = [
|
||||
"@perspective-dev/client",
|
||||
"perspective-3-0-0",
|
||||
"perspective-2-10-0",
|
||||
];
|
||||
|
||||
perspective_bench.suite(
|
||||
[...VERSIONS],
|
||||
path.join(__dirname, "dist/benchmark-js.arrow"),
|
||||
async function (path, version_idx) {
|
||||
let client, metadata;
|
||||
console.log(path);
|
||||
const browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
protocolTimeout: 100_000_000,
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
|
||||
await page.goto("http://localhost:8081/empty.html");
|
||||
|
||||
async function test_suite(suite) {
|
||||
const items = await page.evaluate(
|
||||
async ([version, suite, version_idx]) => {
|
||||
const { default: perspective } = await import(
|
||||
`/tools/bench/node_modules/${version}/dist/esm/perspective.inline.js`
|
||||
);
|
||||
const benchmarks = await import(
|
||||
"/tools/bench/cross_platform_suite.mjs"
|
||||
);
|
||||
|
||||
const metadata = {
|
||||
version,
|
||||
version_idx,
|
||||
};
|
||||
const total = [];
|
||||
window.__SEND__ = (x) => {
|
||||
total.push(x);
|
||||
};
|
||||
|
||||
const client = await perspective.worker();
|
||||
await benchmarks[suite](client, metadata);
|
||||
return total;
|
||||
},
|
||||
[path, suite, version_idx],
|
||||
);
|
||||
|
||||
for (const { obs_records, stats } of items) {
|
||||
process.send({ obs_records, stats });
|
||||
}
|
||||
}
|
||||
|
||||
await test_suite("table_suite");
|
||||
await test_suite("view_suite");
|
||||
await test_suite("to_data_suite");
|
||||
await test_suite("join_suite");
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,79 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
/**
|
||||
* @module python_suite
|
||||
*
|
||||
* Run the Python benchmarks against hte Node.js client.
|
||||
*/
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as url from "node:url";
|
||||
|
||||
import * as python from "./src/js/servers/python.mjs";
|
||||
import * as all_benchmarks from "./cross_platform_suite.mjs";
|
||||
import * as perspective_bench from "./src/js/benchmark.mjs";
|
||||
|
||||
const __dirname = url.fileURLToPath(new URL(".", import.meta.url)).slice(0, -1);
|
||||
|
||||
const CLIENT_VERSION = {
|
||||
master: "@perspective-dev/client",
|
||||
"4.5.1": "perspective-4-5-1",
|
||||
"4.5.0": "perspective-4-5-0",
|
||||
"4.4.0": "perspective-4-4-0",
|
||||
"4.3.0": "perspective-4-3-0",
|
||||
"4.2.0": "perspective-4-2-0",
|
||||
"4.1.0": "perspective-4-1-0",
|
||||
"4.0.0": "perspective-4-0-0",
|
||||
"3.8.0": "perspective-3-8-0",
|
||||
"3.6.0": "perspective-3-6-0",
|
||||
"3.5.0": "perspective-3-5-0",
|
||||
"3.4.0": "perspective-3-4-0",
|
||||
"3.3.0": "perspective-3-3-0",
|
||||
"3.2.0": "perspective-3-2-0",
|
||||
"3.1.0": "perspective-3-1-0",
|
||||
"3.0.3": "perspective-3-0-0",
|
||||
"2.10.1": "perspective-2-10-0",
|
||||
"2.9.0": "perspective-2-9-0",
|
||||
"2.8.0": "perspective-2-8-0",
|
||||
"2.7.0": "perspective-2-7-0",
|
||||
"2.6.0": "perspective-2-6-0",
|
||||
"2.5.0": "perspective-2-5-0",
|
||||
"2.4.0": "perspective-2-4-0",
|
||||
"2.3.2": "perspective-2-3-0",
|
||||
"2.3.1": "perspective-2-3-0",
|
||||
};
|
||||
|
||||
fs.mkdirSync(path.join(__dirname, "./dist"), { recursive: true });
|
||||
|
||||
perspective_bench.suite(
|
||||
[...Object.keys(CLIENT_VERSION)],
|
||||
path.join(__dirname, "dist/benchmark-python.arrow"),
|
||||
async function (version, version_idx) {
|
||||
console.log(version);
|
||||
const { default: perspective } = await import(CLIENT_VERSION[version]);
|
||||
const client = await perspective.websocket(
|
||||
"ws://127.0.0.1:8082/websocket",
|
||||
{ maxPayload: 1024 * 1024 * 1024 },
|
||||
);
|
||||
|
||||
const metadata = { version, version_idx };
|
||||
globalThis.__SEND__ = (x) => process.send(x);
|
||||
await all_benchmarks.table_suite(client, metadata);
|
||||
await all_benchmarks.view_suite(client, metadata);
|
||||
await all_benchmarks.to_data_suite(client, metadata);
|
||||
await all_benchmarks.join_suite(client, metadata);
|
||||
},
|
||||
python.start,
|
||||
python.stop,
|
||||
);
|
||||
@@ -0,0 +1,80 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
const fs = require("fs");
|
||||
import * as all_benchmarks from "./cross_platform_suite.mjs";
|
||||
import * as perspective_bench from "./src/js/benchmark.mjs";
|
||||
|
||||
/**
|
||||
* We use the `dependencies` of this package for the benchmark candidate
|
||||
* module list, so that we only need specify the dependencies and benchmark
|
||||
* candidates in one place.
|
||||
*/
|
||||
const VERSIONS = Object.keys(
|
||||
JSON.parse(fs.readFileSync(require.resolve(`./package.json`))).dependencies,
|
||||
);
|
||||
|
||||
perspective_bench.suite(
|
||||
// ["@perspective-dev/client", ...VERSIONS],
|
||||
[...VERSIONS],
|
||||
async function (path, version_idx) {
|
||||
let perspective = await import(path);
|
||||
perspective = perspective.default || perspective;
|
||||
const pkg_json = JSON.parse(
|
||||
fs.readFileSync(require.resolve(`${path}/package.json`)),
|
||||
);
|
||||
|
||||
let version = pkg_json.version;
|
||||
console.log(`${path} (${pkg_json.name}@${version})`);
|
||||
if (version_idx === 0) {
|
||||
version = `${version} (master)`;
|
||||
}
|
||||
|
||||
const metadata = { version, version_idx };
|
||||
await all_benchmarks.table_suite(perspective, metadata);
|
||||
// await all_benchmarks.view_suite(perspective, metadata);
|
||||
// await all_benchmarks.to_data_suite(perspective, metadata);
|
||||
},
|
||||
async function (path) {
|
||||
let psp = await import(path);
|
||||
psp = psp.default || psp;
|
||||
// const port = Math.floor(Math.random() * 1000) + 1000;
|
||||
const server = new psp.WebSocketServer({
|
||||
port: 8080,
|
||||
});
|
||||
|
||||
function buffer_to_arraybuffer(buffer) {
|
||||
return new Int8Array(
|
||||
buffer.buffer.slice(
|
||||
buffer.byteOffset,
|
||||
buffer.byteOffset + buffer.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const table = await psp.table(
|
||||
buffer_to_arraybuffer(
|
||||
fs.readFileSync(
|
||||
"../../node_modules/superstore-arrow/superstore.arrow",
|
||||
),
|
||||
).buffer,
|
||||
{ name: "superstore" },
|
||||
);
|
||||
|
||||
// // Legacy compat
|
||||
if (server.host_table) {
|
||||
server.host_table("superstore", table);
|
||||
}
|
||||
|
||||
return server;
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,113 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="module" src="/node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js"></script>
|
||||
<script type="module" src="/node_modules/@perspective-dev/viewer-datagrid/dist/cdn/perspective-viewer-datagrid.js"></script>
|
||||
<script type="module" src="/node_modules/@perspective-dev/viewer-charts/dist/cdn/perspective-viewer-charts.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="/node_modules/@perspective-dev/viewer/dist/css/pro.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@fontsource/roboto-mono/400.css" />
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
perspective-viewer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
font-family: "Roboto Mono" !important;
|
||||
--preload-fonts: "Roboto Mono:400";
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="module">
|
||||
import perspective from "/node_modules/@perspective-dev/client/dist/cdn/perspective.js";
|
||||
|
||||
// Synthetic dataset for chart benchmarking. The columns are
|
||||
// chosen to exercise the same chart shapes as the real data
|
||||
// (group_by / split_by / two-numeric-axes), without depending
|
||||
// on superstore.csv parsing time.
|
||||
const ROW_COUNT = 10_000;
|
||||
const CATEGORIES = ["Furniture", "Office Supplies", "Technology", "Apparel", "Books"];
|
||||
|
||||
const REGIONS = ["North", "South", "East", "West", "Central"];
|
||||
const SUBCATS = ["Phones", "Chairs", "Tables", "Storage", "Binders", "Paper", "Art", "Accessories", "Copiers", "Machines"];
|
||||
|
||||
function build_data(n) {
|
||||
const Category = new Array(n);
|
||||
const Region = new Array(n);
|
||||
const SubCategory = new Array(n);
|
||||
const Sales = new Array(n);
|
||||
const Profit = new Array(n);
|
||||
const Quantity = new Array(n);
|
||||
const Discount = new Array(n);
|
||||
const Open = new Array(n);
|
||||
const High = new Array(n);
|
||||
const Low = new Array(n);
|
||||
const Close = new Array(n);
|
||||
|
||||
// Deterministic pseudo-random so iterations are comparable.
|
||||
let seed = 0x9e3779b9;
|
||||
const rand = () => {
|
||||
seed |= 0;
|
||||
seed = (seed + 0x6d2b79f5) | 0;
|
||||
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
||||
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
||||
};
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
Category[i] = CATEGORIES[i % CATEGORIES.length];
|
||||
Region[i] = REGIONS[(i * 7) % REGIONS.length];
|
||||
SubCategory[i] = SUBCATS[(i * 13) % SUBCATS.length];
|
||||
const base = 100 + rand() * 900;
|
||||
Sales[i] = base;
|
||||
Profit[i] = (rand() - 0.4) * base * 0.3;
|
||||
Quantity[i] = 1 + Math.floor(rand() * 20);
|
||||
Discount[i] = Math.round(rand() * 100) / 100;
|
||||
const o = base;
|
||||
const c = base + (rand() - 0.5) * base * 0.05;
|
||||
Open[i] = o;
|
||||
Close[i] = c;
|
||||
High[i] = Math.max(o, c) + rand() * base * 0.02;
|
||||
Low[i] = Math.min(o, c) - rand() * base * 0.02;
|
||||
}
|
||||
|
||||
return {
|
||||
Category,
|
||||
Region,
|
||||
"Sub-Category": SubCategory,
|
||||
Sales,
|
||||
Profit,
|
||||
Quantity,
|
||||
Discount,
|
||||
Open,
|
||||
High,
|
||||
Low,
|
||||
Close,
|
||||
};
|
||||
}
|
||||
|
||||
await customElements.whenDefined("perspective-viewer");
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
const worker = await perspective.worker();
|
||||
const table = await worker.table(build_data(ROW_COUNT), {
|
||||
name: "charts-bench",
|
||||
});
|
||||
|
||||
await viewer.load(table);
|
||||
window.__TEST_WORKER__ = worker;
|
||||
window.__TEST_PERSPECTIVE_READY__ = true;
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<perspective-viewer></perspective-viewer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,38 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
|
||||
<script type="module" src="/node_modules/@perspective-dev/viewer-datagrid/dist/cdn/perspective-viewer-datagrid.js"></script>
|
||||
<script type="module" src="/node_modules/@perspective-dev/viewer-charts/dist/cdn/perspective-viewer-charts.js"></script>
|
||||
<link rel="stylesheet" crossorigin="anonymous" href="/node_modules/@perspective-dev/viewer/dist/css/themes.css" />
|
||||
<style>
|
||||
perspective-viewer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<perspective-viewer></perspective-viewer>
|
||||
<script type="module">
|
||||
import "/node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js";
|
||||
import perspective from "/node_modules/@perspective-dev/client/dist/cdn/perspective.js";
|
||||
const el = document.getElementsByTagName("perspective-viewer")[0];
|
||||
el.addEventListener("perspective-config-update", async () => {
|
||||
const config = await el.save();
|
||||
delete config["table"];
|
||||
localStorage.setItem("layout", JSON.stringify(config));
|
||||
});
|
||||
|
||||
const websocket = await perspective.websocket("ws://localhost:8081/subscribe");
|
||||
const table = await websocket.open_table("benchmarks");
|
||||
const worker = await perspective.worker();
|
||||
const table2 = await worker.table(await table.view());
|
||||
el.load(table2);
|
||||
el.restore(JSON.parse(localStorage.getItem("layout")));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,359 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
/**
|
||||
* @module perspective_bench
|
||||
* The main entrypoint of `perspective_bench` is a CLI node.js application which
|
||||
* may star
|
||||
*/
|
||||
|
||||
const MAX_ITERATIONS = 100;
|
||||
const MIN_ITERATIONS = 5;
|
||||
const WARM_UP_ITERATIONS = 10;
|
||||
|
||||
/**
|
||||
* Utility function to push/filter.
|
||||
* @param {*} a
|
||||
* @param {*} x
|
||||
*/
|
||||
function push_if(a, x) {
|
||||
if (x !== undefined) {
|
||||
a.push(x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate avg fora key.
|
||||
* @param {*} a
|
||||
* @param {*} key
|
||||
* @returns
|
||||
*/
|
||||
function avg(a, key) {
|
||||
return a.map((x) => x[key]).reduce((x, y) => x + y, 0) / a.length / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate stddev for a key.
|
||||
* @param {*} array
|
||||
* @param {*} key
|
||||
* @returns
|
||||
*/
|
||||
function stddev(array, key) {
|
||||
const n = array.length;
|
||||
const mean = array.reduce((a, b) => a + b[key] / 1000, 0) / n;
|
||||
return Math.sqrt(
|
||||
array
|
||||
.map((x) => Math.pow(x[key] / 1000 - mean, 2))
|
||||
.reduce((a, b) => a + b) / n,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate outliers
|
||||
* @param {*} someArray
|
||||
* @returns
|
||||
*/
|
||||
function markOutliers(someArray) {
|
||||
var values = someArray.concat();
|
||||
values.sort(function (a, b) {
|
||||
return a.real_time - b.real_time;
|
||||
});
|
||||
|
||||
var q1 = values[Math.floor(values.length / 4)].real_time;
|
||||
var q3 = values[Math.ceil(values.length * (3 / 4))].real_time;
|
||||
var iqr = q3 - q1;
|
||||
var maxValue = q3 + iqr * 1.5;
|
||||
var minValue = q1 - iqr * 1.5;
|
||||
return someArray.map(function (x) {
|
||||
x.outlier = !(x.real_time <= maxValue && x.real_time >= minValue);
|
||||
return x;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a list to arrow and write it to disk. `@perspective-dev/client` is
|
||||
* imported in this scope to prevent interpreter-wide side effects of the
|
||||
* library from impacting forked processes, based on an observation that some
|
||||
* runs inline had anomalies across many observations that couldn't be explained
|
||||
* by contemporary system load.
|
||||
* @param {Array} obs_records an array of records to persist
|
||||
*/
|
||||
async function persist_to_arrow(benchmarks_table, out_path = "") {
|
||||
const fs = await import("node:fs");
|
||||
const view = await benchmarks_table.view();
|
||||
const arrow = await view.to_arrow();
|
||||
fs.writeFileSync(out_path, Buffer.from(arrow), "binary");
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a single benchmark case, reporting the runtime
|
||||
* @param {*} param0
|
||||
* @returns An observation record with timing and metadata fields for a single
|
||||
* iteration of this case.
|
||||
*/
|
||||
async function benchmark_case({
|
||||
args,
|
||||
before,
|
||||
test,
|
||||
benchmark,
|
||||
metadata,
|
||||
after,
|
||||
i,
|
||||
}) {
|
||||
const args2 = args.slice();
|
||||
push_if(args2, await before?.(...args2));
|
||||
if (typeof process !== "undefined") {
|
||||
const { default: process } = await import("node:process");
|
||||
const { default: microtime } = await import("microtime");
|
||||
globalThis.gc(false);
|
||||
await new Promise((x) => setTimeout(x, 0));
|
||||
const start_time = microtime.now();
|
||||
const start_cpu = process.cpuUsage();
|
||||
const x = await test(...args2);
|
||||
const end = process.cpuUsage(start_cpu);
|
||||
const end_time = microtime.now() - start_time;
|
||||
push_if(args2, x);
|
||||
await after?.(...args2);
|
||||
return {
|
||||
cpu_time: end.user + end.system,
|
||||
real_time: end_time,
|
||||
user_time: end.user,
|
||||
system_time: end.system,
|
||||
benchmark,
|
||||
iteration: i,
|
||||
...structuredClone(metadata),
|
||||
};
|
||||
} else {
|
||||
const start_time = performance.now();
|
||||
const x = await test(...args2);
|
||||
const end_time = performance.now() - start_time;
|
||||
push_if(args2, x);
|
||||
await after?.(...args2);
|
||||
return {
|
||||
real_time: end_time * 1000,
|
||||
benchmark,
|
||||
iteration: i,
|
||||
...structuredClone(metadata),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the benchmarks in a forked process and colelct the observations.
|
||||
* @param {{version: string, i: number}} version the versions spec to send to
|
||||
* the child process.
|
||||
* @returns an array of observation records for this version.
|
||||
*/
|
||||
async function benchmark_node_version(version, benchmarks_table) {
|
||||
const cp = await import("node:child_process");
|
||||
const process = await import("node:process");
|
||||
const path = await import("node:path");
|
||||
const suite_path = path.join(process.argv[1]);
|
||||
let stats = [];
|
||||
const worker = cp.fork(suite_path, {
|
||||
execArgv: ["--expose-gc", "--experimental-wasm-memory64"],
|
||||
env: { BENCH_FLAG: "1" },
|
||||
});
|
||||
|
||||
let cont;
|
||||
worker.on("message", (details) => {
|
||||
if (details.finished) {
|
||||
cont();
|
||||
} else {
|
||||
let msg = " - ";
|
||||
if (!isNaN(details.stats.filtered_avg_cpu)) {
|
||||
msg += `${details.stats.filtered_avg_cpu}ms +/-${details.stats.stddev_percent}% (CPU), `;
|
||||
}
|
||||
msg += `${details.stats.filtered_avg_time}ms (Real) ${details.stats.non_outliers}/${details.stats.iterations} iterations - ${details.stats.benchmark}`;
|
||||
console.log(msg);
|
||||
benchmarks_table.update(details.obs_records);
|
||||
stats.push(details.stats);
|
||||
}
|
||||
});
|
||||
|
||||
worker.send({
|
||||
...version,
|
||||
stats,
|
||||
});
|
||||
|
||||
await new Promise((r) => {
|
||||
cont = r;
|
||||
});
|
||||
|
||||
worker.kill();
|
||||
return { stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a benchmark for a test case.
|
||||
* @param {*} param0
|
||||
*/
|
||||
export async function benchmark({
|
||||
name: benchmark,
|
||||
before,
|
||||
before_all,
|
||||
test,
|
||||
after,
|
||||
after_all,
|
||||
args = [],
|
||||
metadata = {},
|
||||
warm_up_iterations = WARM_UP_ITERATIONS,
|
||||
max_iterations = MAX_ITERATIONS,
|
||||
min_iterations = MIN_ITERATIONS,
|
||||
max_time = 3_000,
|
||||
} = {}) {
|
||||
let obs_records = [];
|
||||
push_if(args, await before_all?.(...args));
|
||||
const start_time = performance.now();
|
||||
for (let i = 0; i < warm_up_iterations; i++) {
|
||||
await benchmark_case({
|
||||
args,
|
||||
before,
|
||||
test,
|
||||
benchmark,
|
||||
metadata,
|
||||
after,
|
||||
i,
|
||||
});
|
||||
}
|
||||
|
||||
let i;
|
||||
for (i = 0; i < max_iterations; i++) {
|
||||
if (performance.now() - start_time > max_time && i >= min_iterations) {
|
||||
break;
|
||||
}
|
||||
|
||||
obs_records.push(
|
||||
await benchmark_case({
|
||||
args,
|
||||
before,
|
||||
test,
|
||||
benchmark,
|
||||
metadata,
|
||||
after,
|
||||
i,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
obs_records = markOutliers(obs_records);
|
||||
const filtered = obs_records.filter((x) => !x.outlier);
|
||||
const n_outliers = obs_records.length - filtered.length;
|
||||
const filtered_avg_cpu = avg(filtered, "cpu_time");
|
||||
const filtered_avg_time = avg(filtered, "real_time").toFixed(3);
|
||||
const stddev_cpu = stddev(filtered, "cpu_time").toFixed(3);
|
||||
const stats = {
|
||||
benchmark,
|
||||
metadata,
|
||||
filtered_avg_cpu: filtered_avg_cpu.toFixed(3),
|
||||
stddev_percent: ((stddev_cpu / filtered_avg_cpu) * 100).toFixed(1),
|
||||
stddev_cpu,
|
||||
filtered_avg_time,
|
||||
hr_time: avg(filtered, "hr_time"),
|
||||
iterations: i,
|
||||
non_outliers: i - n_outliers,
|
||||
};
|
||||
|
||||
await after_all?.(...args);
|
||||
globalThis.__SEND__({ obs_records, stats });
|
||||
}
|
||||
|
||||
function buffer_to_arraybuffer(buffer) {
|
||||
return new Int8Array(
|
||||
buffer.buffer.slice(
|
||||
buffer.byteOffset,
|
||||
buffer.byteOffset + buffer.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Host a Perspective server to view the live benchmark data as it accumulates.
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
async function start_server({
|
||||
cwd_static_file_handler,
|
||||
make_session,
|
||||
WebSocketServer,
|
||||
}) {
|
||||
return new WebSocketServer({ assets: ["src/html/", "../.."], port: 8081 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a suite of benchmarks to run against a set of packages with
|
||||
* similar APIs.
|
||||
* @param {*} versions
|
||||
* @param {*} run_version_callback
|
||||
*/
|
||||
export async function suite(
|
||||
versions,
|
||||
out_path,
|
||||
run_version_callback,
|
||||
start_server_callback,
|
||||
stop_server_callback,
|
||||
) {
|
||||
if (!!process.env.BENCH_FLAG) {
|
||||
const { default: process } = await import("node:process");
|
||||
process.on("message", async function bench_all({ path, i }) {
|
||||
await run_version_callback(path, i);
|
||||
process.send({ finished: true });
|
||||
});
|
||||
} else {
|
||||
const psp = await import("@perspective-dev/client");
|
||||
const benchmarks_table = await psp.default.table(
|
||||
{
|
||||
version: "string",
|
||||
cpu_time: "float",
|
||||
system_time: "float",
|
||||
user_time: "float",
|
||||
real_time: "float",
|
||||
version_idx: "integer",
|
||||
benchmark: "string",
|
||||
outlier: "boolean",
|
||||
},
|
||||
{ name: "benchmarks" },
|
||||
);
|
||||
|
||||
const app = await start_server(psp);
|
||||
|
||||
for (let i = 0; i < versions.length; i++) {
|
||||
let s;
|
||||
if (start_server_callback) {
|
||||
s = await start_server_callback(versions[i], i);
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
benchmark_node_version(
|
||||
{ path: versions[i], i },
|
||||
benchmarks_table,
|
||||
),
|
||||
// benchmark_puppeteer_version(
|
||||
// { path: versions[i], i },
|
||||
// benchmarks_table
|
||||
// ),
|
||||
// benchmark_version({ path: versions[i], i }, benchmarks_table),
|
||||
// benchmark_version({ path: versions[i], i }, benchmarks_table),
|
||||
// benchmark_version({ path: versions[i], i }, benchmarks_table),
|
||||
]);
|
||||
|
||||
await persist_to_arrow(benchmarks_table, out_path);
|
||||
|
||||
if (stop_server_callback) {
|
||||
await stop_server_callback(s, versions[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
await app.close();
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import "zx/globals";
|
||||
|
||||
let SERVER;
|
||||
|
||||
/**
|
||||
* Create a Python `tornado` server, deleting and re-creating the virtual env
|
||||
* if necessary.
|
||||
*/
|
||||
export async function start(path) {
|
||||
const ac = new AbortController();
|
||||
let proc;
|
||||
await $`rm -rf benchmark_venv`;
|
||||
|
||||
if (path !== "master") {
|
||||
await $`python3 -m venv benchmark_venv`;
|
||||
const $$ = $({
|
||||
ac,
|
||||
prefix: "source benchmark_venv/bin/activate && ",
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
|
||||
await $$`pip3 uninstall "perspective-python" -y`;
|
||||
await $$`pip3 install tornado "perspective-python==${path}"`;
|
||||
proc = $$`python3 src/python/server.py`;
|
||||
} else {
|
||||
proc = $`python3 src/python/server.py`;
|
||||
}
|
||||
|
||||
// Await server start
|
||||
let [resolve, sentinel] = (() => {
|
||||
let resolve;
|
||||
const sentinel = new Promise((x) => {
|
||||
resolve = x;
|
||||
});
|
||||
|
||||
return [resolve, sentinel];
|
||||
})();
|
||||
|
||||
async function await_log(pipe) {
|
||||
for await (const _ of proc[pipe]) {
|
||||
resolve?.();
|
||||
resolve = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
await_log("stdout");
|
||||
await_log("stderr");
|
||||
|
||||
await sentinel;
|
||||
SERVER = { ac, proc };
|
||||
return SERVER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a Python server.
|
||||
* @param {*} server
|
||||
*/
|
||||
export async function stop(server) {
|
||||
server.proc.kill();
|
||||
try {
|
||||
await server.proc;
|
||||
} catch (e) {}
|
||||
|
||||
await server.ac.abort();
|
||||
SERVER = undefined;
|
||||
}
|
||||
|
||||
process.on("SIGTERM", async () => {
|
||||
try {
|
||||
SERVER?.proc?.kill();
|
||||
await SERVER?.proc;
|
||||
await SERVER?.ac?.abort();
|
||||
} catch (e) {}
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
/**
|
||||
* Load a file as an `ArrayBuffer`, which is useful for loading Apache Arrow
|
||||
* Feather files.
|
||||
* @param {*} path
|
||||
* @returns
|
||||
*/
|
||||
async function get_buffer(path) {
|
||||
if (typeof window !== "undefined") {
|
||||
const resp = await fetch(
|
||||
"http://localhost:8080/node_modules/superstore-arrow/superstore.lz4.arrow",
|
||||
);
|
||||
|
||||
return await resp.arrayBuffer();
|
||||
} else {
|
||||
const fs = await import("node:fs");
|
||||
const { createRequire } = await import("node:module");
|
||||
const _require = createRequire(import.meta.url);
|
||||
return fs.readFileSync(_require.resolve(path)).buffer;
|
||||
}
|
||||
}
|
||||
|
||||
const SUPERSTORE_ARROW = await get_buffer("superstore-arrow/superstore.arrow");
|
||||
const SUPERSTORE_FEATHER = await get_buffer(
|
||||
"superstore-arrow/superstore.lz4.arrow",
|
||||
);
|
||||
|
||||
/**
|
||||
* Load the Superstore example data set as either a Feather (LZ4) or
|
||||
* uncompressed `Arrow`, depending on whether Perspective supports Feather.
|
||||
* @param {*} metadata
|
||||
* @returns
|
||||
*/
|
||||
export function new_superstore_table(metadata) {
|
||||
if (check_version_gte(metadata.version, "2.5.0")) {
|
||||
return SUPERSTORE_FEATHER.slice();
|
||||
} else {
|
||||
return SUPERSTORE_ARROW.slice();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a version string e.g. "v1.2.3" is greater or equal to another
|
||||
* version string, which must be of the same length/have the same number of
|
||||
* minor version levels.
|
||||
* @param {*} a
|
||||
* @param {*} b
|
||||
* @returns
|
||||
*/
|
||||
export function check_version_gte(a, b) {
|
||||
a = a.split(".").map((x) => parseInt(x));
|
||||
b = b.split(".").map((x) => parseInt(x));
|
||||
|
||||
if (a.length === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const i in a) {
|
||||
if (a[i] > b[i]) {
|
||||
return true;
|
||||
} else if (a[i] < b[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
# ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
# ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
# This module handles historic versions of `perspective-python`'s API.
|
||||
|
||||
|
||||
import os
|
||||
import os.path
|
||||
|
||||
# import concurrent.futures
|
||||
import threading
|
||||
import tornado
|
||||
import perspective
|
||||
import perspective.handlers.tornado
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
file_path = os.path.join(
|
||||
here, "..", "..", "node_modules", "superstore-arrow", "superstore.lz4.arrow"
|
||||
)
|
||||
|
||||
if perspective.__version__.startswith("2"):
|
||||
from perspective import PerspectiveManager, PerspectiveTornadoHandler
|
||||
|
||||
def perspective_thread(
|
||||
manager,
|
||||
):
|
||||
psp_loop = tornado.ioloop.IOLoop()
|
||||
# [x, y, z] = map(int, perspective.__version__.split("."))
|
||||
# if x > 2 or (x > 1 and y > 2):
|
||||
# # with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
# # manager.set_loop_callback(psp_loop.run_in_executor, executor)
|
||||
# psp_loop.start()
|
||||
# else:
|
||||
# manager.set_loop_callback(psp_loop.add_callback)
|
||||
psp_loop.start()
|
||||
|
||||
def make_app():
|
||||
manager = PerspectiveManager()
|
||||
thread = threading.Thread(target=perspective_thread, args=(manager,))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
return tornado.web.Application(
|
||||
[
|
||||
(
|
||||
r"/websocket",
|
||||
PerspectiveTornadoHandler,
|
||||
{"manager": manager, "check_origin": True},
|
||||
),
|
||||
(
|
||||
r"/node_modules/(.*)",
|
||||
tornado.web.StaticFileHandler,
|
||||
{"path": "../../node_modules/"},
|
||||
),
|
||||
],
|
||||
websocket_max_message_size=1000 * 1024 * 1024,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = make_app()
|
||||
app.listen(8082)
|
||||
loop = tornado.ioloop.IOLoop.current()
|
||||
print("Listening on 8082", flush=True)
|
||||
loop.start()
|
||||
|
||||
else:
|
||||
|
||||
def make_app(perspective_server):
|
||||
return tornado.web.Application(
|
||||
[
|
||||
(
|
||||
r"/websocket",
|
||||
perspective.handlers.tornado.PerspectiveTornadoHandler,
|
||||
{"perspective_server": perspective_server},
|
||||
),
|
||||
(
|
||||
r"/node_modules/(.*)",
|
||||
tornado.web.StaticFileHandler,
|
||||
{"path": "../../node_modules/"},
|
||||
),
|
||||
],
|
||||
websocket_max_message_size=1000 * 1024 * 1024,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
perspective_server = perspective.Server()
|
||||
app = make_app(perspective_server)
|
||||
app.listen(8082)
|
||||
loop = tornado.ioloop.IOLoop.current()
|
||||
client = perspective_server.new_local_client()
|
||||
print("Listening on 8082", flush=True)
|
||||
loop.start()
|
||||
Reference in New Issue
Block a user