1use std::{
2 cell::RefCell, collections::BTreeMap, fmt::Debug, marker::PhantomData, rc::Rc, sync::Arc,
3 time::Duration,
4};
5
6use std::time::Instant;
7
8use trustfall::{
9 FieldValue,
10 provider::{
11 Adapter, AsVertex, ContextIterator, ContextOutcomeIterator, EdgeParameters, Eid,
12 ResolveEdgeInfo, ResolveInfo, VertexInfo, VertexIterator, Vid,
13 },
14};
15
16#[derive(Clone)]
20pub struct ExpHistogram {
21 buckets: [u32; 16],
22}
23
24pub const HIST_BOUNDARIES: [u64; 16] = [
26 100,
27 300,
28 1000,
29 3000,
30 10000,
31 30000,
32 100000,
33 300000,
34 1000000,
35 3000000,
36 10000000,
37 30000000,
38 100000000,
39 300000000,
40 1000000000,
41 u64::MAX,
42];
43
44impl ExpHistogram {
45 pub fn new() -> ExpHistogram {
47 ExpHistogram { buckets: [0; 16] }
48 }
49
50 pub fn add(&mut self, num: u64) {
52 for (i, lim) in HIST_BOUNDARIES.iter().enumerate() {
53 if num <= *lim {
54 self.buckets[i] = self.buckets[i].saturating_add(1);
55 break;
56 }
57 }
58 }
59
60 pub fn boundaries(&self) -> &'static [u64; 16] {
62 &HIST_BOUNDARIES
63 }
64
65 pub fn count(&self) -> u32 {
67 self.buckets()
68 .iter()
69 .copied()
70 .fold(0, |acc, num| acc.saturating_add(num))
71 }
72
73 pub fn buckets(&self) -> &[u32; 16] {
75 &self.buckets
76 }
77}
78
79impl Debug for ExpHistogram {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 write!(
82 f,
83 "ExpHistogram {{ buckets: {:?}, count: {:?} }}",
84 &self.buckets,
85 self.count()
86 )
87 }
88}
89
90impl Default for ExpHistogram {
91 fn default() -> Self {
92 Self::new()
93 }
94}
95
96#[derive(Debug, Clone)]
98pub struct Summary {
99 hist: ExpHistogram,
100 min: Duration,
101 max: Duration,
102 sum: Duration,
103}
104
105impl Summary {
106 pub fn new(duration: Duration) -> Summary {
108 let mut hist = ExpHistogram::new();
109 hist.add(duration.as_nanos() as u64);
110
111 Summary {
112 hist,
113 min: duration,
114 max: duration,
115 sum: duration,
116 }
117 }
118
119 pub fn update(&mut self, duration: Duration) {
121 self.hist.add(duration.as_nanos() as u64);
122
123 self.min = self.min.min(duration);
124 self.max = self.max.max(duration);
125 self.sum = self.sum.saturating_add(duration);
126 }
127
128 pub fn count(&self) -> u32 {
130 self.hist.count()
131 }
132
133 pub fn total(&self) -> Duration {
135 self.sum
136 }
137
138 pub fn min(&self) -> Duration {
140 self.min
141 }
142
143 pub fn max(&self) -> Duration {
145 self.max
146 }
147
148 pub fn histogram(&self) -> &ExpHistogram {
150 &self.hist
151 }
152
153 pub fn mean(&self) -> Duration {
155 self.sum / self.count()
157 }
158}
159
160#[derive(Debug, Clone)]
166pub struct Tracer {
167 pub calls: BTreeMap<FunctionCall, Summary>,
168
169 last_input_duration: Option<Duration>,
173}
174
175impl Tracer {
176 pub fn new() -> Self {
178 Self {
179 calls: BTreeMap::new(),
180 last_input_duration: None,
181 }
182 }
183
184 pub fn record_time(&mut self, call_id: &FunctionCall, duration: Duration) {
186 if let Some(summary) = self.calls.get_mut(call_id) {
187 summary.update(duration);
188 } else {
189 self.calls.insert(call_id.clone(), Summary::new(duration));
190 }
191 }
192
193 pub fn record_last_input_duration(&mut self, duration: Duration) {
195 self.last_input_duration = Some(duration);
196 }
197
198 pub fn get_last_input_duration(&self) -> Duration {
200 self.last_input_duration.unwrap()
201 }
202}
203
204impl Default for Tracer {
205 fn default() -> Self {
206 Self::new()
207 }
208}
209
210#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
211pub enum FunctionCall {
212 ResolveProperty(Vid, Arc<str>, Arc<str>), ResolveNeighbors(Vid, Arc<str>, Eid), ResolveNeighborsInner(Vid, Arc<str>, Eid), ResolveCoercion(Vid, Arc<str>, Arc<str>), }
217
218struct PerfSpanIter<I, T, F>
219where
220 I: Iterator<Item = T>,
221 F: Fn(T, Duration) -> T,
222{
223 inner: I,
224 post_action: F,
225}
226
227impl<I, T, F> Iterator for PerfSpanIter<I, T, F>
228where
229 I: Iterator<Item = T>,
230 F: Fn(T, Duration) -> T,
231{
232 type Item = T;
233
234 fn next(&mut self) -> Option<Self::Item> {
235 let start = Instant::now();
236 let item = self.inner.next();
237 let time = start.elapsed();
238 match item {
239 Some(item) => Some((self.post_action)(item, time)),
240 None => None,
241 }
242 }
243
244 fn size_hint(&self) -> (usize, Option<usize>) {
245 self.inner.size_hint()
246 }
247}
248
249fn make_iter_with_perf_span<I, T, F>(inner: I, post_action: F) -> PerfSpanIter<I, T, F>
250where
251 I: Iterator<Item = T>,
252 F: Fn(T, Duration) -> T,
253{
254 PerfSpanIter { inner, post_action }
255}
256
257#[derive(Debug, Clone)]
262pub struct TracingAdapter<'vertex, AdapterT>
263where
264 AdapterT: Adapter<'vertex>,
265 AdapterT::Vertex: Debug + Clone + 'vertex,
266{
267 pub tracer: Rc<RefCell<Tracer>>,
268 inner: AdapterT,
269 _phantom: PhantomData<&'vertex ()>,
270}
271
272impl<'vertex, AdapterT> TracingAdapter<'vertex, AdapterT>
273where
274 AdapterT: Adapter<'vertex>,
275 AdapterT::Vertex: Debug + Clone + 'vertex,
276{
277 pub fn new(adapter: AdapterT) -> Self {
278 Self {
279 tracer: Rc::new(RefCell::new(Tracer::new())),
280 inner: adapter,
281 _phantom: PhantomData,
282 }
283 }
284
285 pub fn finish(&self) -> Tracer {
287 let trace_ref = self.tracer.borrow_mut();
289 let new_trace = Tracer::new();
290 drop(trace_ref);
291 self.tracer.replace(new_trace)
292 }
293}
294
295impl<'vertex, AdapterT> Adapter<'vertex> for TracingAdapter<'vertex, AdapterT>
311where
312 AdapterT: Adapter<'vertex> + 'vertex,
313 AdapterT::Vertex: Debug + Clone + 'vertex,
314{
315 type Vertex = AdapterT::Vertex;
316
317 fn resolve_starting_vertices(
318 &self,
319 edge_name: &Arc<str>,
320 parameters: &EdgeParameters,
321 resolve_info: &ResolveInfo,
322 ) -> VertexIterator<'vertex, Self::Vertex> {
323 self.inner
326 .resolve_starting_vertices(edge_name, parameters, resolve_info)
327 }
328
329 fn resolve_property<V: AsVertex<Self::Vertex> + 'vertex>(
330 &self,
331 contexts: ContextIterator<'vertex, V>,
332 type_name: &Arc<str>,
333 property_name: &Arc<str>,
334 resolve_info: &ResolveInfo,
335 ) -> ContextOutcomeIterator<'vertex, V, FieldValue> {
336 let call_id = FunctionCall::ResolveProperty(
337 resolve_info.vid(),
338 type_name.clone(),
339 property_name.clone(),
340 );
341
342 let tracer_ref = self.tracer.clone();
343
344 let wrapped_contexts = Box::new(make_iter_with_perf_span(
345 contexts,
346 move |context, duration| {
347 tracer_ref.borrow_mut().record_last_input_duration(duration);
348 context
349 },
350 ));
351
352 let inner_iter =
353 self.inner
354 .resolve_property(wrapped_contexts, type_name, property_name, resolve_info);
355
356 let tracer_ref_2 = self.tracer.clone();
357
358 Box::new(make_iter_with_perf_span(
359 inner_iter,
360 move |(context, value), duration| {
361 let input_duration = tracer_ref_2.borrow().get_last_input_duration();
362 tracer_ref_2
363 .borrow_mut()
364 .record_time(&call_id, duration - input_duration);
365 (context, value)
366 },
367 ))
368 }
369
370 fn resolve_neighbors<V: AsVertex<Self::Vertex> + 'vertex>(
371 &self,
372 contexts: ContextIterator<'vertex, V>,
373 type_name: &Arc<str>,
374 edge_name: &Arc<str>,
375 parameters: &EdgeParameters,
376 resolve_info: &ResolveEdgeInfo,
377 ) -> ContextOutcomeIterator<'vertex, V, VertexIterator<'vertex, Self::Vertex>> {
378 let call_id = FunctionCall::ResolveNeighbors(
381 resolve_info.origin_vid(),
382 type_name.clone(),
383 resolve_info.eid(),
384 );
385 let call_id_inner = FunctionCall::ResolveNeighborsInner(
386 resolve_info.origin_vid(),
387 type_name.clone(),
388 resolve_info.eid(),
389 );
390
391 let tracer_ref = self.tracer.clone();
392
393 let wrapped_contexts = Box::new(make_iter_with_perf_span(
394 contexts,
395 move |context, duration| {
396 tracer_ref.borrow_mut().record_last_input_duration(duration);
397 context
398 },
399 ));
400
401 let inner_iter = self.inner.resolve_neighbors(
402 wrapped_contexts,
403 type_name,
404 edge_name,
405 parameters,
406 resolve_info,
407 );
408
409 let tracer_ref_2 = self.tracer.clone();
410
411 Box::new(make_iter_with_perf_span(
412 inner_iter,
413 move |(context, neighbor_iter), duration| {
414 let input_duration = tracer_ref_2.borrow().get_last_input_duration();
415 tracer_ref_2
416 .borrow_mut()
417 .record_time(&call_id, duration - input_duration);
418
419 let tracer_ref_3 = tracer_ref_2.clone();
420
421 let value = call_id_inner.clone();
422
423 let tapped_neighbor_iter = Box::new(make_iter_with_perf_span(
426 neighbor_iter,
427 move |vertex, duration| {
428 tracer_ref_3.borrow_mut().record_time(&value, duration);
429 vertex
430 },
431 ));
432
433 (context, tapped_neighbor_iter)
434 },
435 ))
436 }
437
438 fn resolve_coercion<V: AsVertex<Self::Vertex> + 'vertex>(
439 &self,
440 contexts: ContextIterator<'vertex, V>,
441 type_name: &Arc<str>,
442 coerce_to_type: &Arc<str>,
443 resolve_info: &ResolveInfo,
444 ) -> ContextOutcomeIterator<'vertex, V, bool> {
445 let call_id = FunctionCall::ResolveCoercion(
446 resolve_info.vid(),
447 type_name.clone(),
448 coerce_to_type.clone(),
449 );
450
451 let tracer_ref = self.tracer.clone();
452
453 let wrapped_contexts = Box::new(make_iter_with_perf_span(
454 contexts,
455 move |context, duration| {
456 tracer_ref.borrow_mut().record_last_input_duration(duration);
457 context
458 },
459 ));
460
461 let inner_iter =
462 self.inner
463 .resolve_coercion(wrapped_contexts, type_name, coerce_to_type, resolve_info);
464
465 let tracer_ref_2 = self.tracer.clone();
466
467 Box::new(make_iter_with_perf_span(
468 inner_iter,
469 move |(context, can_coerce), duration| {
470 let input_duration = tracer_ref_2.borrow().get_last_input_duration();
471 tracer_ref_2
472 .borrow_mut()
473 .record_time(&call_id, duration - input_duration);
474 (context, can_coerce)
475 },
476 ))
477 }
478}