Skip to content

Commit 2bfd074

Browse files
committed
ci(bench): add real-world linter benchmarks
1 parent d2afdcf commit 2bfd074

File tree

4 files changed

+121
-2
lines changed

4 files changed

+121
-2
lines changed

.github/workflows/benchmark.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ jobs:
9090
mv target/release/deps/${{ matrix.component }}-* target/codspeed/simulation/oxc_benchmark
9191
rm target/codspeed/simulation/oxc_benchmark/*.d
9292
93+
# Download github.com/calcom/cal.com repository for linter benchmark
94+
- name: Download files for benchmark
95+
if: ${{ matrix.component == 'linter' }}
96+
run: |
97+
REPOSITORY="https://github.com/calcom/cal.com"
98+
git clone --depth 1 $REPOSITORY calcom
99+
echo "CALCOM_PATH=$(pwd)/calcom" >> $GITHUB_ENV
100+
93101
- name: Run benchmark
94102
uses: CodSpeedHQ/action@972e3437949c89e1357ebd1a2dbc852fcbc57245 # v4.5.1
95103
timeout-minutes: 30

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tasks/benchmark/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ criterion2 = { workspace = true }
8282
# Only for lexer benchmark
8383
cow-utils = { workspace = true, optional = true }
8484

85+
# Only for linter benchmark
86+
ignore = { workspace = true, optional = true }
87+
8588
# Only for NAPI benchmark
8689
serde = { workspace = true, optional = true }
8790
serde_json = { workspace = true, optional = true }
@@ -117,6 +120,7 @@ linter = [
117120
"dep:oxc_semantic",
118121
"dep:oxc_span",
119122
"dep:oxc_tasks_common",
123+
"dep:ignore",
120124
"oxc_semantic/cfg",
121125
]
122126

tasks/benchmark/benches/linter.rs

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
use std::{path::Path, sync::Arc};
1+
use std::{
2+
path::{Path, PathBuf},
3+
sync::Arc,
4+
};
25

6+
use oxc_span::SourceType;
37
use rustc_hash::FxHashMap;
48

59
use oxc_allocator::Allocator;
@@ -61,5 +65,107 @@ fn bench_linter(criterion: &mut Criterion) {
6165
group.finish();
6266
}
6367

64-
criterion_group!(linter, bench_linter);
68+
struct RepoTestFile {
69+
path: PathBuf,
70+
source_type: SourceType,
71+
source_text: String,
72+
}
73+
74+
impl RepoTestFile {
75+
fn new(path: PathBuf) -> Self {
76+
let source_type = SourceType::from_path(&path).expect("invalid source type");
77+
let source_text = std::fs::read_to_string(&path).expect("Failed to read source file");
78+
Self { path, source_type, source_text }
79+
}
80+
}
81+
82+
fn bench_linter_real_world(criterion: &mut Criterion) {
83+
let mut group = criterion.benchmark_group("linter_real_world");
84+
85+
let calcom_path = std::env::var("CALCOM_PATH").expect("CALCOM_PATH env var not set");
86+
87+
// TODO: Enable walker to lint whole repository
88+
// let walker = ignore::WalkBuilder::new(&calcom_path)
89+
// .ignore(false)
90+
// .git_global(false)
91+
// .git_ignore(true)
92+
// .follow_links(true)
93+
// .hidden(false)
94+
// .require_git(false)
95+
// .build_parallel();
96+
97+
// Read source files once, outside of the benchmark
98+
let mut test_files = Vec::new();
99+
// TODO: Add files from walker here.
100+
test_files.push(RepoTestFile::new(
101+
format!("{}{}", calcom_path, "/apps/web/components/PageWrapper.tsx").into(),
102+
));
103+
test_files.push(RepoTestFile::new(
104+
format!("{}{}", calcom_path, "/apps/web/components/PageWrapperAppDir.tsx").into(),
105+
));
106+
test_files.push(RepoTestFile::new(
107+
format!("{}{}", calcom_path, "/apps/web/components/EnterprisePage.tsx").into(),
108+
));
109+
110+
// Build the linter config once, outside of the benchmark
111+
let mut external_plugin_store = ExternalPluginStore::default();
112+
let lint_config = ConfigStoreBuilder::all().build(&mut external_plugin_store).unwrap();
113+
let linter = Linter::new(
114+
LintOptions::default(),
115+
ConfigStore::new(lint_config, FxHashMap::default(), external_plugin_store),
116+
None,
117+
)
118+
.with_fix(FixKind::None);
119+
120+
// Create allocator outside of bench_function so same allocator is used for
121+
// both the warmup and measurement phases
122+
let mut allocator = Allocator::default();
123+
124+
// Group all of the files together as a single benchmark for cal.com
125+
group.bench_function("calcom_repo", |b| {
126+
b.iter_with_setup_wrapper(|runner| {
127+
// Reset allocator at start of each iteration to reclaim memory
128+
allocator.reset();
129+
130+
// Step 1: Parse all files first
131+
let parser_results: Vec<_> = test_files
132+
.iter()
133+
.map(|test_file| {
134+
Parser::new(&allocator, &test_file.source_text, test_file.source_type).parse()
135+
})
136+
.collect();
137+
138+
// Step 2: Build semantic analysis for each file
139+
let lint_inputs: Vec<_> = test_files
140+
.iter()
141+
.zip(parser_results.iter())
142+
.map(|(test_file, parser_ret)| {
143+
let semantic_ret = SemanticBuilder::new()
144+
.with_scope_tree_child_ids(true)
145+
.with_cfg(true)
146+
.build(&parser_ret.program);
147+
let semantic = semantic_ret.semantic;
148+
let module_record = Arc::new(ModuleRecord::new(
149+
&test_file.path,
150+
&parser_ret.module_record,
151+
&semantic,
152+
));
153+
(&test_file.path, semantic, module_record)
154+
})
155+
.collect();
156+
157+
runner.run(|| {
158+
for (path, semantic, module_record) in lint_inputs {
159+
linter.run(
160+
path,
161+
vec![ContextSubHost::new(semantic, Arc::clone(&module_record), 0)],
162+
&allocator,
163+
);
164+
}
165+
});
166+
});
167+
});
168+
}
169+
170+
criterion_group!(linter, bench_linter, bench_linter_real_world);
65171
criterion_main!(linter);

0 commit comments

Comments
 (0)