///|
/// Global trace state
priv struct TraceState {
mut stdout_enabled : Bool
mut chrome_path : String?
mut events : Array[Json]
mut start : Instant?
}
///|
let state : TraceState = {
stdout_enabled: false,
chrome_path: None,
events: [],
start: None,
}
///|
/// Initialize the tracing system.
/// - `stdout`: if true, log messages are printed to stdout
/// - `chrome`: if Some(path), Chrome trace events are written to the file
pub fn setup(stdout? : Bool = false, chrome? : String? = None) -> Unit {
state.stdout_enabled = stdout
state.chrome_path = chrome
if chrome is Some(_) {
state.start = Some(instant_now())
state.events = []
}
}
///|
/// Log a debug message.
pub fn debug(msg : String) -> Unit {
if state.stdout_enabled {
println("[DEBUG] \{msg}")
}
write_instant("debug: \{msg}")
}
///|
/// Log an info message.
pub fn info(msg : String) -> Unit {
if state.stdout_enabled {
println("[INFO] \{msg}")
}
write_instant("info: \{msg}")
}
///|
/// Execute a sync function and record its duration as a Chrome trace event.
pub fn[T] scope(name : String, f : () -> T raise?) -> T raise? {
let start = instant_now()
let result = f()
let end = instant_now()
write_complete(name, 0, start, end)
result
}
///|
/// Execute an async function and record its duration as a Chrome trace event.
pub async fn[T] scope_async(name : String, f : async () -> T) -> T {
let start = instant_now()
let result = f()
let end = instant_now()
write_complete(name, 0, start, end)
result
}
///|
/// Finalize and write the trace file.
pub async fn close() -> Unit {
match state.chrome_path {
Some(path) => {
let json = state.events.to_json()
let file = @fs.create(path, permission=0o644)
file.write(json.stringify())
file.close()
}
None => ()
}
}
///|
/// Write an instant event (ph:"i") for logging.
fn write_instant(name : String) -> Unit {
guard state.start is Some(_) else { return }
let ts = elapsed_micros(state.start.unwrap())
let event : Json = { "pid": 0, "name": name, "ts": ts, "ph": "i", "s": "g" }
state.events.push(event)
}
///|
/// Write a complete/duration event (ph:"X") for scope timing.
fn write_complete(
name : String,
tid : Int,
start : Instant,
end : Instant,
) -> Unit {
guard state.start is Some(trace_start) else { return }
let ts = elapsed_micros_from(trace_start, start)
let dur = elapsed_micros_between(start, end)
let event : Json = {
"pid": 0,
"name": name,
"ts": ts,
"tid": tid,
"ph": "X",
"dur": dur,
}
state.events.push(event)
}
///|
/// Calculate elapsed microseconds from trace start to now.
fn elapsed_micros(start : Instant) -> Double {
(instant_as_secs_f64(instant_now()) - instant_as_secs_f64(start)) *
1_000_000.0
}
///|
/// Calculate elapsed microseconds from trace_start to a specific instant.
fn elapsed_micros_from(trace_start : Instant, instant : Instant) -> Double {
(instant_as_secs_f64(instant) - instant_as_secs_f64(trace_start)) *
1_000_000.0
}
///|
/// Calculate elapsed microseconds between two instants.
fn elapsed_micros_between(start : Instant, end : Instant) -> Double {
(instant_as_secs_f64(end) - instant_as_secs_f64(start)) * 1_000_000.0
}