Skip to content

Commit fe2b453

Browse files
committed
Auto merge of rust-lang#18227 - davidbarsky:davidbarsky/push-lmntvwvznyyx, r=davidbarsky
internal: add json `tracing` Layer for profiling startup On `buck2/integrations/rust-project`, this results in the following being printed: ```json {"name":"discover_command","elapsed_ms":18703} {"name":"parallel_prime_caches","elapsed_ms":0} {"name":"vfs_load","elapsed_ms":5895} {"name":"vfs_load","elapsed_ms":547} {"name":"parallel_prime_caches","elapsed_ms":23} {"name":"parallel_prime_caches","elapsed_ms":84} {"name":"parallel_prime_caches","elapsed_ms":5819} ```
2 parents fae34e0 + a5a4d1a commit fe2b453

File tree

8 files changed

+190
-62
lines changed

8 files changed

+190
-62
lines changed

src/tools/rust-analyzer/crates/hir/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,7 @@ impl Module {
556556
acc: &mut Vec<AnyDiagnostic>,
557557
style_lints: bool,
558558
) {
559-
let _p = tracing::info_span!("Module::diagnostics", name = ?self.name(db)).entered();
559+
let _p = tracing::info_span!("diagnostics", name = ?self.name(db)).entered();
560560
let edition = db.crate_graph()[self.id.krate()].edition;
561561
let def_map = self.id.def_map(db.upcast());
562562
for diag in def_map.diagnostics() {

src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ fn setup_logging(log_file_flag: Option<PathBuf>) -> anyhow::Result<()> {
137137
filter: env::var("RA_LOG").ok().unwrap_or_else(|| "error".to_owned()),
138138
chalk_filter: env::var("CHALK_DEBUG").ok(),
139139
profile_filter: env::var("RA_PROFILE").ok(),
140+
json_profile_filter: std::env::var("RA_PROFILE_JSON").ok(),
140141
}
141142
.init()?;
142143

src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ mod handlers {
3434

3535
pub mod tracing {
3636
pub mod config;
37+
pub mod json;
3738
pub use config::Config;
3839
pub mod hprof;
3940
}

src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/config.rs

+57-20
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
//! Simple logger that logs either to stderr or to a file, using `tracing_subscriber`
22
//! filter syntax and `tracing_appender` for non blocking output.
33
4-
use std::io;
4+
use std::io::{self};
55

66
use anyhow::Context;
77
use tracing::level_filters::LevelFilter;
88
use tracing_subscriber::{
9-
filter::Targets, fmt::MakeWriter, layer::SubscriberExt, util::SubscriberInitExt, Layer,
10-
Registry,
9+
filter::{filter_fn, Targets},
10+
fmt::MakeWriter,
11+
layer::SubscriberExt,
12+
Layer, Registry,
1113
};
1214
use tracing_tree::HierarchicalLayer;
1315

1416
use crate::tracing::hprof;
17+
use crate::tracing::json;
1518

1619
#[derive(Debug)]
1720
pub struct Config<T> {
@@ -34,14 +37,20 @@ pub struct Config<T> {
3437
/// env RA_PROFILE=*@3>10 // dump everything, up to depth 3, if it takes more than 10
3538
/// ```
3639
pub profile_filter: Option<String>,
40+
41+
/// Filtering syntax, set in a shell:
42+
/// ```
43+
/// env RA_PROFILE_JSON=foo|bar|baz
44+
/// ```
45+
pub json_profile_filter: Option<String>,
3746
}
3847

3948
impl<T> Config<T>
4049
where
4150
T: for<'writer> MakeWriter<'writer> + Send + Sync + 'static,
4251
{
4352
pub fn init(self) -> anyhow::Result<()> {
44-
let filter: Targets = self
53+
let targets_filter: Targets = self
4554
.filter
4655
.parse()
4756
.with_context(|| format!("invalid log filter: `{}`", self.filter))?;
@@ -52,30 +61,58 @@ where
5261
.with_target(false)
5362
.with_ansi(false)
5463
.with_writer(writer)
55-
.with_filter(filter);
56-
57-
let mut chalk_layer = None;
58-
if let Some(chalk_filter) = self.chalk_filter {
59-
let level: LevelFilter =
60-
chalk_filter.parse().with_context(|| "invalid chalk log filter")?;
61-
62-
let chalk_filter = Targets::new()
63-
.with_target("chalk_solve", level)
64-
.with_target("chalk_ir", level)
65-
.with_target("chalk_recursive", level);
66-
chalk_layer = Some(
64+
.with_filter(targets_filter);
65+
66+
let chalk_layer = match self.chalk_filter {
67+
Some(chalk_filter) => {
68+
let level: LevelFilter =
69+
chalk_filter.parse().with_context(|| "invalid chalk log filter")?;
70+
71+
let chalk_filter = Targets::new()
72+
.with_target("chalk_solve", level)
73+
.with_target("chalk_ir", level)
74+
.with_target("chalk_recursive", level);
75+
// TODO: remove `.with_filter(LevelFilter::OFF)` on the `None` branch.
6776
HierarchicalLayer::default()
6877
.with_indent_lines(true)
6978
.with_ansi(false)
7079
.with_indent_amount(2)
7180
.with_writer(io::stderr)
72-
.with_filter(chalk_filter),
73-
);
81+
.with_filter(chalk_filter)
82+
.boxed()
83+
}
84+
None => None::<HierarchicalLayer>.with_filter(LevelFilter::OFF).boxed(),
85+
};
86+
87+
// TODO: remove `.with_filter(LevelFilter::OFF)` on the `None` branch.
88+
let profiler_layer = match self.profile_filter {
89+
Some(spec) => Some(hprof::SpanTree::new(&spec)).with_filter(LevelFilter::INFO),
90+
None => None.with_filter(LevelFilter::OFF),
91+
};
92+
93+
let json_profiler_layer = match self.json_profile_filter {
94+
Some(spec) => {
95+
let filter = json::JsonFilter::from_spec(&spec);
96+
let filter = filter_fn(move |metadata| {
97+
let allowed = match &filter.allowed_names {
98+
Some(names) => names.contains(metadata.name()),
99+
None => true,
100+
};
101+
102+
allowed && metadata.is_span()
103+
});
104+
Some(json::TimingLayer::new(std::io::stderr).with_filter(filter))
105+
}
106+
None => None,
74107
};
75108

76-
let profiler_layer = self.profile_filter.map(|spec| hprof::layer(&spec));
109+
let subscriber = Registry::default()
110+
.with(ra_fmt_layer)
111+
.with(json_profiler_layer)
112+
.with(profiler_layer)
113+
.with(chalk_layer);
77114

78-
Registry::default().with(ra_fmt_layer).with(chalk_layer).with(profiler_layer).try_init()?;
115+
tracing::subscriber::set_global_default(subscriber)?;
79116

80117
Ok(())
81118
}

src/tools/rust-analyzer/crates/rust-analyzer/src/tracing/hprof.rs

+30-40
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
3434
use std::{
3535
fmt::Write,
36+
marker::PhantomData,
3637
mem,
3738
time::{Duration, Instant},
3839
};
@@ -50,53 +51,42 @@ use tracing_subscriber::{
5051
Layer, Registry,
5152
};
5253

53-
use crate::tracing::hprof;
54-
5554
pub fn init(spec: &str) -> tracing::subscriber::DefaultGuard {
56-
let subscriber = Registry::default().with(layer(spec));
55+
let subscriber = Registry::default().with(SpanTree::new(spec));
5756
tracing::subscriber::set_default(subscriber)
5857
}
5958

60-
pub fn layer<S>(spec: &str) -> impl Layer<S>
61-
where
62-
S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
63-
{
64-
let (write_filter, allowed_names) = WriteFilter::from_spec(spec);
65-
66-
// this filter the first pass for `tracing`: these are all the "profiling" spans, but things like
67-
// span depth or duration are not filtered here: that only occurs at write time.
68-
let profile_filter = filter::filter_fn(move |metadata| {
69-
let allowed = match &allowed_names {
70-
Some(names) => names.contains(metadata.name()),
71-
None => true,
72-
};
73-
74-
allowed
75-
&& metadata.is_span()
76-
&& metadata.level() >= &Level::INFO
77-
&& !metadata.target().starts_with("salsa")
78-
&& metadata.name() != "compute_exhaustiveness_and_usefulness"
79-
&& !metadata.target().starts_with("chalk")
80-
});
81-
82-
hprof::SpanTree::default().aggregate(true).spec_filter(write_filter).with_filter(profile_filter)
83-
}
84-
85-
#[derive(Default, Debug)]
86-
pub(crate) struct SpanTree {
59+
#[derive(Debug)]
60+
pub(crate) struct SpanTree<S> {
8761
aggregate: bool,
8862
write_filter: WriteFilter,
63+
_inner: PhantomData<fn(S)>,
8964
}
9065

91-
impl SpanTree {
92-
/// Merge identical sibling spans together.
93-
pub(crate) fn aggregate(self, yes: bool) -> SpanTree {
94-
SpanTree { aggregate: yes, ..self }
95-
}
96-
97-
/// Add a write-time filter for span duration or tree depth.
98-
pub(crate) fn spec_filter(self, write_filter: WriteFilter) -> SpanTree {
99-
SpanTree { write_filter, ..self }
66+
impl<S> SpanTree<S>
67+
where
68+
S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
69+
{
70+
pub(crate) fn new(spec: &str) -> impl Layer<S> {
71+
let (write_filter, allowed_names) = WriteFilter::from_spec(spec);
72+
73+
// this filter the first pass for `tracing`: these are all the "profiling" spans, but things like
74+
// span depth or duration are not filtered here: that only occurs at write time.
75+
let profile_filter = filter::filter_fn(move |metadata| {
76+
let allowed = match &allowed_names {
77+
Some(names) => names.contains(metadata.name()),
78+
None => true,
79+
};
80+
81+
allowed
82+
&& metadata.is_span()
83+
&& metadata.level() >= &Level::INFO
84+
&& !metadata.target().starts_with("salsa")
85+
&& metadata.name() != "compute_exhaustiveness_and_usefulness"
86+
&& !metadata.target().starts_with("chalk")
87+
});
88+
89+
Self { aggregate: true, write_filter, _inner: PhantomData }.with_filter(profile_filter)
10090
}
10191
}
10292

@@ -136,7 +126,7 @@ impl<'a> Visit for DataVisitor<'a> {
136126
}
137127
}
138128

139-
impl<S> Layer<S> for SpanTree
129+
impl<S> Layer<S> for SpanTree<S>
140130
where
141131
S: Subscriber + for<'span> LookupSpan<'span>,
142132
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//! A [tracing_subscriber::layer::Layer] that exports new-line delinated JSON.
2+
//!
3+
//! Usage:
4+
//!
5+
//! ```rust
6+
//! let layer = json::TimingLayer::new(std::io::stderr);
7+
//! Registry::default().with(layer).init();
8+
//! ```
9+
10+
use std::{io::Write as _, marker::PhantomData, time::Instant};
11+
12+
use ide_db::FxHashSet;
13+
use tracing::{
14+
span::{Attributes, Id},
15+
Event, Subscriber,
16+
};
17+
use tracing_subscriber::{fmt::MakeWriter, layer::Context, registry::LookupSpan, Layer};
18+
19+
struct JsonData {
20+
name: &'static str,
21+
start: std::time::Instant,
22+
}
23+
24+
impl JsonData {
25+
fn new(name: &'static str) -> Self {
26+
Self { name, start: Instant::now() }
27+
}
28+
}
29+
30+
#[derive(Debug)]
31+
pub(crate) struct TimingLayer<S, W> {
32+
writer: W,
33+
_inner: PhantomData<fn(S)>,
34+
}
35+
36+
impl<S, W> TimingLayer<S, W> {
37+
pub(crate) fn new(writer: W) -> Self {
38+
Self { writer, _inner: PhantomData }
39+
}
40+
}
41+
42+
impl<S, W> Layer<S> for TimingLayer<S, W>
43+
where
44+
S: Subscriber + for<'span> LookupSpan<'span>,
45+
W: for<'writer> MakeWriter<'writer> + Send + Sync + 'static,
46+
{
47+
fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
48+
let span = ctx.span(id).unwrap();
49+
50+
let data = JsonData::new(attrs.metadata().name());
51+
span.extensions_mut().insert(data);
52+
}
53+
54+
fn on_event(&self, _event: &Event<'_>, _ctx: Context<'_, S>) {}
55+
56+
fn on_close(&self, id: Id, ctx: Context<'_, S>) {
57+
#[derive(serde::Serialize)]
58+
struct JsonDataInner {
59+
name: &'static str,
60+
elapsed_ms: u128,
61+
}
62+
63+
let span = ctx.span(&id).unwrap();
64+
let Some(data) = span.extensions_mut().remove::<JsonData>() else {
65+
return;
66+
};
67+
68+
let data = JsonDataInner { name: data.name, elapsed_ms: data.start.elapsed().as_millis() };
69+
let mut out = serde_json::to_string(&data).expect("Unable to serialize data");
70+
out.push('\n');
71+
self.writer.make_writer().write_all(out.as_bytes()).expect("Unable to write data");
72+
}
73+
}
74+
75+
#[derive(Default, Clone, Debug)]
76+
pub(crate) struct JsonFilter {
77+
pub(crate) allowed_names: Option<FxHashSet<String>>,
78+
}
79+
80+
impl JsonFilter {
81+
pub(crate) fn from_spec(spec: &str) -> Self {
82+
let allowed_names = if spec == "*" {
83+
None
84+
} else {
85+
Some(FxHashSet::from_iter(spec.split('|').map(String::from)))
86+
};
87+
88+
Self { allowed_names }
89+
}
90+
}

src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ impl Project<'_> {
103103
filter: std::env::var("RA_LOG").ok().unwrap_or_else(|| "error".to_owned()),
104104
chalk_filter: std::env::var("CHALK_DEBUG").ok(),
105105
profile_filter: std::env::var("RA_PROFILE").ok(),
106+
json_profile_filter: std::env::var("RA_PROFILE_JSON").ok(),
106107
};
107108
});
108109

src/tools/rust-analyzer/docs/dev/README.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,15 @@ RA_PROFILE=foo|bar|baz // enabled only selected entries
178178
RA_PROFILE=*@3>10 // dump everything, up to depth 3, if it takes more than 10 ms
179179
```
180180

181-
In particular, I have `export RA_PROFILE='*>10'` in my shell profile.
181+
Some rust-analyzer contributors have `export RA_PROFILE='*>10'` in my shell profile.
182+
183+
For machine-readable JSON output, we have the `RA_PROFILE_JSON` env variable. We support
184+
filtering only by span name:
185+
186+
```
187+
RA_PROFILE=* // dump everything
188+
RA_PROFILE_JSON="vfs_load|parallel_prime_caches|discover_command" // dump selected spans
189+
```
182190

183191
We also have a "counting" profiler which counts number of instances of popular structs.
184192
It is enabled by `RA_COUNT=1`.

0 commit comments

Comments
 (0)