Skip to content

Commit c554564

Browse files
authored
chore: add bench pr yaml (#52)
1 parent 34d26bc commit c554564

File tree

6 files changed

+226
-6
lines changed

6 files changed

+226
-6
lines changed

.github/workflows/pr-bench.yaml

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
name: Bench PR
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
prNumber:
7+
description: 'PR number'
8+
required: true
9+
type: string
10+
product:
11+
description: 'product name of which to bench'
12+
required: true
13+
type: string
14+
repo:
15+
description: 'repo name of which to bench'
16+
required: true
17+
type: string
18+
19+
jobs:
20+
create-comment:
21+
runs-on: ubuntu-latest
22+
outputs:
23+
comment-id: ${{ steps.create-comment.outputs.result }}
24+
steps:
25+
- id: create-comment
26+
uses: actions/github-script@v6
27+
with:
28+
github-token: ${{ secrets.RSPACK_BOT_ACCESS_TOKEN }}
29+
result-encoding: string
30+
script: |
31+
const url = `${context.serverUrl}//${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`
32+
const urlLink = `[Open](${url})`
33+
34+
const { data: comment } = await github.rest.issues.createComment({
35+
issue_number: context.payload.inputs.prNumber,
36+
owner: context.repo.owner,
37+
repo: context.payload.inputs.repo,
38+
body: `⏳ Triggered benchmark: ${urlLink}`
39+
})
40+
return comment.id
41+
run-bench:
42+
runs-on: ${{ fromJSON(vars.SELF_LINUX_LABELS || '"ubuntu-latest"') }}
43+
timeout-minutes: 30
44+
needs: create-comment
45+
outputs:
46+
diff-result: ${{ steps.print-results.outputs.diff-result }}
47+
steps:
48+
- name: Print runner context
49+
env:
50+
RUNNER_CONTEXT: ${{ toJson(runner) }}
51+
run: echo "$RUNNER_CONTEXT"
52+
53+
- name: Checkout
54+
uses: actions/checkout@v3
55+
with:
56+
fetch-depth: 1
57+
58+
- name: Setup Node.js
59+
uses: actions/setup-node@v3
60+
with:
61+
node-version: 16
62+
cache-dependency-path: pnpm-lock.yaml
63+
64+
- name: Install Pnpm
65+
run: corepack enable && pnpm -v && pnpm store path
66+
67+
- name: Install Dependencies
68+
run: pnpm run install:scripts
69+
70+
- id: print-results
71+
name: 🚀 Run specified benchmark and Print results
72+
run: |
73+
result=$(cd scripts && PR_NUMBER=${{ inputs.prNumber }} pnpm start ${{ inputs.product }})
74+
echo "$result"
75+
echo "diff-result=${result//$'\n'/'@@'}" >> $GITHUB_OUTPUT
76+
update-comment:
77+
runs-on: ubuntu-latest
78+
needs: [create-comment, run-bench]
79+
if: always()
80+
steps:
81+
- uses: actions/github-script@v6
82+
with:
83+
github-token: ${{ secrets.RSPACK_BOT_ACCESS_TOKEN }}
84+
script: |
85+
const diffResult = `${{ needs.run-bench.outputs.diff-result }}`
86+
let result = "task ${{ needs.run-bench.result }}"
87+
if (diffResult) {
88+
result = diffResult.replace(/@@/g, "\n");
89+
}
90+
91+
const url = `${context.serverUrl}//${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`
92+
const urlLink = `[Open](${url})`
93+
const body = `
94+
📝 Benchmark detail: ${urlLink}
95+
96+
${result}
97+
`
98+
99+
await github.rest.issues.updateComment({
100+
owner: context.repo.owner,
101+
repo: context.payload.inputs.repo,
102+
comment_id: `${{ needs.create-comment.outputs.comment-id }}`,
103+
body
104+
})

scripts/src/main.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import logger from 'consola';
22
import { dev } from './runners/dev';
33
import { build } from './runners/build';
4-
import { cloneRepo, getDataPath, mergeMetrics } from './shared';
4+
import {
5+
DefaultBenchCase,
6+
cloneRepo,
7+
getDataPath,
8+
mergeMetrics,
9+
} from './shared';
510
import { remove } from 'fs-extra';
611
import { yarnInstall } from './runners/yarn-install';
12+
import { compare } from './shared/compare';
713

814
const productName = process.argv[2] || 'MODERNJS_FRAMEWORK';
9-
const caseName = process.argv[3] || 'app-minimal';
15+
const caseName =
16+
process.argv[3] ||
17+
DefaultBenchCase[productName as keyof typeof DefaultBenchCase];
1018

1119
async function main() {
1220
const dataPath = getDataPath(productName);
@@ -35,7 +43,11 @@ async function main() {
3543
console.log('failed to collect install size metrics:', err);
3644
}
3745

38-
await mergeMetrics(productName, caseName);
46+
const jsonPath = await mergeMetrics(productName, caseName);
47+
48+
if (process.env.PR_NUMBER) {
49+
await compare(jsonPath);
50+
}
3951
} else {
4052
logger.error(`Case not found: ${productName} ${caseName}`);
4153
}

scripts/src/shared/compare.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { readJson } from 'fs-extra';
2+
import { Metrics } from './types';
3+
4+
const formatFileSize = (size: number, target = 'KB') => {
5+
if (target === 'KB') {
6+
size = size / 1024;
7+
} else if (target === 'MB') {
8+
size = size / 1024 / 1024;
9+
}
10+
return Number(size.toFixed(2));
11+
};
12+
13+
const formatSecond = (ms: number) => Number((ms / 1000).toFixed(2));
14+
15+
function formatValue(value: number, property: string) {
16+
if (property.endsWith('Time')) {
17+
return String(formatSecond(value)) + 's';
18+
} else if (property === 'installSize') {
19+
return String(formatFileSize(value, 'MB')) + 'MB';
20+
} else if (property.endsWith('Size')) {
21+
return String(formatFileSize(value)) + 'KB';
22+
} else {
23+
return String(value);
24+
}
25+
}
26+
27+
function generateTable(
28+
base: Record<string, number>,
29+
current: Record<string, number>,
30+
) {
31+
const properties = Object.keys(base);
32+
33+
const maxPropertyLength = Math.max(...properties.map(p => p.length));
34+
35+
const table = [
36+
`| Metric${' '.repeat(maxPropertyLength - 6)} | Base${' '.repeat(
37+
6,
38+
)} | Current${' '.repeat(3)} | %${' '.repeat(9)} |`,
39+
`|${'-'.repeat(maxPropertyLength + 2)}|${'-'.repeat(12)}|${'-'.repeat(
40+
12,
41+
)}|${'-'.repeat(12)}|`,
42+
];
43+
44+
properties.forEach(property => {
45+
if (property === 'time') return;
46+
const percent =
47+
((current[property] - base[property]) * 100) / base[property];
48+
const formattedPercent =
49+
percent > 0 ? `+${percent.toFixed(2)}%` : `${percent.toFixed(2)}%`;
50+
51+
const row = `| ${property.padEnd(maxPropertyLength)} | ${formatValue(
52+
base[property],
53+
property,
54+
).padEnd(10)} | ${formatValue(current[property], property).padEnd(
55+
10,
56+
)} | ${formattedPercent.padEnd(10)} |`;
57+
table.push(row);
58+
});
59+
60+
return table.join('\n');
61+
}
62+
63+
export async function compare(jsonPath: string) {
64+
const allMetrics: Metrics[] = await readJson(jsonPath);
65+
const keys = Object.keys(allMetrics);
66+
const currentKey = keys[keys.length - 1];
67+
const baseKey = keys[keys.length - 2];
68+
const current = allMetrics[currentKey as any];
69+
const base = allMetrics[baseKey as any];
70+
71+
const formatTable = generateTable(base, current);
72+
console.log(formatTable);
73+
}

scripts/src/shared/fs.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,10 @@ const cleanData = (nums: number[]) => {
102102
return nums;
103103
};
104104

105-
export async function mergeMetrics(productName: string, caseName: string) {
105+
export async function mergeMetrics(
106+
productName: string,
107+
caseName: string,
108+
): Promise<string> {
106109
const { jsonPath, jsonName, remoteURL } = await getMetricsPath(
107110
productName,
108111
caseName,
@@ -143,4 +146,6 @@ export async function mergeMetrics(productName: string, caseName: string) {
143146
await outputJson(jsonPath, allData);
144147
logger.success(`Successfully merged metrics to ${jsonName}.`);
145148
}
149+
150+
return jsonPath;
146151
}

scripts/src/shared/git.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ export async function cloneRepo(productName: string, caseName: string) {
1515
const repoName = getRepoName(productName);
1616
const localRepoPath = getRepoPath(repoName);
1717
if (!(await pathExists(localRepoPath))) {
18-
const { GITHUB_ACTOR, GITHUB_TOKEN, COMMIT_ID } = process.env;
18+
const { GITHUB_ACTOR, GITHUB_TOKEN, COMMIT_ID, PR_NUMBER } = process.env;
1919
const repoURL = GITHUB_TOKEN
2020
? `https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/web-infra-dev/${repoName}.git`
2121
: `[email protected]:web-infra-dev/${repoName}.git`;
2222

2323
const options = ['clone', '--single-branch'];
24-
if (!COMMIT_ID) {
24+
25+
if (!COMMIT_ID && !PR_NUMBER) {
2526
options.push('--depth', '1');
2627
}
2728

@@ -38,6 +39,24 @@ export async function cloneRepo(productName: string, caseName: string) {
3839
stdout: 'inherit',
3940
});
4041
}
42+
43+
if (PR_NUMBER) {
44+
const branchName = String(new Date().valueOf());
45+
await execa(
46+
'git',
47+
['fetch', 'origin', `pull/${PR_NUMBER}/head:${branchName}`],
48+
{
49+
cwd: localRepoPath,
50+
stderr: 'inherit',
51+
stdout: 'inherit',
52+
},
53+
);
54+
await execa('git', ['checkout', `${branchName}`], {
55+
cwd: localRepoPath,
56+
stderr: 'inherit',
57+
stdout: 'inherit',
58+
});
59+
}
4160
}
4261

4362
if (process.env.ONLY_INSTALL_SIZE === 'true') {

scripts/src/shared/product.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,10 @@ export enum REPO_NAME {
1313
RSPRESS = 'rspress',
1414
RSPACK = 'rspack',
1515
}
16+
17+
export const DefaultBenchCase = {
18+
MODERNJS_FRAMEWORK: 'app-arco-pro-rspack',
19+
MODERNJS_MODULE: 'module-library',
20+
RSPRESS: 'rspress-website',
21+
RSBUILD: 'rsbuild-react',
22+
};

0 commit comments

Comments
 (0)