Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Benchmark

on:
pull_request:
branches: [master]
types: [opened, synchronize]
paths:
- "packages/dbml-parse/**"

jobs:
benchmark:
runs-on: blacksmith-2vcpu-ubuntu-2204
timeout-minutes: 30
strategy:
matrix:
node-version: [22.x]

steps:
- uses: actions/checkout@v4
with:
# Fetch full history so we can checkout master for comparison
fetch-depth: 0

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Run benchmarks and compare with master
run: npx tsx@4.19.4 .github/workflows/scripts/collect-benchmarks.mts master

- name: Comment benchmark results on PR
if: github.event_name == 'pull_request'
uses: marocchino/sticky-pull-request-comment@v2
with:
header: benchmark-report
path: benchmark-report.md
136 changes: 136 additions & 0 deletions .github/workflows/scripts/collect-benchmarks.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#!/usr/bin/env node

/**
* Collect and compare benchmarks between the current branch and a base branch (default: master).
*
* Usage:
* npx tsx collect-benchmarks.mts [base-branch]
*/

import fs from "node:fs";
import path from "node:path";
import { execSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { MarkdownTable, TableAlignment } from "./utils/markdownTable.mjs";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.join(__dirname, "../../..");
const PKG_DIR = path.join(ROOT, "packages/dbml-parse");
const BENCH_DIR = path.join(PKG_DIR, "__benchmarks__");
const BENCH_OUTPUT = path.join(BENCH_DIR, "output/bench.json");
const TMP_DIR = path.join(ROOT, ".tmp-bench");

interface BenchResult {
ms: number;
error: number;
count: number;
}

type BenchReport = Record<string, BenchResult>;

function main() {
const baseBranch = process.argv[2] || "master";

console.log("Running benchmarks on current branch...");
runBenchmarks();
const current = readReport();

console.log(`\nRunning benchmarks on ${baseBranch}...`);
const baseline = runOnBranch(baseBranch);

const markdown = generateReport(current, baseline, baseBranch);
const reportPath = path.join(ROOT, "benchmark-report.md");
fs.writeFileSync(reportPath, markdown);
console.log(`\nBenchmark report written to: ${reportPath}`);

Check failure on line 44 in .github/workflows/scripts/collect-benchmarks.mts

View check run for this annotation

reviewdog / rdjson

[Bearer] .github/workflows/scripts/collect-benchmarks.mts#L44 <javascript_lang_logger_leak>(https://docs.bearer.com/reference/rules/javascript_lang_logger_leak)

# Leakage of information in logger message ## Description Information leakage through logger messages can compromise sensitive data. This vulnerability arises when dynamic data or variables, which may contain sensitive information, are included in log messages. ## Remediations - **Do not** include sensitive data directly in logger messages. This can lead to the exposure of such data in log files, which might be accessible to unauthorized individuals. ```javascript logger.info(`Results: ${data}`) // unsafe ``` - **Do** use logging levels appropriately to control the verbosity of log output and minimize the risk of leaking sensitive information in production environments.
Raw output
message:"\n# Leakage of information in logger message\n## Description\n\nInformation leakage through logger messages can compromise sensitive data. This vulnerability arises when dynamic data or variables, which may contain sensitive information, are included in log messages.\n\n## Remediations\n\n- **Do not** include sensitive data directly in logger messages. This can lead to the exposure of such data in log files, which might be accessible to unauthorized individuals.\n  ```javascript\n  logger.info(`Results: ${data}`) // unsafe\n  ```\n- **Do** use logging levels appropriately to control the verbosity of log output and minimize the risk of leaking sensitive information in production environments."  location:{path:".github/workflows/scripts/collect-benchmarks.mts"  range:{start:{line:44  column:3}  end:{line:44  column:62}}}  severity:ERROR  source:{name:"Bearer"  url:"https://docs.bearer.com/"}  code:{value:"javascript_lang_logger_leak"  url:"https://docs.bearer.com/reference/rules/javascript_lang_logger_leak"}
console.log("\n" + markdown);

Check failure on line 45 in .github/workflows/scripts/collect-benchmarks.mts

View check run for this annotation

reviewdog / rdjson

[Bearer] .github/workflows/scripts/collect-benchmarks.mts#L45 <javascript_lang_logger_leak>(https://docs.bearer.com/reference/rules/javascript_lang_logger_leak)

# Leakage of information in logger message ## Description Information leakage through logger messages can compromise sensitive data. This vulnerability arises when dynamic data or variables, which may contain sensitive information, are included in log messages. ## Remediations - **Do not** include sensitive data directly in logger messages. This can lead to the exposure of such data in log files, which might be accessible to unauthorized individuals. ```javascript logger.info(`Results: ${data}`) // unsafe ``` - **Do** use logging levels appropriately to control the verbosity of log output and minimize the risk of leaking sensitive information in production environments.
Raw output
message:"\n# Leakage of information in logger message\n## Description\n\nInformation leakage through logger messages can compromise sensitive data. This vulnerability arises when dynamic data or variables, which may contain sensitive information, are included in log messages.\n\n## Remediations\n\n- **Do not** include sensitive data directly in logger messages. This can lead to the exposure of such data in log files, which might be accessible to unauthorized individuals.\n  ```javascript\n  logger.info(`Results: ${data}`) // unsafe\n  ```\n- **Do** use logging levels appropriately to control the verbosity of log output and minimize the risk of leaking sensitive information in production environments."  location:{path:".github/workflows/scripts/collect-benchmarks.mts"  range:{start:{line:45  column:3}  end:{line:45  column:31}}}  severity:ERROR  source:{name:"Bearer"  url:"https://docs.bearer.com/"}  code:{value:"javascript_lang_logger_leak"  url:"https://docs.bearer.com/reference/rules/javascript_lang_logger_leak"}

if (process.env.GITHUB_OUTPUT) {
for (const [suite, v] of Object.entries(current)) {
fs.appendFileSync(process.env.GITHUB_OUTPUT, `dbml-parse-${suite}=${v.ms}\n`);
}
}
}

function runBenchmarks() {
execSync("npx tsx __benchmarks__/compiler.benchmark.ts", {
cwd: PKG_DIR,
stdio: "inherit",
});
}

function readReport(): BenchReport {
if (!fs.existsSync(BENCH_OUTPUT)) {
console.error("No benchmark results found");
process.exit(1);
}
return JSON.parse(fs.readFileSync(BENCH_OUTPUT, "utf8"));
}

function runOnBranch(branch: string): BenchReport | undefined {
const currentBranch = execSync("git rev-parse --abbrev-ref HEAD", {
cwd: ROOT,
encoding: "utf8",
}).trim();

execSync(`mkdir -p '${TMP_DIR}' && cp -r '${BENCH_DIR}/.' '${TMP_DIR}/'`, { cwd: ROOT });

const stashOutput = execSync("git stash", { cwd: ROOT, encoding: "utf8" }).trim();
const didStash = !stashOutput.includes("No local changes");

try {
execSync(`git checkout ${branch}`, { cwd: ROOT, stdio: "inherit" });
execSync(`mkdir -p '${BENCH_DIR}' && cp -r '${TMP_DIR}/.' '${BENCH_DIR}/'`, { cwd: ROOT });

runBenchmarks();
return readReport();
} catch (e) {
console.error(`Failed to run benchmarks on ${branch}:`, (e as Error).message);

Check failure on line 87 in .github/workflows/scripts/collect-benchmarks.mts

View check run for this annotation

reviewdog / rdjson

[Bearer] .github/workflows/scripts/collect-benchmarks.mts#L87 <javascript_lang_logger_leak>(https://docs.bearer.com/reference/rules/javascript_lang_logger_leak)

# Leakage of information in logger message ## Description Information leakage through logger messages can compromise sensitive data. This vulnerability arises when dynamic data or variables, which may contain sensitive information, are included in log messages. ## Remediations - **Do not** include sensitive data directly in logger messages. This can lead to the exposure of such data in log files, which might be accessible to unauthorized individuals. ```javascript logger.info(`Results: ${data}`) // unsafe ``` - **Do** use logging levels appropriately to control the verbosity of log output and minimize the risk of leaking sensitive information in production environments.
Raw output
message:"\n# Leakage of information in logger message\n## Description\n\nInformation leakage through logger messages can compromise sensitive data. This vulnerability arises when dynamic data or variables, which may contain sensitive information, are included in log messages.\n\n## Remediations\n\n- **Do not** include sensitive data directly in logger messages. This can lead to the exposure of such data in log files, which might be accessible to unauthorized individuals.\n  ```javascript\n  logger.info(`Results: ${data}`) // unsafe\n  ```\n- **Do** use logging levels appropriately to control the verbosity of log output and minimize the risk of leaking sensitive information in production environments."  location:{path:".github/workflows/scripts/collect-benchmarks.mts"  range:{start:{line:87  column:5}  end:{line:87  column:82}}}  severity:ERROR  source:{name:"Bearer"  url:"https://docs.bearer.com/"}  code:{value:"javascript_lang_logger_leak"  url:"https://docs.bearer.com/reference/rules/javascript_lang_logger_leak"}
return undefined;
} finally {
execSync(`git checkout ${currentBranch}`, { cwd: ROOT, stdio: "inherit" });
if (didStash) execSync("git stash pop", { cwd: ROOT, stdio: "inherit" });
execSync(`rm -rf '${TMP_DIR}'`, { cwd: ROOT });
}
}

function formatMs(v: BenchResult): string {
const error = Math.round(v.error * 10000) / 100;
return `${v.ms}ms \u00b1${error}%`;
}

function formatChange(current: BenchResult, baseline: BenchResult): string {
const pct = ((current.ms - baseline.ms) / baseline.ms) * 100;
const sign = pct >= 0 ? "+" : "";
const icon = pct <= -5 ? "\uD83D\uDFE2" : pct >= 5 ? "\uD83D\uDD34" : "\u26AA";
return `${icon} ${sign}${pct.toFixed(1)}%`;
}

function generateReport(current: BenchReport, baseline: BenchReport | undefined, baseBranch: string): string {
let md = `## Benchmark Result\n\n`;
md += `### dbml-parse\n\n`;

const table = new MarkdownTable()
.headers(["suite", `\uD83C\uDFE0 ${baseBranch}`, `\uD83D\uDD00 this branch`, "change"])
.align([TableAlignment.Left, TableAlignment.Right, TableAlignment.Right, TableAlignment.Right]);

const allSuites = new Set([
...Object.keys(current),
...(baseline ? Object.keys(baseline) : []),
]);

for (const suite of allSuites) {
const cur = current[suite];
const base = baseline?.[suite];
table.row([
suite,
base ? formatMs(base) : "N/A",
cur ? formatMs(cur) : "N/A",
cur && base ? formatChange(cur, base) : "",
]);
}

md += table.build() + "\n";
return md;
}

main();
218 changes: 0 additions & 218 deletions .github/workflows/scripts/collect-coverage.js

This file was deleted.

Loading
Loading