Skip to main content

rustymath_core/
events.rs

1//! Producer events shared by BPL's `•explain` and rustymath's Python `explain`. Capture is process-wide, including workers, with nesting on one thread. Payloads own their diagnostic data and never retain runtime values.
2use std::sync::{Arc, Mutex, MutexGuard, PoisonError, atomic::{AtomicUsize, Ordering::Relaxed}};
3
4macro_rules! routes {
5    ($($id:ident: $name:literal, $taken:literal, $declined:literal, $itemwise:literal;)+) => {
6        #[derive(Clone, Copy, Debug, Eq, PartialEq)]
7        pub enum Route { $($id),+ }
8        impl Route {
9            pub const ALL: &'static [Self] = &[$(Self::$id),+];
10            pub const REQUIRED: &'static [Self] = &[Self::Compiled, Self::FusedFold, Self::Trace];
11            pub const fn name(self) -> &'static str { match self { $(Self::$id => $name),+ } }
12            /// Whether a frontend reports the calls that took this route.
13            pub const fn reports_taken(self) -> bool { match self { $(Self::$id => $taken),+ } }
14            /// Whether a frontend reports the calls that declined this route.
15            pub const fn reports_decline(self) -> bool { match self { $(Self::$id => $declined),+ } }
16            /// Whether this route handles items one at a time, which basedpl records in `•explain`'s own rows.
17            pub const fn reports_itemwise(self) -> bool { match self { $(Self::$id => $itemwise),+ } }
18        }
19    };
20}
21routes! {
22    Trace: "trace", true, false, false;
23    Fusion: "fusion", true, true, false;
24    Batching: "batching", true, true, false;
25    SlidingFold: "sliding fold", true, false, false;
26    MappedFold: "mapped fold", true, false, false;
27    AlternatedFold: "alternated fold", true, false, false;
28    MappedProduct: "mapped product", true, false, false;
29    VectorKernel: "vector kernel", true, false, false;
30    MappedKernel: "mapped kernel", true, false, false;
31    PartialGrade: "partial grade", true, true, false;
32    PartialSort: "partial sort", true, true, false;
33    Ranks: "ranks", true, true, false;
34    KeyClasses: "key classes", true, false, false;
35    GroupCounts: "group counts", true, false, false;
36    GroupFolds: "group folds", true, true, false;
37    Determinant: "determinant", true, true, false;
38    Permanent: "permanent", true, true, false;
39    MaskedSelect: "masked select", true, false, false;
40    Contraction: "contraction", true, true, false;
41    Program: "program", false, true, false;
42    FusedFold: "fused fold", true, true, false;
43    Compiled: "compiled", true, true, false;
44    InPlace: "in place", true, true, false;
45    ItemPairs: "item pairs", false, false, true;
46    ItemLanes: "item lanes", false, false, true;
47    NumberParts: "number parts", false, false, true;
48    Accumulation: "accumulation", false, false, true;
49    NestedLabels: "nested labels", false, false, true;
50    LabelIds: "label ids", false, false, true;
51    InverseScan: "inverse scan", false, false, true;
52    KeyGroups: "key groups", false, false, true;
53    BatchCells: "batch cells", false, false, true;
54    BatchedItems: "batched items", false, false, true;
55    DerivativeItems: "∂ items", false, false, true;
56    TracedItems: "traced items", false, false, true;
57}
58
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
60pub enum EventKind { Taken, Declined, Stopped }
61impl EventKind {
62    pub const fn name(self) -> &'static str { match self { Self::Taken => "taken", Self::Declined => "declined", Self::Stopped => "stopped" } }
63}
64
65#[derive(Clone, Debug)]
66pub struct Record { pub kind: EventKind, pub route: Route, pub reason: &'static str, pub calls: usize, pub items: usize, pub site: Option<String> }
67
68#[derive(Clone, Debug)]
69pub enum Event {
70    Route(Record), Fused { glyphs: String, items: usize, blocks: usize, runs: usize, copied: usize },
71    Stored { glyphs: String, items: usize, reason: &'static str, partway: bool },
72    Forced { function: String, line: usize, column: usize, items: usize, reason: &'static str, site: Option<String> },
73    Program { event: &'static str, steps: usize, listing: String }, Fast { rule: &'static str, function: String }, Fallback { reason: String, items: usize },
74    Accumulation { items: usize, size: usize, path: &'static str },
75    Mixed { code: String, line: usize, column: usize, target: &'static str, written: &'static str, items: usize },
76    Compile { glyphs: String, microseconds: usize }, Itemwise { function: String, line: usize, column: usize, route: Route, items: usize }
77}
78#[derive(Clone, Copy)]
79pub enum Column { Text, Count, Truth }
80pub enum Field<'a> { Text(&'a str), Count(usize), Truth(bool) }
81impl Field<'_> {
82    pub fn text(&self) -> &str { match self { Self::Text(t) => t, _ => unreachable!("text column") } }
83    pub fn count(&self) -> usize { match self { Self::Count(n) => *n, _ => unreachable!("count column") } }
84    pub fn truth(&self) -> bool { match self { Self::Truth(t) => *t, _ => unreachable!("truth column") } }
85}
86pub const TABLES: &[(&str, &[(&str, Column)])] = {
87    use Column::*;
88    &[
89        ("fused", &[("glyphs", Text), ("items", Count), ("blocks", Count), ("runs", Count), ("copied", Count)]),
90        ("stored", &[("glyphs", Text), ("items", Count), ("reason", Text), ("partway", Truth)]),
91        ("forced", &[("function", Text), ("line", Count), ("column", Count), ("items", Count), ("reason", Text), ("site", Text)]),
92        ("programs", &[("event", Text), ("steps", Count), ("listing", Text)]),
93        ("stopped", &[("reason", Text), ("calls", Count), ("items", Count), ("site", Text)]), ("fast", &[("rule", Text), ("function", Text)]),
94        ("fallbacks", &[("reason", Text), ("items", Count)]), ("declined", &[("route", Text), ("reason", Text), ("calls", Count), ("items", Count)]),
95        ("accumulations", &[("items", Count), ("size", Count), ("path", Text)]),
96        ("mixed", &[("code", Text), ("line", Count), ("column", Count), ("target", Text), ("written", Text), ("items", Count)]),
97        ("compiles", &[("glyphs", Text), ("microseconds", Count)]), ("routes", &[("route", Text), ("calls", Count), ("items", Count)]),
98        ("itemwise", &[("function", Text), ("line", Count), ("column", Count), ("route", Text), ("items", Count)]),
99    ]
100};
101impl Event {
102    pub const fn table(&self) -> usize {
103        match self {
104            Self::Fused { .. } => 0, Self::Stored { .. } => 1, Self::Forced { .. } => 2, Self::Program { .. } => 3,
105            Self::Route(r) => match r.kind { EventKind::Stopped => 4, EventKind::Declined => 7, EventKind::Taken => 11 }, Self::Fast { .. } => 5,
106            Self::Fallback { .. } => 6, Self::Accumulation { .. } => 8, Self::Mixed { .. } => 9, Self::Compile { .. } => 10, Self::Itemwise { .. } => 12
107        }
108    }
109    pub const fn name(&self) -> &'static str { TABLES[self.table()].0 }
110    pub fn route(&self) -> Option<&Record> { match self { Self::Route(r) => Some(r), _ => None } }
111    /// The text that names this event in a route directive, where the event marks a slow path: a stopped trace, a stored or forced pending array, a fallback, a mixed write or an itemwise route. `None` for every other event.
112    pub fn slow_name(&self) -> Option<&str> {
113        match self {
114            Self::Route(Record { kind: EventKind::Stopped, reason, .. }) | Self::Stored { reason, .. } => Some(*reason),
115            Self::Forced { function: name, .. } | Self::Fallback { reason: name, .. } | Self::Mixed { code: name, .. } => Some(name.as_str()),
116            Self::Itemwise { route, .. } => Some(route.name()), _ => None
117        }
118    }
119    pub fn fields(&self) -> Vec<Field<'_>> {
120        use Field::*;
121        match self {
122            Self::Route(r) => match r.kind {
123                EventKind::Taken => vec![Text(r.route.name()), Count(r.calls), Count(r.items)],
124                EventKind::Declined => vec![Text(r.route.name()), Text(r.reason), Count(r.calls), Count(r.items)],
125                EventKind::Stopped => vec![Text(r.reason), Count(r.calls), Count(r.items), Text(r.site.as_deref().unwrap_or_default())],
126            },
127            Self::Fused { glyphs, items, blocks, runs, copied } => vec![Text(glyphs), Count(*items), Count(*blocks), Count(*runs), Count(*copied)],
128            Self::Stored { glyphs, items, reason, partway } => vec![Text(glyphs), Count(*items), Text(reason), Truth(*partway)],
129            Self::Forced { function, line, column, items, reason, site } => {
130                vec![Text(function), Count(*line), Count(*column), Count(*items), Text(reason), Text(site.as_deref().unwrap_or_default())]
131            }
132            Self::Program { event, steps, listing } => vec![Text(event), Count(*steps), Text(listing)],
133            Self::Fast { rule, function } => vec![Text(rule), Text(function)],
134            Self::Fallback { reason, items } => vec![Text(reason), Count(*items)],
135            Self::Accumulation { items, size, path } => vec![Count(*items), Count(*size), Text(path)],
136            Self::Mixed { code, line, column, target, written, items } => {
137                vec![Text(code), Count(*line), Count(*column), Text(target), Text(written), Count(*items)]
138            }
139            Self::Compile { glyphs, microseconds } => vec![Text(glyphs), Count(*microseconds)],
140            Self::Itemwise { function, line, column, route, items } => vec![Text(function), Count(*line), Count(*column), Text(route.name()), Count(*items)],
141        }
142    }
143}
144
145#[derive(Clone, Default)]
146pub struct Report { pub records: Vec<Event> }
147impl Report {
148    pub fn record(&mut self, event: Event) {
149        if let Event::Route(r) = &event {
150            if let Some(existing) = self.records.iter_mut().find_map(|event| match event {
151                Event::Route(existing) if existing.kind == r.kind && existing.route == r.route && existing.reason == r.reason && existing.site == r.site => {
152                    Some(existing)
153                }
154                _ => None,
155            }) {
156                existing.calls += r.calls;
157                existing.items += r.items;
158                return;
159            }
160        }
161        self.records.push(event);
162    }
163    pub fn extend(&mut self, other: &Self) { for event in &other.records { self.record(event.clone()); } }
164    pub fn routes(&self) -> impl Iterator<Item = &Record> { self.records.iter().filter_map(Event::route) }
165}
166
167pub trait Sink: Send + Sync {
168    fn sites(&self) -> bool { false }
169    fn record(&self, event: Event);
170}
171
172static CURRENT: Mutex<Option<Arc<dyn Sink>>> = Mutex::new(None);
173static RUNS: AtomicUsize = AtomicUsize::new(0);
174
175#[inline]
176pub fn running() -> bool { RUNS.load(Relaxed) > 0 }
177
178/// Restores the outer capture when dropped, including during unwinding.
179pub struct Run { outer: Option<Arc<dyn Sink>> }
180impl Run {
181    pub fn start(sink: Arc<dyn Sink>) -> Self { RUNS.fetch_add(1, Relaxed); Self { outer: current().replace(sink) } }
182    pub fn outer(&self) -> Option<&Arc<dyn Sink>> { self.outer.as_ref() }
183}
184impl Drop for Run {
185    fn drop(&mut self) {
186        *current() = self.outer.take();
187        RUNS.fetch_sub(1, Relaxed);
188    }
189}
190fn current() -> MutexGuard<'static, Option<Arc<dyn Sink>>> { CURRENT.lock().unwrap_or_else(PoisonError::into_inner) }
191
192/// Calls `f` after releasing the capture registry's lock.
193#[inline]
194pub fn with<T>(f: impl FnOnce(&Arc<dyn Sink>) -> T) -> Option<T> {
195    if !running() { return None; }
196    let sink = current().clone();
197    sink.as_ref().map(f)
198}
199
200/// Builds an owned event outside capture locks. The argument requests optional sites.
201#[inline]
202pub fn emit(make: impl FnOnce(bool) -> Event) {
203    with(|sink| {
204        let event = make(sink.sites());
205        sink.record(event);
206    });
207}
208
209#[inline]
210pub fn taken(route: Route, items: usize) { emit(|_| Event::Route(Record { kind: EventKind::Taken, route, reason: "", calls: 1, items, site: None })); }
211#[inline]
212pub fn declined(route: Route, reason: &'static str, items: usize) {
213    emit(|_| Event::Route(Record { kind: EventKind::Declined, route, reason, calls: 1, items, site: None }));
214}
215#[inline]
216pub fn stopped(reason: &'static str, items: usize, site: impl FnOnce() -> Option<String>) {
217    emit(|sites| Event::Route(Record { kind: EventKind::Stopped, route: Route::Trace, reason, calls: 1, items, site: if sites { site() } else { None } }));
218}