Skip to main content

rustymath_core/
events.rs

1//! Diagnostic capture shared by mathematical kernels and their callers. Rows own diagnostic data and never retain runtime values. Capture includes worker threads and permits nesting on one thread.
2use std::{borrow::Cow, sync::{Arc, Mutex, MutexGuard, PoisonError, atomic::{AtomicUsize, Ordering::Relaxed}}};
3
4#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5pub struct Route { name: &'static str, taken: bool, declined: bool, itemwise: bool }
6impl Route {
7    pub const fn new(name: &'static str, taken: bool, declined: bool, itemwise: bool) -> Self { Self { name, taken, declined, itemwise } }
8    pub const fn name(self) -> &'static str { self.name }
9    pub const fn reports_taken(self) -> bool { self.taken }
10    pub const fn reports_decline(self) -> bool { self.declined }
11    pub const fn reports_itemwise(self) -> bool { self.itemwise }
12}
13macro_rules! routes {
14    ($($id:ident: $name:literal, $declined:literal;)+) => {
15        #[allow(non_upper_case_globals)]
16        impl Route {
17            pub const ALL: &'static [Self] = &[$(Self::$id),+];
18            $(pub const $id: Self = Self::new($name, true, $declined, false);)+
19        }
20    };
21}
22routes! {
23    SlidingFold: "sliding fold", false;
24    MappedFold: "mapped fold", false;
25    AlternatedFold: "alternated fold", false;
26    MappedProduct: "mapped product", false;
27    VectorKernel: "vector kernel", false;
28    MappedKernel: "mapped kernel", false;
29    Contraction: "contraction", true;
30    InPlace: "in place", true;
31}
32
33#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34pub enum EventKind { Taken, Declined, Stopped }
35impl EventKind {
36    pub const fn name(self) -> &'static str { match self { Self::Taken => "taken", Self::Declined => "declined", Self::Stopped => "stopped" } }
37}
38
39#[derive(Clone, Debug)]
40pub struct Record { pub kind: EventKind, pub route: Route, pub reason: &'static str, pub calls: usize, pub items: usize, pub site: Option<String> }
41
42#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub enum Column { Text, Count, Truth }
44pub type Table = (&'static str, &'static [(&'static str, Column)]);
45pub const ROUTES: Table = ("routes", &[("route", Column::Text), ("calls", Column::Count), ("items", Column::Count)]);
46pub const DECLINED: Table = ("declined", &[("route", Column::Text), ("reason", Column::Text), ("calls", Column::Count), ("items", Column::Count)]);
47pub const STOPPED: Table = ("stopped", &[("reason", Column::Text), ("calls", Column::Count), ("items", Column::Count), ("site", Column::Text)]);
48
49#[derive(Clone, Debug)]
50pub enum Field<'a> { Text(Cow<'a, str>), Count(usize), Truth(bool) }
51impl Field<'_> {
52    pub fn text(&self) -> &str { match self { Self::Text(t) => t, _ => unreachable!("text column") } }
53    pub fn count(&self) -> usize { match self { Self::Count(n) => *n, _ => unreachable!("count column") } }
54    pub fn truth(&self) -> bool { match self { Self::Truth(t) => *t, _ => unreachable!("truth column") } }
55    fn borrowed(&self) -> Field<'_> {
56        match self { Self::Text(t) => Field::Text(t.as_ref().into()), Self::Count(n) => Field::Count(*n), Self::Truth(t) => Field::Truth(*t) }
57    }
58}
59
60#[derive(Clone, Debug)]
61pub enum Event { Route(Record), Row { table: &'static Table, fields: Vec<Field<'static>>, label: Option<usize> } }
62impl Event {
63    pub fn table(&self) -> &'static Table {
64        match self {
65            Self::Route(r) => match r.kind { EventKind::Stopped => &STOPPED, EventKind::Declined => &DECLINED, EventKind::Taken => &ROUTES },
66            Self::Row { table, .. } => table,
67        }
68    }
69    pub fn name(&self) -> &'static str { self.table().0 }
70    pub fn route(&self) -> Option<&Record> { match self { Self::Route(r) => Some(r), _ => None } }
71    /// The producer's designated text label, if this event has one.
72    pub fn slow_name(&self) -> Option<&str> {
73        match self {
74            Self::Route(Record { kind: EventKind::Stopped, reason, .. }) => Some(*reason),
75            Self::Row { fields, label: Some(i), .. } => Some(fields[*i].text()), _ => None,
76        }
77    }
78    pub fn fields(&self) -> Vec<Field<'_>> {
79        use Field::*;
80        match self {
81            Self::Route(r) => match r.kind {
82                EventKind::Taken => vec![Text(r.route.name().into()), Count(r.calls), Count(r.items)],
83                EventKind::Declined => vec![Text(r.route.name().into()), Text(r.reason.into()), Count(r.calls), Count(r.items)],
84                EventKind::Stopped => vec![Text(r.reason.into()), Count(r.calls), Count(r.items), Text(r.site.as_deref().unwrap_or_default().into())],
85            },
86            Self::Row { fields, .. } => fields.iter().map(Field::borrowed).collect(),
87        }
88    }
89}
90
91#[derive(Clone, Default)]
92pub struct Report { pub records: Vec<Event> }
93impl Report {
94    pub fn record(&mut self, event: Event) {
95        if let Event::Route(r) = &event {
96            if let Some(existing) = self.records.iter_mut().find_map(|event| match event {
97                Event::Route(existing) if existing.kind == r.kind && existing.route == r.route && existing.reason == r.reason && existing.site == r.site => Some(existing),
98                _ => None,
99            }) {
100                existing.calls += r.calls;
101                existing.items += r.items;
102                return;
103            }
104        }
105        self.records.push(event);
106    }
107    pub fn extend(&mut self, other: &Self) { for event in &other.records { self.record(event.clone()); } }
108    pub fn routes(&self) -> impl Iterator<Item = &Record> { self.records.iter().filter_map(Event::route) }
109}
110
111
112pub trait Sink: Send + Sync {
113    fn sites(&self) -> bool { false }
114    fn record(&self, event: Event);
115}
116
117static CURRENT: Mutex<Option<Arc<dyn Sink>>> = Mutex::new(None);
118static RUNS: AtomicUsize = AtomicUsize::new(0);
119
120#[inline]
121pub fn running() -> bool { RUNS.load(Relaxed) > 0 }
122
123/// Restores the outer capture when dropped, including during unwinding.
124pub struct Run { outer: Option<Arc<dyn Sink>> }
125impl Run {
126    pub fn start(sink: Arc<dyn Sink>) -> Self { RUNS.fetch_add(1, Relaxed); Self { outer: current().replace(sink) } }
127    pub fn outer(&self) -> Option<&Arc<dyn Sink>> { self.outer.as_ref() }
128}
129impl Drop for Run {
130    fn drop(&mut self) {
131        *current() = self.outer.take();
132        RUNS.fetch_sub(1, Relaxed);
133    }
134}
135fn current() -> MutexGuard<'static, Option<Arc<dyn Sink>>> { CURRENT.lock().unwrap_or_else(PoisonError::into_inner) }
136
137/// Calls `f` after releasing the capture registry's lock.
138#[inline]
139pub fn with<T>(f: impl FnOnce(&Arc<dyn Sink>) -> T) -> Option<T> {
140    if !running() { return None; }
141    let sink = current().clone();
142    sink.as_ref().map(f)
143}
144
145/// Builds an owned event outside capture locks. The argument requests optional sites.
146#[inline]
147pub fn emit(make: impl FnOnce(bool) -> Event) {
148    with(|sink| {
149        let event = make(sink.sites());
150        sink.record(event);
151    });
152}
153
154#[inline]
155pub fn taken(route: Route, items: usize) { emit(|_| Event::Route(Record { kind: EventKind::Taken, route, reason: "", calls: 1, items, site: None })); }
156#[inline]
157pub fn declined(route: Route, reason: &'static str, items: usize) {
158    emit(|_| Event::Route(Record { kind: EventKind::Declined, route, reason, calls: 1, items, site: None }));
159}