mirror of
https://github.com/oxc-project/oxc.git
synced 2026-09-14 19:36:11 +08:00
d174661221
Adds CodSpeed's [Memory Instrument](https://codspeed.io/docs/instruments/memory) for the `pipeline` benchmarks: peak memory, total allocated bytes, allocation count, and a memory timeline per benchmark case, tracked with baseline comparison on PRs alongside the existing CPU simulation runs. ## Changes - `generate-benchmark-matrix.js`: entry construction is factored into a `buildMatrixEntries()` helper used by all five construction sites. All entries now carry an explicit `mode`: `simulation` for every component, `simulation,memory` for `pipeline` — so the pipeline job runs both instruments and inherits the existing changed-file affection logic. - `benchmark.yml`: the action's `mode:` input comes from the matrix. Job names and count are unchanged. No build-step changes needed: `cargo-codspeed` maps memory mode to the same `analysis` build the workflow already stages (`MeasurementMode::Memory` → `BuildMode::Analysis`), and the pinned action v4.18.5 supports memory mode (added in v4.17.0) including comma-separated multi-mode runs. Notes: - The first memory numbers on this PR will have no baseline; comparisons start after the post-merge main run. - Memory metrics reflect the bench crate's deliberate `NeverGrowInPlaceAllocator` worst-case realloc behavior — consistent run-to-run, which is what matters for regression tracking. AI Disclosure: This was generated with Claude Code, Fable 5. It has been tested and reviewed by me for correctness.
227 lines
6.3 KiB
JavaScript
Executable File
227 lines
6.3 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
// oxlint-disable no-console
|
|
|
|
/**
|
|
* Generate a dynamic matrix for benchmark jobs based on affected components.
|
|
* This script determines which benchmark components need to run based on changed files.
|
|
*/
|
|
|
|
const process = require("process");
|
|
const { getChangedFiles } = require("./get-changed-files.js");
|
|
const { getCrateDependencies } = require("./utils.js");
|
|
|
|
// All available benchmark components
|
|
const ALL_COMPONENTS = [
|
|
"lexer",
|
|
"parser",
|
|
"transformer",
|
|
"semantic",
|
|
"minifier",
|
|
"codegen",
|
|
"formatter",
|
|
"pipeline",
|
|
"react_compiler",
|
|
"linter",
|
|
];
|
|
|
|
// Files that when changed affect all benchmarks
|
|
const GLOBAL_FILES = [
|
|
"Cargo.lock",
|
|
"rust-toolchain.toml",
|
|
".github/workflows/benchmark.yml",
|
|
".github/scripts/generate-benchmark-matrix.js",
|
|
];
|
|
|
|
/**
|
|
* Check if any global files have changed that affect all benchmarks
|
|
* @param {string[]} changedFiles - Array of changed file paths
|
|
* @returns {boolean} True if global changes detected
|
|
*/
|
|
function checkGlobalChanges(changedFiles) {
|
|
if (!changedFiles || changedFiles.length === 0) {
|
|
return false;
|
|
}
|
|
|
|
for (const file of changedFiles) {
|
|
if (GLOBAL_FILES.some((globalFile) => file === globalFile || file.endsWith(`/${globalFile}`))) {
|
|
console.error(`Global file changed: ${file}`);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Map component to its feature
|
|
* @param {string} component - Component name
|
|
* @returns {string} Feature name
|
|
*/
|
|
function getFeatureForComponent(component) {
|
|
if (component === "linter") {
|
|
return "linter";
|
|
}
|
|
if (component === "react_compiler") {
|
|
return "react_compiler";
|
|
}
|
|
return "compiler";
|
|
}
|
|
|
|
/**
|
|
* Build matrix entries for components.
|
|
* `pipeline` additionally runs the memory instrument in the same job.
|
|
* @param {string[]} components - Component names
|
|
* @returns {Array<{component: string, feature: string, mode: string}>} Matrix entries
|
|
*/
|
|
function buildMatrixEntries(components) {
|
|
return components.map((component) => ({
|
|
component,
|
|
feature: getFeatureForComponent(component),
|
|
mode: component === "pipeline" ? "simulation,memory" : "simulation",
|
|
}));
|
|
}
|
|
|
|
/** @type {Map<string, string[]>} Cache of cargo tree results keyed by feature */
|
|
const depsCache = new Map();
|
|
|
|
/**
|
|
* Get dependencies for a specific benchmark component
|
|
* @param {string} component - Component name
|
|
* @returns {string[]} Array of dependency names
|
|
*/
|
|
function getComponentDependencies(component) {
|
|
const feature = getFeatureForComponent(component);
|
|
|
|
const cached = depsCache.get(feature);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
|
|
const deps = getCrateDependencies("oxc_benchmark", {
|
|
features: feature,
|
|
noDefaultFeatures: true,
|
|
});
|
|
|
|
if (deps.length === 0) {
|
|
console.error(`Warning: Could not get dependencies for ${component} (feature: ${feature})`);
|
|
}
|
|
|
|
depsCache.set(feature, deps);
|
|
return deps;
|
|
}
|
|
|
|
/**
|
|
* Check if a component is affected by the changed files
|
|
* @param {string} component - Component name
|
|
* @param {string[]} changedFiles - Array of changed file paths
|
|
* @returns {boolean} True if component is affected
|
|
*/
|
|
function isComponentAffected(component, changedFiles) {
|
|
if (!changedFiles || changedFiles.length === 0) {
|
|
return false;
|
|
}
|
|
|
|
// Get component dependencies
|
|
const dependencies = getComponentDependencies(component);
|
|
console.error(`Component ${component} dependencies: ${dependencies.join(", ")}`);
|
|
|
|
// Check if any dependency files changed
|
|
for (const dep of dependencies) {
|
|
const depPath = `crates/${dep}/`;
|
|
if (changedFiles.some((file) => file.startsWith(depPath))) {
|
|
console.error(` Component ${component} affected by changes in ${depPath}`);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Determine which components are affected by changes
|
|
* @returns {Promise<Array<{component: string, feature: string, mode: string}>>} Matrix entries for affected components
|
|
*/
|
|
async function determineAffectedComponents() {
|
|
const changedFiles = await getChangedFiles();
|
|
|
|
// Manual trigger - run all benchmarks
|
|
if (changedFiles === null) {
|
|
return buildMatrixEntries(ALL_COMPONENTS);
|
|
}
|
|
|
|
// Check for global changes
|
|
if (checkGlobalChanges(changedFiles)) {
|
|
console.error("Global changes detected - will run all benchmarks");
|
|
return buildMatrixEntries(ALL_COMPONENTS);
|
|
}
|
|
|
|
// Check benchmark and common task files - affects all components
|
|
if (
|
|
changedFiles.some(
|
|
(file) => file.startsWith("tasks/benchmark/") || file.startsWith("tasks/common/"),
|
|
)
|
|
) {
|
|
console.error("Benchmark/common task files changed - will run all benchmarks");
|
|
return buildMatrixEntries(ALL_COMPONENTS);
|
|
}
|
|
|
|
// If no crate files changed, no benchmarks need to run
|
|
if (!changedFiles.some((file) => file.startsWith("crates/"))) {
|
|
console.error("No crate files changed - skipping cargo tree and all benchmarks");
|
|
return [];
|
|
}
|
|
|
|
// Check each component individually
|
|
const affectedComponents = [];
|
|
|
|
for (const component of ALL_COMPONENTS) {
|
|
console.error(`\nChecking component: ${component}`);
|
|
if (isComponentAffected(component, changedFiles)) {
|
|
affectedComponents.push(component);
|
|
}
|
|
}
|
|
|
|
if (affectedComponents.length === 0) {
|
|
console.error("\nNo components were affected by the changes");
|
|
} else {
|
|
console.error(`\nAffected components: ${affectedComponents.join(", ")}`);
|
|
}
|
|
|
|
return buildMatrixEntries(affectedComponents);
|
|
}
|
|
|
|
/**
|
|
* Main entry point
|
|
*/
|
|
async function main() {
|
|
try {
|
|
const affectedComponents = await determineAffectedComponents();
|
|
|
|
// Output the matrix as JSON array
|
|
// This will be captured by GitHub Actions
|
|
console.log(JSON.stringify(affectedComponents));
|
|
|
|
// Set GitHub Actions notice
|
|
if (affectedComponents.length === 0) {
|
|
console.error(
|
|
"::notice title=No benchmarks to run::No components were affected by the changes",
|
|
);
|
|
} else {
|
|
const componentNames = affectedComponents.map((obj) => obj.component).join(", ");
|
|
console.error(`::notice title=Running benchmarks::Affected components: ${componentNames}`);
|
|
}
|
|
|
|
process.exit(0);
|
|
} catch (error) {
|
|
console.error("Error generating benchmark matrix:", error);
|
|
// On error, run all benchmarks as a fallback
|
|
const fallbackMatrix = buildMatrixEntries(ALL_COMPONENTS);
|
|
console.log(JSON.stringify(fallbackMatrix));
|
|
process.exit(0);
|
|
}
|
|
}
|
|
|
|
// Run the script
|
|
void main();
|