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    fn workflow_owner(&self) -> String {
57        match self {
58            Self::Main => "main".to_string(),
59            Self::Process { process_name, .. } => format!("process:{process_name}"),
60        }
61    }
62}
63
64#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
65pub(crate) struct LashlangAstPath(Vec<u32>);
66
67impl LashlangAstPath {
68    pub(crate) fn from_indices(indices: &[u32]) -> Self {
69        Self(indices.to_vec())
70    }
71
72    fn stable_text(&self) -> String {
73        if self.0.is_empty() {
74            return "root".to_string();
75        }
76        self.0
77            .iter()
78            .map(u32::to_string)
79            .collect::<Vec<_>>()
80            .join(".")
81    }
82
83    fn indices(&self) -> &[u32] {
84        &self.0
85    }
86}
87
88#[derive(Clone, Debug)]
89pub(crate) struct LashlangExecutionSiteBuilder<'context> {
90    context: &'context LashlangExecutionContext,
91}
92
93impl LashlangExecutionSiteBuilder<'_> {
94    pub(crate) fn node_site(
95        &self,
96        path: &LashlangAstPath,
97        kind: impl Into<String>,
98        label: impl Into<String>,
99    ) -> LashlangExecutionSite {
100        let kind = kind.into();
101        let label = label.into();
102        LashlangExecutionSite {
103            node_id: self.node_id(path, &kind),
104            node_kind: kind.clone(),
105            label: label.clone(),
106            branch: None,
107            workflow_site: WorkflowExecutionSite::new(
108                self.context.entry.workflow_owner(),
109                path.indices(),
110                kind,
111                label,
112            ),
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            workflow_site: WorkflowExecutionSite::new(
126                self.context.entry.workflow_owner(),
127                path.indices(),
128                "branch",
129                "if",
130            ),
131        }
132    }
133
134    pub(crate) fn branch_edge_id(
135        &self,
136        path: &LashlangAstPath,
137        selection: ProcessBranchSelection,
138    ) -> String {
139        let label = match selection {
140            ProcessBranchSelection::Then => "then",
141            ProcessBranchSelection::Else => "else",
142        };
143        stable_id(
144            "edge",
145            &[
146                self.context.module_ref.to_string(),
147                self.context.entry.stable_key(),
148                path.stable_text(),
149                "branch".to_string(),
150                label.to_string(),
151            ],
152        )
153    }
154
155    fn node_id(&self, path: &LashlangAstPath, kind: impl AsRef<str>) -> String {
156        stable_id(
157            kind.as_ref(),
158            &[
159                self.context.module_ref.to_string(),
160                self.context.entry.stable_key(),
161                path.stable_text(),
162                kind.as_ref().to_string(),
163            ],
164        )
165    }
166}
167
168/// Stable source-level location carried by runtime sites for workflow-graph joins.
169#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
170pub struct WorkflowExecutionSite {
171    pub owner: String,
172    #[serde(default)]
173    pub path: Vec<u32>,
174    pub kind: String,
175    pub label: String,
176}
177
178impl WorkflowExecutionSite {
179    pub fn new(
180        owner: impl Into<String>,
181        path: impl AsRef<[u32]>,
182        kind: impl Into<String>,
183        label: impl Into<String>,
184    ) -> Self {
185        Self {
186            owner: owner.into(),
187            path: path.as_ref().to_vec(),
188            kind: kind.into(),
189            label: label.into(),
190        }
191    }
192
193    pub(crate) fn same_location(&self, other: &Self) -> bool {
194        self.owner == other.owner && self.path == other.path
195    }
196}
197
198fn stable_id(prefix: &str, parts: &[String]) -> String {
199    let mut hasher = Sha256::new();
200    for part in parts {
201        hasher.update(part.as_bytes());
202        hasher.update([0]);
203    }
204    let hash = format!("{:x}", hasher.finalize());
205    format!("{prefix}:{}", &hash[..24])
206}
207
208pub fn process_ref_key(process_ref: &ProcessRef) -> String {
209    format!("{}:{}", process_ref.component, process_ref.pos)
210}
211
212#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
213pub struct LashlangExecutionSite {
214    pub node_id: String,
215    pub node_kind: String,
216    pub label: String,
217    #[serde(default, skip_serializing_if = "Option::is_none")]
218    pub branch: Option<LashlangBranchSite>,
219    pub workflow_site: WorkflowExecutionSite,
220}
221
222#[derive(Clone, Debug, PartialEq, Eq)]
223pub struct LashlangExecutionCallSite {
224    pub site: LashlangExecutionSite,
225    pub occurrence: u64,
226}
227
228#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
229pub struct LashlangBranchSite {
230    pub then_edge_id: String,
231    pub else_edge_id: String,
232}
233
234#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
235#[serde(rename_all = "snake_case")]
236pub enum ProcessBranchSelection {
237    Then,
238    Else,
239}
240
241#[derive(Clone, Debug, PartialEq, Eq)]
242pub struct LashlangExecutionChild {
243    pub process_id: String,
244    pub module_ref: ModuleRef,
245    pub process_ref: ProcessRef,
246    pub process_name: String,
247}
248
249#[derive(Clone, Debug, PartialEq, Eq)]
250pub enum LashlangExecutionObservation {
251    NodeStarted {
252        site: LashlangExecutionSite,
253        occurrence: u64,
254    },
255    NodeCompleted {
256        site: LashlangExecutionSite,
257        occurrence: u64,
258    },
259    NodeFailed {
260        site: LashlangExecutionSite,
261        occurrence: u64,
262        error: String,
263    },
264    BranchSelected {
265        site: LashlangExecutionSite,
266        occurrence: u64,
267        edge_id: String,
268        selected: ProcessBranchSelection,
269    },
270    ChildStarted {
271        site: LashlangExecutionSite,
272        occurrence: u64,
273        child: LashlangExecutionChild,
274    },
275}