1use std::sync::Mutex;
2use std::sync::atomic::{AtomicBool, Ordering};
3use std::collections::BTreeMap;
4use std::time::Duration;
5use std::cell::RefCell;
6
7use once_cell::sync::Lazy;
8use quanta::Clock;
9use petgraph::graph::{Graph, NodeIndex};
10
11use crate::{CallSite, CallSiteId};
12
13static CLOCK: Lazy<Clock> = Lazy::new(Clock::new);
15
16static CALL_GRAPH: Lazy<Mutex<LightCallGraph>> = Lazy::new(|| {
18 Mutex::new(LightCallGraph::new())
19});
20
21static COLLECTION_ENABLED: AtomicBool = AtomicBool::new(false);
23
24thread_local! {
25 pub static LOCAL_CURRENT_SPAN: RefCell<Option<CallSiteId>> = const { RefCell::new(None) };
28}
29
30pub struct Span {
36 callsite: &'static CallSite,
37}
38
39impl Span {
40 pub fn new(callsite: &'static CallSite) -> Span {
42 Span {
43 callsite: callsite,
44 }
45 }
46
47 #[must_use]
50 pub fn enter(&mut self) -> SpanGuard<'_> {
51 if !COLLECTION_ENABLED.load(Ordering::Acquire) {
52 return SpanGuard {
53 span: self,
54 parent: None,
55 start: 0,
56 };
57 }
58
59 let id = self.callsite.id();
60 let parent = LOCAL_CURRENT_SPAN.with(|parent| {
61 let mut parent = parent.borrow_mut();
62
63 let previous = *parent;
64 *parent = Some(id);
65 return previous;
66 });
67
68 self.callsite.depth.with(|depth| {
69 let current = depth.get();
70 depth.set(current + 1);
71 });
72
73 SpanGuard {
74 span: self,
75 parent: parent,
76 start: CLOCK.raw(),
77 }
78 }
79}
80
81pub struct SpanGuard<'a> {
84 span: &'a mut Span,
85 parent: Option<CallSiteId>,
86 start: u64,
87}
88
89impl<'a> Drop for SpanGuard<'a> {
90 fn drop(&mut self) {
91 if !COLLECTION_ENABLED.load(Ordering::Acquire) {
92 return;
93 }
94 let elapsed = CLOCK.delta(self.start, CLOCK.raw());
95
96 LOCAL_CURRENT_SPAN.with(|parent| {
97 let mut parent = parent.borrow_mut();
98 *parent = self.parent;
99 });
100
101 let mut graph = CALL_GRAPH.lock().expect("poisoned mutex");
102 let callsite = self.span.callsite.id();
103 graph.add_node(callsite);
104
105 let depth = self.span.callsite.depth.with(|depth| {
106 let current = depth.get();
107 depth.set(current - 1);
108 depth.get()
109 });
110
111 if depth == 0 {
112 graph.increase_timing(callsite, elapsed);
113 } else {
114 graph.increase_called(callsite);
115 }
116
117 if let Some(parent) = self.parent {
118 graph.add_node(parent);
119 graph.increase_call_count(parent, callsite);
120 }
121 }
122}
123
124struct LightGraphNode {
126 callsite: CallSiteId,
127 elapsed: Duration,
128 called: u32,
129}
130
131impl LightGraphNode {
132 fn new(callsite: CallSiteId) -> LightGraphNode {
133 LightGraphNode {
134 callsite: callsite,
135 elapsed: Duration::new(0, 0),
136 called: 0,
137 }
138 }
139}
140
141struct LightCallGraph {
146 graph: Graph<LightGraphNode, usize>
147}
148
149impl LightCallGraph {
150 fn new() -> LightCallGraph {
151 LightCallGraph {
152 graph: Graph::new(),
153 }
154 }
155
156 pub fn clear(&mut self) {
157 self.graph.clear()
158 }
159
160 fn find(&mut self, callsite: CallSiteId) -> Option<NodeIndex> {
162 self.graph.node_indices().find(|&id| self.graph[id].callsite == callsite)
163 }
164
165 pub fn add_node(&mut self, callsite: CallSiteId) {
168 if self.find(callsite).is_none() {
169 self.graph.add_node(LightGraphNode::new(callsite));
170 }
171 }
172
173 pub fn increase_call_count(&mut self, parent: CallSiteId, child: CallSiteId) {
176 let parent = self.find(parent).expect("missing node for parent");
177 let child = self.find(child).expect("missing node for child");
178 if let Some(edge) = self.graph.find_edge(parent, child) {
179 let count = self
180 .graph
181 .edge_weight_mut(edge)
182 .expect("failed to get edge weights");
183 *count += 1;
184 } else {
185 self.graph.add_edge(parent, child, 1);
187 }
188 }
189
190 pub fn increase_timing(&mut self, span: CallSiteId, time: Duration) {
193 let id = self.find(span).expect("missing node");
194 self.graph[id].elapsed += time;
195 self.graph[id].called += 1;
196 }
197
198 pub fn increase_called(&mut self, span: CallSiteId) {
200 let id = self.find(span).expect("missing node");
201 self.graph[id].called += 1;
202 }
203}
204
205pub fn clear_collected_data() {
207 CALL_GRAPH.lock().expect("poisoned mutex").clear();
208}
209
210pub fn enable_data_collection(enabled: bool) {
212 COLLECTION_ENABLED.store(enabled, Ordering::Release);
213}
214
215pub fn get_full_graph() -> FullCallGraph {
217 let graph = CALL_GRAPH.lock().expect("poisoned mutex");
218
219 let mut all_callsites = BTreeMap::new();
220 crate::traverse_registered_callsite(|callsite| {
221 all_callsites.insert(callsite.id(), callsite);
222 });
223
224 let graph = graph.graph.map(|index, node| {
225 TimedSpan::new(node, index.index(), all_callsites[&node.callsite])
226 }, |_, &edge| edge);
227
228 return FullCallGraph {
229 graph: graph
230 };
231}
232
233#[derive(Debug, Clone)]
236pub struct TimedSpan {
237 pub id: usize,
239 pub callsite: &'static CallSite,
241 pub elapsed: Duration,
243 pub called: u32,
245}
246
247impl TimedSpan {
248 fn new(node: &LightGraphNode, id: usize, callsite: &'static CallSite) -> TimedSpan {
249 TimedSpan {
250 id: id,
251 callsite: callsite,
252 elapsed: node.elapsed,
253 called: node.called,
254 }
255 }
256}
257
258impl std::fmt::Display for TimedSpan {
259 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
260 write!(
261 f,
262 "{} ran for {:?}, called {} times",
263 self.callsite.full_name(), self.elapsed, self.called
264 )
265 }
266}
267
268#[derive(Debug, Clone)]
310pub struct FullCallGraph {
311 graph: Graph<TimedSpan, usize>
312}
313
314#[derive(Debug, Clone)]
316pub struct Calls {
317 pub caller: usize,
319 pub callee: usize,
321 pub count: usize,
323}
324
325impl FullCallGraph {
326 pub fn spans(&self) -> impl Iterator<Item = &TimedSpan> {
328 self.graph.raw_nodes().iter().map(|node| &node.weight)
329 }
330
331 pub fn calls(&self) -> impl Iterator<Item = Calls> + '_ {
333 self.graph.raw_edges().iter().map(|edge| Calls {
334 caller: edge.target().index(),
335 callee: edge.source().index(),
336 count: edge.weight,
337 })
338 }
339
340 pub fn as_dot(&self) -> String {
344 petgraph::dot::Dot::new(&self.graph).to_string()
345 }
346
347 #[cfg(feature = "table")]
358 pub fn as_table(&self) -> String {
359 self.as_table_impl(false)
360 }
361
362 #[cfg(feature = "table")]
365 pub fn as_short_table(&self) -> String {
366 self.as_table_impl(true)
367 }
368
369 #[cfg(feature = "table")]
370 fn as_table_impl(&self, short_names: bool) -> String {
371 use petgraph::Direction;
372
373 use crate::table::Row;
374 use crate::table::TableCell;
375
376 let mut names = BTreeMap::new();
377 for node in self.graph.node_weights() {
378 if short_names {
379 names.insert(node.id, node.callsite.name().to_string());
380 } else {
381 names.insert(node.id, node.callsite.full_name());
382 }
383 }
384
385 let mut table = crate::table::Table::new();
386 table.style = crate::table::TableStyle::extended();
387
388 table.add_row(Row::new(vec![
389 "id",
390 "span name ",
392 "call count",
393 "called by",
394 "total",
395 "mean",
396 ]));
397
398 for &node_id in petgraph::algo::kosaraju_scc(&self.graph)
399 .iter()
400 .rev()
401 .flatten()
402 {
403 let node = &self.graph[node_id];
404
405 let mut called_by = vec![];
406 for other in self.graph.neighbors_directed(node_id, Direction::Incoming) {
407 called_by.push(self.graph[other].id.to_string());
408 }
409 let called_by = if !called_by.is_empty() {
410 called_by.join(", ")
411 } else {
412 "—".into()
413 };
414
415 let mean = node.elapsed / node.called;
416 let warn = if mean < Duration::from_nanos(1500) { " ⚠️ " } else { "" };
417
418 table.add_row(Row::new(vec![
419 TableCell::new_right_aligned(node.id),
420 TableCell::new(&names[&node.id]),
421 TableCell::new_right_aligned(node.called),
422 TableCell::new_right_aligned(called_by),
423 TableCell::new_right_aligned(format!("{:.2?}", node.elapsed)),
424 TableCell::new_right_aligned(format!("{:.2?}{}", mean, warn)),
425 ]));
426 }
427
428 return table.render();
429 }
430
431 #[cfg(feature = "json")]
437 pub fn as_json(&self) -> String {
438 let mut spans = json::JsonValue::new_object();
439 for span in self.spans() {
440 spans[&span.callsite.full_name()] = json::object! {
441 "id" => span.id,
442 "elapsed" => format!("{:?}", span.elapsed),
443 "called" => span.called,
444 };
445 }
446
447 let mut all_calls = json::JsonValue::new_array();
448 for call in self.calls() {
449 all_calls.push(json::object! {
450 "caller" => call.caller,
451 "callee" => call.callee,
452 "count" => call.count,
453 }).expect("failed to add edge information to JSON");
454 }
455
456 return json::stringify(json::object! {
457 "timings" => spans,
458 "calls" => all_calls,
459 });
460 }
461}