ra_ap_rust_analyzer/tracing/
hprof.rs1use std::{
36 fmt::Write,
37 marker::PhantomData,
38 mem,
39 time::{Duration, Instant},
40};
41
42use rustc_hash::FxHashSet;
43use tracing::{
44 field::{Field, Visit},
45 span::Attributes,
46 Event, Id, Level, Subscriber,
47};
48use tracing_subscriber::{
49 filter,
50 layer::{Context, SubscriberExt},
51 registry::LookupSpan,
52 Layer, Registry,
53};
54
55pub fn init(spec: &str) -> tracing::subscriber::DefaultGuard {
56 let subscriber = Registry::default().with(SpanTree::new(spec));
57 tracing::subscriber::set_default(subscriber)
58}
59
60#[derive(Debug)]
61pub(crate) struct SpanTree<S> {
62 aggregate: bool,
63 write_filter: WriteFilter,
64 _inner: PhantomData<fn(S)>,
65}
66
67impl<S> SpanTree<S>
68where
69 S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
70{
71 pub(crate) fn new(spec: &str) -> impl Layer<S> {
72 let (write_filter, allowed_names) = WriteFilter::from_spec(spec);
73
74 let profile_filter = filter::filter_fn(move |metadata| {
77 let allowed = match &allowed_names {
78 Some(names) => names.contains(metadata.name()),
79 None => true,
80 };
81
82 allowed
83 && metadata.is_span()
84 && metadata.level() >= &Level::INFO
85 && !metadata.target().starts_with("salsa")
86 && metadata.name() != "compute_exhaustiveness_and_usefulness"
87 && !metadata.target().starts_with("chalk")
88 });
89
90 Self { aggregate: true, write_filter, _inner: PhantomData }.with_filter(profile_filter)
91 }
92}
93
94struct Data {
95 start: Instant,
96 children: Vec<Node>,
97 fields: String,
98}
99
100impl Data {
101 fn new(attrs: &Attributes<'_>) -> Self {
102 let mut data = Self { start: Instant::now(), children: Vec::new(), fields: String::new() };
103
104 let mut visitor = DataVisitor { string: &mut data.fields };
105 attrs.record(&mut visitor);
106 data
107 }
108
109 fn into_node(self, name: &'static str) -> Node {
110 Node {
111 name,
112 fields: self.fields,
113 count: 1,
114 duration: self.start.elapsed(),
115 children: self.children,
116 }
117 }
118}
119
120pub struct DataVisitor<'a> {
121 string: &'a mut String,
122}
123
124impl Visit for DataVisitor<'_> {
125 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
126 write!(self.string, "{} = {:?} ", field.name(), value).unwrap();
127 }
128}
129
130impl<S> Layer<S> for SpanTree<S>
131where
132 S: Subscriber + for<'span> LookupSpan<'span>,
133{
134 fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
135 let span = ctx.span(id).unwrap();
136
137 let data = Data::new(attrs);
138 span.extensions_mut().insert(data);
139 }
140
141 fn on_event(&self, _event: &Event<'_>, _ctx: Context<'_, S>) {}
142
143 fn on_close(&self, id: Id, ctx: Context<'_, S>) {
144 let span = ctx.span(&id).unwrap();
145 let data = span.extensions_mut().remove::<Data>().unwrap();
146 let mut node = data.into_node(span.name());
147
148 match span.parent() {
149 Some(parent_span) => {
150 parent_span.extensions_mut().get_mut::<Data>().unwrap().children.push(node);
151 }
152 None => {
153 if self.aggregate {
154 node.aggregate()
155 }
156 node.print(&self.write_filter)
157 }
158 }
159 }
160}
161
162#[derive(Default)]
163struct Node {
164 name: &'static str,
165 fields: String,
166 count: u32,
167 duration: Duration,
168 children: Vec<Node>,
169}
170
171impl Node {
172 fn print(&self, filter: &WriteFilter) {
173 self.go(0, filter)
174 }
175
176 #[allow(clippy::print_stderr)]
177 fn go(&self, level: usize, filter: &WriteFilter) {
178 if self.duration > filter.longer_than && level < filter.depth {
179 let duration = ms(self.duration);
180 let current_indent = level * 2;
181
182 let mut out = String::new();
183 let _ = write!(out, "{:current_indent$} {duration} {:<6}", "", self.name);
184
185 if !self.fields.is_empty() {
186 let _ = write!(out, " @ {}", self.fields);
187 }
188
189 if self.count > 1 {
190 let _ = write!(out, " ({} calls)", self.count);
191 }
192
193 eprintln!("{out}");
194
195 for child in &self.children {
196 child.go(level + 1, filter)
197 }
198 }
199 }
200
201 fn aggregate(&mut self) {
202 if self.children.is_empty() {
203 return;
204 }
205
206 self.children.sort_by_key(|it| it.name);
207 let mut idx = 0;
208 for i in 1..self.children.len() {
209 if self.children[idx].name == self.children[i].name {
210 let child = mem::take(&mut self.children[i]);
211 self.children[idx].duration += child.duration;
212 self.children[idx].count += child.count;
213 self.children[idx].children.extend(child.children);
214 } else {
215 idx += 1;
216 assert!(idx <= i);
217 self.children.swap(idx, i);
218 }
219 }
220 self.children.truncate(idx + 1);
221 for child in &mut self.children {
222 child.aggregate()
223 }
224 }
225}
226
227#[derive(Default, Clone, Debug)]
228pub(crate) struct WriteFilter {
229 depth: usize,
230 longer_than: Duration,
231}
232
233impl WriteFilter {
234 pub(crate) fn from_spec(mut spec: &str) -> (WriteFilter, Option<FxHashSet<String>>) {
235 let longer_than = if let Some(idx) = spec.rfind('>') {
236 let longer_than = spec[idx + 1..].parse().expect("invalid profile longer_than");
237 spec = &spec[..idx];
238 Duration::from_millis(longer_than)
239 } else {
240 Duration::new(0, 0)
241 };
242
243 let depth = if let Some(idx) = spec.rfind('@') {
244 let depth: usize = spec[idx + 1..].parse().expect("invalid profile depth");
245 spec = &spec[..idx];
246 depth
247 } else {
248 999
249 };
250 let allowed = if spec == "*" {
251 None
252 } else {
253 Some(FxHashSet::from_iter(spec.split('|').map(String::from)))
254 };
255 (WriteFilter { depth, longer_than }, allowed)
256 }
257}
258
259#[allow(non_camel_case_types)]
260struct ms(Duration);
261
262impl std::fmt::Display for ms {
263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264 let n = self.0.as_millis();
265 write!(f, "{n:5}ms")
266 }
267}