rw_deno_core/runtime/
stats.rs

1// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2use super::op_driver::OpDriver;
3use super::op_driver::OpInflightStats;
4use super::ContextState;
5use crate::OpId;
6use crate::OpState;
7use crate::PromiseId;
8use crate::ResourceId;
9use bit_set::BitSet;
10use serde::Serialize;
11use serde::Serializer;
12use std::cell::Cell;
13use std::cell::RefCell;
14use std::collections::BTreeMap;
15use std::fmt::Display;
16use std::ops::Deref;
17use std::rc::Rc;
18
19type ActivityId = usize;
20
21/// Fast, const no-trace collection of hashes.
22const NO_TRACES: [BTreeMap<ActivityId, Rc<str>>;
23  RuntimeActivityType::MAX_TYPE as usize] = [
24  BTreeMap::new(),
25  BTreeMap::new(),
26  BTreeMap::new(),
27  BTreeMap::new(),
28];
29
30#[derive(Default)]
31pub struct RuntimeActivityTraces {
32  enabled: Cell<bool>,
33  traces: RefCell<
34    [BTreeMap<ActivityId, Rc<str>>; RuntimeActivityType::MAX_TYPE as usize],
35  >,
36}
37
38impl RuntimeActivityTraces {
39  pub(crate) fn set_enabled(&self, enabled: bool) {
40    self.enabled.set(enabled);
41    if !enabled {
42      *self.traces.borrow_mut() = Default::default();
43    }
44  }
45
46  pub(crate) fn submit(
47    &self,
48    activity_type: RuntimeActivityType,
49    id: ActivityId,
50    trace: &str,
51  ) {
52    debug_assert_ne!(
53      activity_type,
54      RuntimeActivityType::Interval,
55      "Use Timer for for timers and intervals"
56    );
57    self.traces.borrow_mut()[activity_type as usize].insert(id, trace.into());
58  }
59
60  pub(crate) fn complete(
61    &self,
62    activity_type: RuntimeActivityType,
63    id: ActivityId,
64  ) {
65    self.traces.borrow_mut()[activity_type as usize].remove(&id);
66  }
67
68  pub fn is_enabled(&self) -> bool {
69    self.enabled.get()
70  }
71
72  pub fn count(&self) -> usize {
73    self.traces.borrow().len()
74  }
75
76  pub fn get_all(
77    &self,
78    mut f: impl FnMut(RuntimeActivityType, ActivityId, &str),
79  ) {
80    let traces = self.traces.borrow();
81    for i in 0..RuntimeActivityType::MAX_TYPE {
82      for (key, value) in traces[i as usize].iter() {
83        f(RuntimeActivityType::from_u8(i), *key, value.as_ref())
84      }
85    }
86  }
87
88  pub fn capture(
89    &self,
90  ) -> [BTreeMap<ActivityId, Rc<str>>; RuntimeActivityType::MAX_TYPE as usize]
91  {
92    if self.is_enabled() {
93      self.traces.borrow().clone()
94    } else {
95      NO_TRACES
96    }
97  }
98
99  pub fn get<T>(
100    &self,
101    activity_type: RuntimeActivityType,
102    id: ActivityId,
103    f: impl FnOnce(Option<&str>) -> T,
104  ) -> T {
105    f(self.traces.borrow()[activity_type as u8 as usize]
106      .get(&id)
107      .map(|x| x.as_ref()))
108  }
109}
110
111#[derive(Clone)]
112pub struct RuntimeActivityStatsFactory {
113  pub(super) context_state: Rc<ContextState>,
114  pub(super) op_state: Rc<RefCell<OpState>>,
115}
116
117/// Selects the statistics that you are interested in capturing.
118#[derive(Clone, Default, PartialEq, Eq)]
119pub struct RuntimeActivityStatsFilter {
120  include_timers: bool,
121  include_ops: bool,
122  include_resources: bool,
123  op_filter: BitSet,
124}
125
126impl RuntimeActivityStatsFilter {
127  pub fn all() -> Self {
128    RuntimeActivityStatsFilter {
129      include_ops: true,
130      include_resources: true,
131      include_timers: true,
132      op_filter: BitSet::default(),
133    }
134  }
135
136  pub fn with_ops(mut self) -> Self {
137    self.include_ops = true;
138    self
139  }
140
141  pub fn with_resources(mut self) -> Self {
142    self.include_resources = true;
143    self
144  }
145
146  pub fn with_timers(mut self) -> Self {
147    self.include_timers = true;
148    self
149  }
150
151  pub fn omit_op(mut self, op: OpId) -> Self {
152    self.op_filter.insert(op as _);
153    self
154  }
155
156  pub fn is_empty(&self) -> bool {
157    // This ensures we don't miss a newly-added field in the empty comparison
158    let Self {
159      include_ops,
160      include_resources,
161      include_timers,
162      op_filter: _,
163    } = self;
164    !(*include_ops) && !(*include_resources) && !(*include_timers)
165  }
166}
167
168impl RuntimeActivityStatsFactory {
169  /// Capture the current runtime activity.
170  pub fn capture(
171    self,
172    filter: &RuntimeActivityStatsFilter,
173  ) -> RuntimeActivityStats {
174    let resources = if filter.include_resources {
175      let res = &self.op_state.borrow().resource_table;
176      let mut resources = ResourceOpenStats {
177        resources: Vec::with_capacity(res.len()),
178      };
179      for resource in res.names() {
180        resources
181          .resources
182          .push((resource.0, resource.1.to_string()))
183      }
184      resources
185    } else {
186      ResourceOpenStats::default()
187    };
188
189    let timers = if filter.include_timers {
190      let timer_count = self.context_state.timers.len();
191      let mut timers = TimerStats {
192        timers: Vec::with_capacity(timer_count),
193        repeats: BitSet::with_capacity(timer_count),
194      };
195      for (timer_id, repeats) in &self.context_state.timers.iter() {
196        if repeats {
197          timers.repeats.insert(timers.timers.len());
198        }
199        timers.timers.push(timer_id as usize);
200      }
201      timers
202    } else {
203      TimerStats::default()
204    };
205
206    let (ops, activity_traces) = if filter.include_ops {
207      let ops = self.context_state.pending_ops.stats(&filter.op_filter);
208      let activity_traces = self.context_state.activity_traces.capture();
209      (ops, activity_traces)
210    } else {
211      (Default::default(), Default::default())
212    };
213
214    RuntimeActivityStats {
215      context_state: self.context_state.clone(),
216      ops,
217      activity_traces,
218      resources,
219      timers,
220    }
221  }
222}
223
224#[derive(Default)]
225pub struct ResourceOpenStats {
226  pub(super) resources: Vec<(u32, String)>,
227}
228
229#[derive(Default)]
230pub struct TimerStats {
231  pub(super) timers: Vec<usize>,
232  /// `repeats` is a bitset that reports whether a given index in the ID array
233  /// is an interval (if true) or a timer (if false).
234  pub(super) repeats: BitSet,
235}
236
237/// Information about in-flight ops, open resources, active timers and other runtime-specific
238/// data that can be used for test sanitization.
239pub struct RuntimeActivityStats {
240  context_state: Rc<ContextState>,
241  pub(super) ops: OpInflightStats,
242  pub(super) activity_traces: [BTreeMap<ActivityId, Rc<str>>; 4],
243  pub(super) resources: ResourceOpenStats,
244  pub(super) timers: TimerStats,
245}
246
247/// Contains a runtime activity (op, timer, resource, etc.) stack trace.
248#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
249#[repr(transparent)]
250pub struct RuntimeActivityTrace(Rc<str>);
251
252impl Serialize for RuntimeActivityTrace {
253  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
254  where
255    S: Serializer,
256  {
257    self.0.as_ref().serialize(serializer)
258  }
259}
260
261impl Display for RuntimeActivityTrace {
262  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
263    f.write_str(self.0.as_ref())
264  }
265}
266
267impl Deref for RuntimeActivityTrace {
268  type Target = str;
269  fn deref(&self) -> &Self::Target {
270    self.0.as_ref()
271  }
272}
273
274impl From<&Rc<str>> for RuntimeActivityTrace {
275  fn from(value: &Rc<str>) -> Self {
276    Self(value.clone())
277  }
278}
279
280/// The type of runtime activity being tracked.
281#[derive(Debug, Serialize)]
282pub enum RuntimeActivity {
283  /// An async op, including the promise ID and op name, with an optional trace.
284  AsyncOp(PromiseId, Option<RuntimeActivityTrace>, &'static str),
285  /// A resource, including the resource ID and name, with an optional trace.
286  Resource(ResourceId, Option<RuntimeActivityTrace>, String),
287  /// A timer, including the timer ID, with an optional trace.
288  Timer(usize, Option<RuntimeActivityTrace>),
289  /// An interval, including the interval ID, with an optional trace.
290  Interval(usize, Option<RuntimeActivityTrace>),
291}
292
293impl RuntimeActivity {
294  pub fn activity(&self) -> RuntimeActivityType {
295    match self {
296      Self::AsyncOp(..) => RuntimeActivityType::AsyncOp,
297      Self::Resource(..) => RuntimeActivityType::Resource,
298      Self::Timer(..) => RuntimeActivityType::Timer,
299      Self::Interval(..) => RuntimeActivityType::Interval,
300    }
301  }
302}
303
304/// A data-less discriminant for [`RuntimeActivity`].
305#[derive(
306  Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize,
307)]
308#[repr(u8)]
309pub enum RuntimeActivityType {
310  AsyncOp,
311  Resource,
312  Timer,
313  Interval,
314}
315
316impl RuntimeActivityType {
317  const MAX_TYPE: u8 = 4;
318
319  pub(crate) fn from_u8(value: u8) -> Self {
320    match value {
321      0 => Self::AsyncOp,
322      1 => Self::Resource,
323      2 => Self::Timer,
324      3 => Self::Interval,
325      _ => unreachable!(),
326    }
327  }
328}
329
330impl RuntimeActivityStats {
331  fn trace_for(
332    &self,
333    activity_type: RuntimeActivityType,
334    id: ActivityId,
335  ) -> Option<RuntimeActivityTrace> {
336    debug_assert_ne!(
337      activity_type,
338      RuntimeActivityType::Interval,
339      "Use Timer for for timers and intervals"
340    );
341    self.activity_traces[activity_type as u8 as usize]
342      .get(&id)
343      .map(|x| x.into())
344  }
345
346  /// Capture the data within this [`RuntimeActivityStats`] as a [`RuntimeActivitySnapshot`]
347  /// with details of activity.
348  pub fn dump(&self) -> RuntimeActivitySnapshot {
349    let has_traces = !self.activity_traces.is_empty();
350    let mut v = Vec::with_capacity(
351      self.ops.ops.len()
352        + self.resources.resources.len()
353        + self.timers.timers.len(),
354    );
355    let ops = &self.context_state.op_ctxs;
356    if has_traces {
357      for op in self.ops.ops.iter() {
358        v.push(RuntimeActivity::AsyncOp(
359          op.0,
360          self.trace_for(RuntimeActivityType::AsyncOp, op.0 as _),
361          ops[op.1 as usize].decl.name,
362        ));
363      }
364    } else {
365      for op in self.ops.ops.iter() {
366        v.push(RuntimeActivity::AsyncOp(
367          op.0,
368          None,
369          ops[op.1 as usize].decl.name,
370        ));
371      }
372    }
373    for resource in self.resources.resources.iter() {
374      v.push(RuntimeActivity::Resource(
375        resource.0,
376        None,
377        resource.1.clone(),
378      ))
379    }
380    if has_traces {
381      for i in 0..self.timers.timers.len() {
382        let id = self.timers.timers[i];
383        if self.timers.repeats.contains(i) {
384          v.push(RuntimeActivity::Interval(
385            id,
386            self.trace_for(RuntimeActivityType::Timer, id),
387          ));
388        } else {
389          v.push(RuntimeActivity::Timer(
390            id,
391            self.trace_for(RuntimeActivityType::Timer, id),
392          ));
393        }
394      }
395    } else {
396      for i in 0..self.timers.timers.len() {
397        if self.timers.repeats.contains(i) {
398          v.push(RuntimeActivity::Interval(self.timers.timers[i], None));
399        } else {
400          v.push(RuntimeActivity::Timer(self.timers.timers[i], None));
401        }
402      }
403    }
404    RuntimeActivitySnapshot { active: v }
405  }
406
407  pub fn diff(before: &Self, after: &Self) -> RuntimeActivityDiff {
408    let mut appeared = vec![];
409    let mut disappeared = vec![];
410    let ops = &before.context_state.op_ctxs;
411
412    let mut a = BitSet::new();
413    for op in after.ops.ops.iter() {
414      a.insert(op.0 as usize);
415    }
416    for op in before.ops.ops.iter() {
417      if a.remove(op.0 as usize) {
418        // continuing op
419      } else {
420        // before, but not after
421        disappeared.push(RuntimeActivity::AsyncOp(
422          op.0,
423          before.trace_for(RuntimeActivityType::AsyncOp, op.0 as _),
424          ops[op.1 as usize].decl.name,
425        ));
426      }
427    }
428    for op in after.ops.ops.iter() {
429      if a.contains(op.0 as usize) {
430        // after but not before
431        appeared.push(RuntimeActivity::AsyncOp(
432          op.0,
433          after.trace_for(RuntimeActivityType::AsyncOp, op.0 as _),
434          ops[op.1 as usize].decl.name,
435        ));
436      }
437    }
438
439    let mut a = BitSet::new();
440    for op in after.resources.resources.iter() {
441      a.insert(op.0 as usize);
442    }
443    for op in before.resources.resources.iter() {
444      if a.remove(op.0 as usize) {
445        // continuing op
446      } else {
447        // before, but not after
448        disappeared.push(RuntimeActivity::Resource(op.0, None, op.1.clone()));
449      }
450    }
451    for op in after.resources.resources.iter() {
452      if a.contains(op.0 as usize) {
453        // after but not before
454        appeared.push(RuntimeActivity::Resource(op.0, None, op.1.clone()));
455      }
456    }
457
458    let mut a = BitSet::new();
459    for timer in after.timers.timers.iter() {
460      a.insert(*timer);
461    }
462    for index in 0..before.timers.timers.len() {
463      let timer = before.timers.timers[index];
464      if a.remove(timer) {
465        // continuing op
466      } else {
467        // before, but not after
468        if before.timers.repeats.contains(index) {
469          disappeared.push(RuntimeActivity::Interval(
470            timer,
471            before.trace_for(RuntimeActivityType::Timer, timer),
472          ));
473        } else {
474          disappeared.push(RuntimeActivity::Timer(
475            timer,
476            before.trace_for(RuntimeActivityType::Timer, timer),
477          ));
478        }
479      }
480    }
481    for index in 0..after.timers.timers.len() {
482      let timer = after.timers.timers[index];
483      if a.contains(timer) {
484        // after but not before
485        if after.timers.repeats.contains(index) {
486          appeared.push(RuntimeActivity::Interval(
487            timer,
488            after.trace_for(RuntimeActivityType::Timer, timer),
489          ));
490        } else {
491          appeared.push(RuntimeActivity::Timer(
492            timer,
493            after.trace_for(RuntimeActivityType::Timer, timer),
494          ));
495        }
496      }
497    }
498
499    RuntimeActivityDiff {
500      appeared,
501      disappeared,
502    }
503  }
504}
505
506#[derive(Debug, Serialize)]
507pub struct RuntimeActivityDiff {
508  pub appeared: Vec<RuntimeActivity>,
509  pub disappeared: Vec<RuntimeActivity>,
510}
511
512impl RuntimeActivityDiff {
513  pub fn is_empty(&self) -> bool {
514    self.appeared.is_empty() && self.disappeared.is_empty()
515  }
516}
517
518#[derive(Debug, Serialize)]
519pub struct RuntimeActivitySnapshot {
520  pub active: Vec<RuntimeActivity>,
521}