use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{LazyLock, Once, 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 SYNC_WRITER: OnceLock<std::sync::Mutex<std::io::BufWriter<std::fs::File>>> = OnceLock::new();
static ENABLED: AtomicBool = AtomicBool::new(false);
static INIT: Once = Once::new();
pub(super) fn init() {
INIT.call_once(|| {
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 (dx12) enabled -> {:?}", cfg.path);
ENABLED.store(true, Ordering::Relaxed);
if cfg.sync {
let writer = std::io::BufWriter::new(file);
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_dx12_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_dx12_api_log_writer");
});
}
#[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 com_identity<I: windows::core::Interface>(obj: &I) -> u64 {
obj.as_raw() as u64
}
pub(super) fn log_device_create(adapter_id: u32, device: super::DeviceHandle) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"device_create","adapter_id":{},"device":{}}}"#,
t_us(),
tid_name(),
adapter_id,
device
));
}
pub(super) fn log_device_destroy(device: super::DeviceHandle) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"device_destroy","device":{}}}"#,
t_us(),
tid_name(),
device
));
}
pub(super) fn log_context_create(device: super::DeviceHandle, ctx: super::ContextHandle, is_warp: bool) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"context_create","device":{},"ctx":{},"is_warp":{}}}"#,
t_us(),
tid_name(),
device,
ctx,
is_warp
));
}
pub(super) fn log_context_destroy(device: super::DeviceHandle, ctx: super::ContextHandle) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"context_destroy","device":{},"ctx":{}}}"#,
t_us(),
tid_name(),
device,
ctx
));
}
pub(super) fn log_queue_wait(queue: u64, producer_fence: u64, value: u64) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"queue_wait","queue":{},"producer_fence":{},"value":{}}}"#,
t_us(),
tid_name(),
queue,
producer_fence,
value
));
}
pub(super) fn log_execute_command_lists(queue: u64, num_lists: usize) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"execute_command_lists","queue":{},"num_lists":{}}}"#,
t_us(),
tid_name(),
queue,
num_lists
));
}
pub(super) fn log_queue_signal(queue: u64, ctx_fence: u64, value: u64) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"queue_signal","queue":{},"ctx_fence":{},"value":{}}}"#,
t_us(),
tid_name(),
queue,
ctx_fence,
value
));
}
pub(super) fn log_device_removed(device: super::DeviceHandle, hresult: i32) {
emit(format!(
r#"{{"t_us":{:.3},"tid":"{}","op":"device_removed","device":{},"hresult":{}}}"#,
t_us(),
tid_name(),
device,
hresult
));
}