use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{LazyLock, OnceLock};
use std::time::Instant;
pub struct ApiLogConfig {
pub path: PathBuf,
pub sync: bool,
}
impl ApiLogConfig {
fn from_env() -> Option<Self> {
let path = std::env::var("GOLDY_API_LOG").ok().filter(|s| !s.is_empty())?;
let sync = std::env::var("GOLDY_API_LOG_SYNC").map(|v| v == "1").unwrap_or(false);
Some(Self {
path: PathBuf::from(path),
sync,
})
}
}
static EPOCH: LazyLock<Instant> = LazyLock::new(Instant::now);
#[inline]
fn t_us() -> f64 {
EPOCH.elapsed().as_secs_f64() * 1_000_000.0
}
#[inline]
fn tid_name() -> String {
std::thread::current()
.name()
.map(str::to_owned)
.unwrap_or_else(|| format!("{:?}", std::thread::current().id()))
}
const CHAN_CAP: usize = 4096;
static SENDER: OnceLock<std::sync::mpsc::SyncSender<String>> = OnceLock::new();
static ENABLED: AtomicBool = AtomicBool::new(false);
pub(super) fn init() {
let Some(cfg) = ApiLogConfig::from_env() else {
return;
};
let file = match std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&cfg.path)
{
Ok(f) => f,
Err(e) => {
tracing::warn!("GOLDY_API_LOG: cannot open {:?}: {e}", cfg.path);
return;
}
};
tracing::info!("GOLDY_API_LOG enabled → {:?}", cfg.path);
ENABLED.store(true, Ordering::Relaxed);
if cfg.sync {
let writer = std::io::BufWriter::new(file);
let (tx, _rx) = std::sync::mpsc::sync_channel::<String>(1);
let _ = SENDER.set(tx);
let _ = SYNC_WRITER.set(std::sync::Mutex::new(writer));
return;
}
let (tx, rx) = std::sync::mpsc::sync_channel::<String>(CHAN_CAP);
let _ = SENDER.set(tx);
std::thread::Builder::new()
.name("goldy_api_log_writer".into())
.spawn(move || {
use std::io::Write;
let mut writer = std::io::BufWriter::with_capacity(64 * 1024, file);
while let Ok(line) = rx.recv() {
let _ = writeln!(writer, "{line}");
while let Ok(extra) = rx.try_recv() {
let _ = writeln!(writer, "{extra}");
}
let _ = writer.flush();
}
let _ = writer.flush();
})
.expect("spawn goldy_api_log_writer");
}
static SYNC_WRITER: OnceLock<std::sync::Mutex<std::io::BufWriter<std::fs::File>>> = OnceLock::new();
#[inline]
fn emit(line: String) {
if let Some(sw) = SYNC_WRITER.get() {
use std::io::Write;
if let Ok(mut w) = sw.lock() {
let _ = writeln!(w, "{line}");
}
return;
}
if let Some(tx) = SENDER.get() {
let _ = tx.try_send(line);
}
}
#[inline]
pub(super) fn enabled() -> bool {
ENABLED.load(Ordering::Relaxed)
}
pub(super) fn log_encoder_open(kind: &'static str) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"encoder_open","kind":"{}"}}"#,
t_us(),
tid_name(),
kind
));
}
pub(super) fn log_encoder_end(kind: &'static str) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"encoder_end","kind":"{}"}}"#,
t_us(),
tid_name(),
kind
));
}
pub(super) fn log_dispatch(label: Option<&str>, wg_x: u32, wg_y: u32, wg_z: u32) {
let lbl = label.unwrap_or("?");
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"dispatch","label":"{}","wg":[{},{},{}]}}"#,
t_us(),
tid_name(),
lbl,
wg_x,
wg_y,
wg_z
));
}
pub(super) fn log_dispatch_indirect(label: Option<&str>, buf: u64, offset: u64) {
let lbl = label.unwrap_or("?");
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"dispatch_indirect","label":"{}","buf":{},"offset":{}}}"#,
t_us(),
tid_name(),
lbl,
buf,
offset
));
}
pub(super) fn log_dispatch_batch(label: Option<&str>, count: u32) {
let lbl = label.unwrap_or("?");
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"dispatch_batch","label":"{}","count":{}}}"#,
t_us(),
tid_name(),
lbl,
count
));
}
pub(super) fn log_resource_barrier(buf_count: usize, tex_count: usize) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"resource_barrier","bufs":{},"texs":{}}}"#,
t_us(),
tid_name(),
buf_count,
tex_count
));
}
pub(super) fn log_copy_texture(src: u64, dst: u64, w: u64, h: u64) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"copy_texture","src":{},"dst":{},"w":{},"h":{}}}"#,
t_us(),
tid_name(),
src,
dst,
w,
h
));
}
pub(super) fn log_copy_buffer(src: u64, dst: u64, size: u64) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"copy_buffer","src":{},"dst":{},"size":{}}}"#,
t_us(),
tid_name(),
src,
dst,
size
));
}
pub(super) fn log_fill_buffer(buf: u64, size: u64) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"fill_buffer","buf":{},"size":{}}}"#,
t_us(),
tid_name(),
buf,
size
));
}
pub(super) fn log_commit(tv: u64) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"commit","tv":{}}}"#,
t_us(),
tid_name(),
tv
));
}
pub(super) fn log_next_drawable(ok: bool) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"next_drawable","ok":{}}}"#,
t_us(),
tid_name(),
ok
));
}
pub(super) fn log_present_drawable(tv: u64) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"present_drawable","tv":{}}}"#,
t_us(),
tid_name(),
tv
));
}
pub(super) fn log_write_texture(tex: u64, w: u32, h: u32, bytes: usize) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"write_texture","tex":{},"w":{},"h":{},"bytes":{}}}"#,
t_us(),
tid_name(),
tex,
w,
h,
bytes
));
}