Skip to content

Commit 60c6b66

Browse files
authored
feat(vscode): add diagnostics profiling (#110)
2 parents 758d0bd + 75581e3 commit 60c6b66

30 files changed

Lines changed: 2221 additions & 81 deletions

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ serde_json.workspace = true
4848
thiserror.workspace = true
4949
toml = "0.9.8"
5050
tracing-subscriber = { version = "0.3.17", default-features = false, features = ["registry", "fmt", "tracing-log",] }
51+
tracing-chrome = "0.7.2"
5152
tracing.workspace = true
5253
triomphe.workspace = true
5354

crates/base-db/src/source_db.rs

Lines changed: 191 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,20 @@ impl SourceFileKind {
8888
}
8989

9090
fn parse_src(db: &dyn SourceDb, file_id: FileId) -> SyntaxTree {
91+
let _span = tracing::info_span!("slang.parse_src", ?file_id).entered();
9192
let text = db.file_text(file_id);
9293

9394
match db.file_kind(file_id) {
9495
SourceFileKind::SystemVerilog | SourceFileKind::IncludeHeader => {
9596
// HIR source maps are local to the queried file; project-aware include
9697
// expansion belongs to parse_src_for_compilation.
98+
let _span = tracing::info_span!(
99+
"slang.syntax_tree.from_text",
100+
?file_id,
101+
bytes = text.len(),
102+
include_buffer_count = 0usize
103+
)
104+
.entered();
97105
SyntaxTree::from_text_with_options(
98106
&text,
99107
"",
@@ -180,6 +188,7 @@ fn syntax_tree_options_for_file(
180188
db: &dyn SourceRootDb,
181189
file_id: FileId,
182190
) -> syntax::SyntaxTreeOptions {
191+
let _span = tracing::info_span!("slang.syntax_tree_options.file", ?file_id).entered();
183192
let project_config = db.project_config();
184193
let profile_id = db.file_compilation_profile(file_id);
185194
let include_buffers = db.include_buffers_for_profile(profile_id).as_ref().clone();
@@ -202,12 +211,25 @@ fn syntax_tree_options_for_profile(
202211
}
203212

204213
fn parse_src_for_compilation(db: &dyn SourceRootDb, file_id: FileId) -> SyntaxTree {
205-
let text = db.file_text(file_id);
214+
let _span = tracing::info_span!("slang.parse_for_compilation", ?file_id).entered();
215+
let text = {
216+
let _span =
217+
tracing::info_span!("slang.parse_for_compilation.file_text", ?file_id).entered();
218+
db.file_text(file_id)
219+
};
206220
let identity = source_file_identity(db, file_id);
207221

208222
match db.file_kind(file_id) {
209223
SourceFileKind::SystemVerilog | SourceFileKind::IncludeHeader => {
210224
let options = syntax_tree_options_for_file(db, file_id);
225+
let include_buffer_count = options.include_buffers.len();
226+
let _span = tracing::info_span!(
227+
"slang.parse_for_compilation.from_text",
228+
?file_id,
229+
bytes = text.len(),
230+
include_buffer_count
231+
)
232+
.entered();
211233
SyntaxTree::from_text_with_options(&text, &identity.name, &identity.path, &options)
212234
}
213235
SourceFileKind::LibraryMap => {
@@ -257,14 +279,40 @@ fn parse_diagnostics(db: &dyn SourceRootDb, file_id: FileId) -> Arc<[SyntaxDiagn
257279
return Arc::from(Vec::<SyntaxDiagnostic>::new());
258280
}
259281

260-
let tree = db.parse_src_for_compilation(file_id);
282+
let _span = tracing::info_span!("slang.parse_diagnostics", ?file_id).entered();
283+
let tree = {
284+
let _span = tracing::info_span!("slang.parse_diagnostics.parse_tree", ?file_id).entered();
285+
db.parse_src_for_compilation(file_id)
286+
};
261287
let root_buffer_id = tree.buffer_id();
262-
let diags = tree
263-
.diagnostics_with_options(&config.slang.warnings)
264-
.into_iter()
265-
.filter(|diag| diag.buffer_id.is_none_or(|buffer_id| buffer_id == root_buffer_id))
266-
.filter_map(|diag| config.apply_rules(DiagnosticSource::Parse, diag))
267-
.collect::<Vec<_>>();
288+
let raw_diagnostics = {
289+
let _span = tracing::info_span!("slang.parse.raw_diagnostics", ?file_id).entered();
290+
tree.diagnostics_with_options(&config.slang.warnings)
291+
};
292+
let raw_diagnostic_count = raw_diagnostics.len();
293+
let mut non_root_buffer_count = 0usize;
294+
let mut ignored_diagnostic_count = 0usize;
295+
let mut diags = Vec::new();
296+
297+
for diag in raw_diagnostics {
298+
if !diag.buffer_id.is_none_or(|buffer_id| buffer_id == root_buffer_id) {
299+
non_root_buffer_count += 1;
300+
continue;
301+
}
302+
303+
match config.apply_rules(DiagnosticSource::Parse, diag) {
304+
Some(diag) => diags.push(diag),
305+
None => ignored_diagnostic_count += 1,
306+
}
307+
}
308+
309+
tracing::info!(
310+
raw_diagnostic_count,
311+
non_root_buffer_count,
312+
ignored_diagnostic_count,
313+
diagnostic_count = diags.len(),
314+
"parse diagnostics complete"
315+
);
268316
Arc::from(diags)
269317
}
270318

@@ -395,8 +443,21 @@ fn compilation_profile_diagnostics(
395443

396444
let project_config = db.project_config();
397445
let plan = db.compilation_plan_for_profile(Some(profile_id));
398-
let compilation_include_buffers =
399-
compilation_plan::compilation_source_buffers_for_plan(db, &plan);
446+
let compilation_include_buffers = {
447+
let _span = tracing::info_span!("slang.semantic.compilation_buffers").entered();
448+
compilation_plan::compilation_source_buffers_for_plan(db, &plan)
449+
};
450+
let root_count = plan.roots.len();
451+
let top_module_count = plan.top_modules.len();
452+
let include_buffer_count = compilation_include_buffers.len();
453+
let _span = tracing::info_span!(
454+
"slang.compilation_profile_diagnostics",
455+
?profile_id,
456+
root_count,
457+
top_module_count,
458+
include_buffer_count
459+
)
460+
.entered();
400461
let compilation_options = syntax_tree_options_for_profile(
401462
&project_config,
402463
Some(profile_id),
@@ -405,61 +466,135 @@ fn compilation_profile_diagnostics(
405466
let mut compilation = Compilation::new_with_top_modules(&plan.top_modules);
406467
let mut buffer_file_ids = FxHashMap::default();
407468
let path_file_ids = path_file_ids(db);
408-
for file_id in plan.roots.iter().copied() {
409-
let text = db.file_text(file_id);
410-
let identity = source_file_identity(db, file_id);
411-
let buffer_ids = match db.file_kind(file_id) {
412-
SourceFileKind::SystemVerilog => compilation.add_syntax_tree_from_text(
413-
&text,
414-
&identity.name,
415-
&identity.path,
416-
&compilation_options,
417-
),
418-
SourceFileKind::LibraryMap => compilation.add_library_map_syntax_tree_from_text(
419-
&text,
420-
&identity.name,
421-
&identity.path,
422-
),
423-
SourceFileKind::IncludeHeader | SourceFileKind::ProjectManifest => continue,
424-
};
425-
insert_buffer_file_ids(&mut buffer_file_ids, &path_file_ids, buffer_ids, file_id);
469+
let mut compilation_root_count = 0usize;
470+
let mut compilation_buffer_count = 0usize;
471+
{
472+
let _span = tracing::info_span!("slang.semantic.add_roots", root_count).entered();
473+
for file_id in plan.roots.iter().copied() {
474+
let text = {
475+
let _span =
476+
tracing::info_span!("slang.semantic.add_root.file_text", ?file_id).entered();
477+
db.file_text(file_id)
478+
};
479+
let identity = source_file_identity(db, file_id);
480+
let buffer_ids = match db.file_kind(file_id) {
481+
SourceFileKind::SystemVerilog => {
482+
let include_buffer_count = compilation_options.include_buffers.len();
483+
let _span = tracing::info_span!(
484+
"slang.semantic.add_root.from_text",
485+
?file_id,
486+
bytes = text.len(),
487+
include_buffer_count
488+
)
489+
.entered();
490+
compilation.add_syntax_tree_from_text(
491+
&text,
492+
&identity.name,
493+
&identity.path,
494+
&compilation_options,
495+
)
496+
}
497+
SourceFileKind::LibraryMap => compilation.add_library_map_syntax_tree_from_text(
498+
&text,
499+
&identity.name,
500+
&identity.path,
501+
),
502+
SourceFileKind::IncludeHeader | SourceFileKind::ProjectManifest => continue,
503+
};
504+
compilation_root_count += 1;
505+
compilation_buffer_count += 1 + buffer_ids.source_buffers.len();
506+
insert_buffer_file_ids(&mut buffer_file_ids, &path_file_ids, buffer_ids, file_id);
507+
}
426508
}
509+
tracing::info!(
510+
compilation_root_count,
511+
compilation_buffer_count,
512+
mapped_buffer_count = buffer_file_ids.len(),
513+
"semantic compilation roots added"
514+
);
427515

428516
let mut diagnostics = Vec::new();
429517
if config.parse.enabled {
430-
diagnostics.extend(
431-
compilation
432-
.parse_diagnostics_with_options(&config.slang.warnings)
433-
.into_iter()
434-
.filter_map(|diag| {
435-
let diag_file_id = diag
436-
.buffer_id
437-
.and_then(|buffer_id| buffer_file_ids.get(&buffer_id).copied())?;
438-
let diag = config.apply_rules(DiagnosticSource::Parse, diag)?;
439-
Some(CompilationDiagnostic {
440-
file_id: diag_file_id,
441-
source: DiagnosticSource::Parse,
442-
diagnostic: diag,
443-
})
444-
}),
445-
);
446-
}
447-
448-
diagnostics.extend(
449-
compilation
450-
.semantic_diagnostics_with_options(&config.slang.warnings)
451-
.into_iter()
452-
.filter_map(|diag| {
453-
let diag_file_id = diag
518+
let raw_diagnostics = {
519+
let _span = tracing::info_span!("slang.semantic.parse_diagnostics").entered();
520+
compilation.parse_diagnostics_with_options(&config.slang.warnings)
521+
};
522+
let raw_diagnostic_count = raw_diagnostics.len();
523+
let mut unmapped_buffer_count = 0usize;
524+
let mut ignored_diagnostic_count = 0usize;
525+
{
526+
let _span =
527+
tracing::info_span!("slang.semantic.map_parse_diagnostics", raw_diagnostic_count)
528+
.entered();
529+
diagnostics.extend(raw_diagnostics.into_iter().filter_map(|diag| {
530+
let diag_file_id = match diag
454531
.buffer_id
455-
.and_then(|buffer_id| buffer_file_ids.get(&buffer_id).copied())?;
456-
let diag = config.apply_rules(DiagnosticSource::Semantic, diag)?;
532+
.and_then(|buffer_id| buffer_file_ids.get(&buffer_id).copied())
533+
{
534+
Some(file_id) => file_id,
535+
None => {
536+
unmapped_buffer_count += 1;
537+
return None;
538+
}
539+
};
540+
let diag = match config.apply_rules(DiagnosticSource::Parse, diag) {
541+
Some(diag) => diag,
542+
None => {
543+
ignored_diagnostic_count += 1;
544+
return None;
545+
}
546+
};
457547
Some(CompilationDiagnostic {
458548
file_id: diag_file_id,
459-
source: DiagnosticSource::Semantic,
549+
source: DiagnosticSource::Parse,
460550
diagnostic: diag,
461551
})
462-
}),
552+
}));
553+
}
554+
tracing::info!(
555+
raw_diagnostic_count,
556+
unmapped_buffer_count,
557+
ignored_diagnostic_count,
558+
diagnostic_count = diagnostics.len(),
559+
"compilation parse diagnostics complete"
560+
);
561+
}
562+
563+
let raw_semantic_diagnostics = {
564+
let _span = tracing::info_span!("slang.semantic.raw_diagnostics").entered();
565+
compilation.semantic_diagnostics_with_options(&config.slang.warnings)
566+
};
567+
let raw_semantic_diagnostic_count = raw_semantic_diagnostics.len();
568+
let mut unmapped_semantic_buffer_count = 0usize;
569+
let mut ignored_semantic_diagnostic_count = 0usize;
570+
{
571+
let _span =
572+
tracing::info_span!("slang.semantic.map_diagnostics", raw_semantic_diagnostic_count)
573+
.entered();
574+
diagnostics.extend(raw_semantic_diagnostics.into_iter().filter_map(|diag| {
575+
let diag_file_id =
576+
diag.buffer_id.and_then(|buffer_id| buffer_file_ids.get(&buffer_id).copied());
577+
let Some(diag_file_id) = diag_file_id else {
578+
unmapped_semantic_buffer_count += 1;
579+
return None;
580+
};
581+
let Some(diag) = config.apply_rules(DiagnosticSource::Semantic, diag) else {
582+
ignored_semantic_diagnostic_count += 1;
583+
return None;
584+
};
585+
Some(CompilationDiagnostic {
586+
file_id: diag_file_id,
587+
source: DiagnosticSource::Semantic,
588+
diagnostic: diag,
589+
})
590+
}));
591+
}
592+
tracing::info!(
593+
raw_semantic_diagnostic_count,
594+
unmapped_semantic_buffer_count,
595+
ignored_semantic_diagnostic_count,
596+
diagnostic_count = diagnostics.len(),
597+
"semantic diagnostics complete"
463598
);
464599

465600
Arc::from(diagnostics)

docs/src/content/docs/commands-status-logs.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ description: Vizsla 的命令面板命令、状态栏提示和输出通道。
55

66
## 命令面板命令
77

8-
VS Code 扩展贡献了三个命令:
8+
VS Code 扩展贡献了这些命令:
99

1010
| 命令 | 作用 |
1111
| --- | --- |
1212
| `Vizsla: Show Language Server Output` | 打开 `Vizsla Language Server` 输出通道。 |
1313
| `Vizsla: Restart Language Server` | 停止并重新启动语言服务器。 |
1414
| `Vizsla: Show Server Version` | 执行服务器 `--version`, 并显示第一行版本输出。 |
15+
| `Vizsla: Profile Diagnostics` | 对工作区或当前 Verilog/SystemVerilog 文件运行一次独立 diagnostics profiling, 并生成 trace、summary 和 flamegraph。 |
1516

1617
## 状态栏
1718

@@ -39,6 +40,27 @@ VS Code 扩展贡献了三个命令:
3940
- bundled server 查找结果。
4041
- 启动、停止、重启和版本查询结果。
4142

43+
执行 `Vizsla: Profile Diagnostics` 时, 扩展还会打开 `Vizsla Profiling` 输出通道。这里会显示本次 profiling 的目标、产物目录、诊断请求耗时和生成的文件路径。
44+
45+
## 性能分析诊断
46+
47+
执行 `Vizsla: Profile Diagnostics`。扩展会启动一个独立的临时语言服务器进程, 然后根据选择的目标发送一次诊断请求:
48+
49+
- 工作区目标发送 `workspace/diagnostic`, 用于观察项目级诊断路径。
50+
- 当前文件目标发送 `textDocument/diagnostic`, 用于缩小到单文件诊断路径。
51+
52+
请求结束后扩展会关闭临时进程; 这个过程不会重启或影响正在使用的语言服务器。
53+
54+
完成后会生成:
55+
56+
| 文件 | 说明 |
57+
| --- | --- |
58+
| `trace.json` | Chrome/Perfetto/Speedscope 兼容 trace, 也就是 Speedscope 的交互式输入文件。 |
59+
| `summary.json` | 请求耗时、diagnostics 汇总和 top span 汇总。 |
60+
| `trace.folded` | 从 trace 生成的 folded stack。 |
61+
| `flamegraph.svg` | 静态火焰图备用文件。交互式查看会在 VS Code 标签页中用扩展内置的本地 Speedscope viewer 打开 `trace.json`|
62+
| `server.log` | 临时语言服务器日志。 |
63+
4264
## 查询服务器版本
4365

4466
你可以从命令面板执行 `Vizsla: Show Server Version`。扩展会解析当前服务器启动配置, 然后执行:

0 commit comments

Comments
 (0)