1#![cfg_attr(not(feature = "std"), no_std)]
2
3#[cfg(feature = "std")]
4use std::time::SystemTime;
5
6#[cfg(feature = "enabled")]
7pub use serde_json::{json, to_writer as json_to_writer, Value as JsonValue};
8
9#[cfg(feature = "std")]
10pub fn time_to_micros(time: SystemTime) -> u64 {
11 time.duration_since(SystemTime::UNIX_EPOCH)
12 .unwrap()
13 .as_micros() as u64
14}
15
16#[cfg(feature = "std")]
17pub fn now_micros() -> u64 {
18 time_to_micros(SystemTime::now())
19}
20
21#[cfg(feature = "std")]
22pub enum TimeInput {
23 Microseconds(u64),
24 SystemTime(SystemTime),
25}
26
27#[cfg(feature = "std")]
28impl TimeInput {
29 pub fn micros(self) -> u64 {
30 match self {
31 Self::Microseconds(v) => v,
32 Self::SystemTime(v) => time_to_micros(v),
33 }
34 }
35}
36
37#[cfg(feature = "std")]
38impl From<u64> for TimeInput {
39 fn from(value: u64) -> Self {
40 Self::Microseconds(value)
41 }
42}
43
44#[cfg(feature = "std")]
45impl From<SystemTime> for TimeInput {
46 fn from(value: SystemTime) -> Self {
47 Self::SystemTime(value)
48 }
49}
50
51#[cfg(feature = "enabled")]
62#[macro_export]
63macro_rules! decl_traces {
64 ($name:ident; $($checkpoint:ident),+) => {
65
66 pub mod $name {
67 use std::rc::Rc;
68 use std::cell::RefCell;
69
70 #[derive(serde::Serialize, Debug, Default, Clone)]
71 pub struct Traces {
72 $(
73 pub $checkpoint: (u64, $crate::JsonValue),
74 )*
75 }
76
77 impl std::fmt::Display for Traces {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 let mut arr = [
80 $(
81 (stringify!($checkpoint), self.$checkpoint.0, &self.$checkpoint.1),
82 )+
83 ];
84 arr.sort_by_key(|v| v.1);
85 let mut buf = Vec::new();
86 for (name, time, meta) in arr.into_iter().filter(|v| v.1 != 0) {
87 $crate::json_to_writer(&mut buf, &(name, (time as f64) / 1_000_000.0)).unwrap();
88 buf.push(b'\n');
89 if !meta.is_null() {
90 $crate::json_to_writer(&mut buf, meta).unwrap();
91 buf.push(b'\n');
92 }
93 }
94 write!(f, "{}", &String::from_utf8(buf).unwrap())
95 }
96 }
97
98 impl From<Traces> for String {
99 fn from(t: Traces) -> Self {
100 t.to_string()
101 }
102 }
103
104 thread_local! {
105 pub static TRACES: Rc<RefCell<Traces>> = Default::default();
106 }
107
108 pub fn start_tracing() {
110 take_traces();
111 }
112
113 pub fn take_traces() -> Traces {
115 TRACES.with(|v| v.take())
116 }
117
118 #[cfg(feature = "ocaml_types")]
119 #[allow(dead_code, non_local_definitions)]
120 pub mod caml {
121 use super::*;
122
123 #[derive(Debug, ocaml::IntoValue, ocaml::FromValue, ocaml_gen::Struct)]
124 pub struct CamlTraces(String);
125
126 impl From<Traces> for CamlTraces {
127 fn from(t: Traces) -> Self {
128 Self(t.to_string())
129 }
130 }
131 }
132 }
133 };
134}
135#[cfg(not(feature = "enabled"))]
137#[macro_export]
138macro_rules! decl_traces {
139 ($($_ignored:tt)+) => {};
140}
141
142#[cfg(feature = "enabled")]
144#[macro_export]
145macro_rules! checkpoint {
146 ($name:ident; $checkpoint:ident) => {
147 $crate::checkpoint!(|$name; $checkpoint, $crate::now_micros(), $crate::json!(null));
148 };
149 ($name:ident; $checkpoint:ident, {$($metadata:tt)+}) => {
150 $crate::checkpoint!(|$name; $checkpoint, $crate::now_micros(), $crate::json!({$($metadata)+}));
151 };
152 ($name:ident; $checkpoint:ident, $time:expr) => {
153 $crate::checkpoint!(|$name; $checkpoint, $crate::TimeInput::from($time).micros(), $crate::json!(null));
154 };
155 ($name:ident; $checkpoint:ident, $time:expr, {$($metadata:tt)+}) => {
156 $crate::checkpoint!(|$name; $checkpoint, $crate::TimeInput::from($time).micros(), $crate::json!({$($metadata)+}));
157 };
158 (|$name:ident; $checkpoint:ident, $time:expr, $metadata:expr) => {
159 $name::TRACES.with(|traces| traces.borrow_mut().$checkpoint = ($time, $metadata));
160 };
161}
162#[cfg(not(feature = "enabled"))]
164#[macro_export]
165macro_rules! checkpoint {
166 ($($_ignored:tt)+) => {};
167}
168
169#[cfg(feature = "enabled")]
170#[cfg(test)]
171mod tests {
172 decl_traces!(test_fn; c1, c2, c3, c4);
173
174 #[test]
175 fn test_fn() {
176 test_fn::start_tracing();
177
178 checkpoint!(test_fn; c1);
179 checkpoint!(test_fn; c2, 2);
180 checkpoint!(test_fn; c3, { "arg": 1 });
181 checkpoint!(test_fn; c4, 3, { "arg": 2 });
182
183 let traces = test_fn::take_traces();
184
185 assert_ne!(traces.c1.0, 0);
186 assert_eq!(traces.c1.1, serde_json::Value::Null);
187
188 assert_eq!(traces.c2.0, 2);
189 assert_eq!(traces.c2.1, serde_json::Value::Null);
190
191 assert_ne!(traces.c3.0, 0);
192 assert_eq!(traces.c3.1, serde_json::json!({ "arg": 1 }));
193
194 assert_eq!(traces.c4.0, 3);
195 assert_eq!(traces.c4.1, serde_json::json!({ "arg": 2 }));
196 }
197}