chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:23:53 +08:00
commit bf6f0825b2
1681 changed files with 296950 additions and 0 deletions
+113
View File
@@ -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>
View File
+38
View File
@@ -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>
+359
View File
@@ -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);
}
}
+86
View File
@@ -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) {}
});
+78
View File
@@ -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;
}
+101
View File
@@ -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()