guinea_core/observability/
panels.rs1use std::cell::RefCell;
6use std::rc::Rc;
7
8#[derive(Clone, Debug, Default, PartialEq)]
11pub struct Panel {
12 pub id: &'static str,
13 pub title: &'static str,
14 pub nodes: Vec<PanelNode>,
15}
16
17#[derive(Clone, Debug, Default, PartialEq)]
18pub struct PanelNode {
19 pub label: String,
20 pub kind: String,
21 pub properties: Vec<(String, String)>,
22 pub children: Vec<PanelNode>,
23}
24
25type Provider = Rc<dyn Fn() -> Option<Panel>>;
26
27thread_local! {
28 static PANELS: RefCell<Vec<(u64, Option<u64>, Provider)>> = const { RefCell::new(Vec::new()) };
29 static NEXT_PANEL: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
30}
31
32fn offer(root: Option<u64>, provider: impl Fn() -> Option<Panel> + 'static) -> PanelGuard {
33 let id = NEXT_PANEL.with(|next| {
34 let id = next.get();
35 next.set(id + 1);
36 id
37 });
38 PANELS.with(|panels| panels.borrow_mut().push((id, root, Rc::new(provider))));
39 PanelGuard { id }
40}
41
42fn built(root: Option<u64>) -> Vec<Panel> {
43 let providers: Vec<Provider> = PANELS.with(|panels| {
44 panels
45 .borrow()
46 .iter()
47 .filter(|(_, owner, _)| *owner == root)
48 .map(|(_, _, provider)| provider.clone())
49 .collect()
50 });
51 providers.iter().filter_map(|provider| provider()).collect()
52}
53
54#[must_use = "the panel is withdrawn when the guard is dropped"]
57pub fn contribute(root: u64, provider: impl Fn() -> Option<Panel> + 'static) -> PanelGuard {
58 offer(Some(root), provider)
59}
60
61#[must_use = "the panel is withdrawn when the guard is dropped"]
64pub fn contribute_to_app(provider: impl Fn() -> Option<Panel> + 'static) -> PanelGuard {
65 offer(None, provider)
66}
67
68pub fn for_root(root: u64) -> Vec<Panel> {
70 built(Some(root))
71}
72
73pub fn for_app() -> Vec<Panel> {
75 built(None)
76}
77
78pub struct PanelGuard {
79 id: u64,
80}
81
82impl Drop for PanelGuard {
83 fn drop(&mut self) {
84 let id = self.id;
85 let _ = PANELS.try_with(|panels| panels.borrow_mut().retain(|(own, _, _)| *own != id));
86 }
87}