Skip to main content

lashlang/
tracking.rs

1use serde::{Deserialize, Serialize};
2use sha2::{Digest, Sha256};
3
4use crate::{ModuleRef, ProcessRef};
5
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub(crate) struct LashlangExecutionContext {
8    pub(crate) module_ref: ModuleRef,
9    pub(crate) entry: LashlangExecutionEntry,
10}
11
12impl LashlangExecutionContext {
13    pub(crate) fn main(module_ref: ModuleRef) -> Self {
14        Self {
15            module_ref,
16            entry: LashlangExecutionEntry::Main,
17        }
18    }
19
20    pub(crate) fn process(
21        module_ref: ModuleRef,
22        process_ref: ProcessRef,
23        process_name: impl Into<String>,
24    ) -> Self {
25        Self {
26            module_ref,
27            entry: LashlangExecutionEntry::Process {
28                process_ref,
29                process_name: process_name.into(),
30            },
31        }
32    }
33
34    pub(crate) fn builder(&self) -> LashlangExecutionSiteBuilder<'_> {
35        LashlangExecutionSiteBuilder { context: self }
36    }
37}
38
39#[derive(Clone, Debug, PartialEq, Eq)]
40pub(crate) enum LashlangExecutionEntry {
41    Main,
42    Process {
43        process_ref: ProcessRef,
44        process_name: String,
45    },
46}
47
48impl LashlangExecutionEntry {
49    fn stable_key(&self) -> String {
50        match self {
51            Self::Main => "main".to_string(),
52            Self::Process { process_ref, .. } => process_ref_key(process_ref),
53        }
54    }
55}
56
57#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
58pub(crate) struct LashlangAstPath(Vec<u32>);
59
60impl LashlangAstPath {
61    pub(crate) fn root() -> Self {
62        Self(Vec::new())
63    }
64
65    pub(crate) fn from_indices(indices: &[u32]) -> Self {
66        Self(indices.to_vec())
67    }
68
69    pub(crate) fn child(&self, index: usize) -> Self {
70        let mut path = self.0.clone();
71        path.push(index as u32);
72        Self(path)
73    }
74
75    fn stable_text(&self) -> String {
76        if self.0.is_empty() {
77            return "root".to_string();
78        }
79        self.0
80            .iter()
81            .map(u32::to_string)
82            .collect::<Vec<_>>()
83            .join(".")
84    }
85}
86
87#[derive(Clone, Debug)]
88pub(crate) struct LashlangExecutionSiteBuilder<'context> {
89    context: &'context LashlangExecutionContext,
90}
91
92impl LashlangExecutionSiteBuilder<'_> {
93    pub(crate) fn process_node_id(&self) -> String {
94        self.node_id(&LashlangAstPath::root(), "process")
95    }
96
97    pub(crate) fn main_node_id(&self) -> String {
98        self.node_id(&LashlangAstPath::root(), "main")
99    }
100
101    pub(crate) fn node_site(
102        &self,
103        path: &LashlangAstPath,
104        kind: impl Into<String>,
105        label: impl Into<String>,
106    ) -> LashlangExecutionSite {
107        let kind = kind.into();
108        LashlangExecutionSite {
109            node_id: self.node_id(path, &kind),
110            node_kind: kind,
111            label: label.into(),
112            branch: None,
113        }
114    }
115
116    pub(crate) fn branch_site(&self, path: &LashlangAstPath) -> LashlangExecutionSite {
117        LashlangExecutionSite {
118            node_id: self.node_id(path, "branch"),
119            node_kind: "branch".to_string(),
120            label: "if".to_string(),
121            branch: Some(LashlangBranchSite {
122                then_edge_id: self.branch_edge_id(path, ProcessBranchSelection::Then),
123                else_edge_id: self.branch_edge_id(path, ProcessBranchSelection::Else),
124            }),
125        }
126    }
127
128    pub(crate) fn branch_arm_node_id(
129        &self,
130        path: &LashlangAstPath,
131        selection: ProcessBranchSelection,
132    ) -> String {
133        let kind = match selection {
134            ProcessBranchSelection::Then => "branch_arm_then",
135            ProcessBranchSelection::Else => "branch_arm_else",
136        };
137        self.node_id(path, kind)
138    }
139
140    pub(crate) fn edge_id(
141        &self,
142        path: &LashlangAstPath,
143        from: &str,
144        to: &str,
145        label: &str,
146    ) -> String {
147        stable_id(
148            "edge",
149            &[
150                self.context.module_ref.to_string(),
151                self.context.entry.stable_key(),
152                path.stable_text(),
153                from.to_string(),
154                to.to_string(),
155                label.to_string(),
156            ],
157        )
158    }
159
160    pub(crate) fn branch_edge_id(
161        &self,
162        path: &LashlangAstPath,
163        selection: ProcessBranchSelection,
164    ) -> String {
165        let label = match selection {
166            ProcessBranchSelection::Then => "then",
167            ProcessBranchSelection::Else => "else",
168        };
169        stable_id(
170            "edge",
171            &[
172                self.context.module_ref.to_string(),
173                self.context.entry.stable_key(),
174                path.stable_text(),
175                "branch".to_string(),
176                label.to_string(),
177            ],
178        )
179    }
180
181    fn node_id(&self, path: &LashlangAstPath, kind: impl AsRef<str>) -> String {
182        stable_id(
183            kind.as_ref(),
184            &[
185                self.context.module_ref.to_string(),
186                self.context.entry.stable_key(),
187                path.stable_text(),
188                kind.as_ref().to_string(),
189            ],
190        )
191    }
192}
193
194fn stable_id(prefix: &str, parts: &[String]) -> String {
195    let mut hasher = Sha256::new();
196    for part in parts {
197        hasher.update(part.as_bytes());
198        hasher.update([0]);
199    }
200    let hash = format!("{:x}", hasher.finalize());
201    format!("{prefix}:{}", &hash[..24])
202}
203
204pub fn process_ref_key(process_ref: &ProcessRef) -> String {
205    format!("{}:{}", process_ref.component, process_ref.pos)
206}
207
208#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
209pub struct LashlangExecutionSite {
210    pub node_id: String,
211    pub node_kind: String,
212    pub label: String,
213    #[serde(default, skip_serializing_if = "Option::is_none")]
214    pub branch: Option<LashlangBranchSite>,
215}
216
217#[derive(Clone, Debug, PartialEq, Eq)]
218pub struct LashlangExecutionCallSite {
219    pub site: LashlangExecutionSite,
220    pub occurrence: u64,
221}
222
223#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
224pub struct LashlangBranchSite {
225    pub then_edge_id: String,
226    pub else_edge_id: String,
227}
228
229#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
230#[serde(rename_all = "snake_case")]
231pub enum ProcessBranchSelection {
232    Then,
233    Else,
234}
235
236#[derive(Clone, Debug, PartialEq, Eq)]
237pub struct LashlangExecutionChild {
238    pub process_id: String,
239    pub module_ref: ModuleRef,
240    pub process_ref: ProcessRef,
241    pub process_name: String,
242}
243
244#[derive(Clone, Debug, PartialEq, Eq)]
245pub enum LashlangExecutionObservation {
246    NodeStarted {
247        site: LashlangExecutionSite,
248        occurrence: u64,
249    },
250    NodeCompleted {
251        site: LashlangExecutionSite,
252        occurrence: u64,
253    },
254    NodeFailed {
255        site: LashlangExecutionSite,
256        occurrence: u64,
257        error: String,
258    },
259    BranchSelected {
260        site: LashlangExecutionSite,
261        occurrence: u64,
262        edge_id: String,
263        selected: ProcessBranchSelection,
264    },
265    ChildStarted {
266        site: LashlangExecutionSite,
267        occurrence: u64,
268        child: LashlangExecutionChild,
269    },
270}