Skip to main content

llm_git/
profile.rs

1//! File-backed tracing for profiling CLI execution.
2//!
3//! Profiling uses tracing spans as the primary timing primitive. The subscriber
4//! emits JSONL span lifecycle events; span close events include elapsed
5//! busy/idle time, so nested functions and sections can be profiled without
6//! bespoke timers.
7
8use std::{
9   fs::OpenOptions,
10   path::{Path, PathBuf},
11};
12
13use tracing::Level;
14use tracing_appender::non_blocking::WorkerGuard;
15use tracing_subscriber::{
16   filter::{LevelFilter, Targets},
17   fmt::format::FmtSpan,
18   layer::SubscriberExt,
19};
20
21use crate::{CommitGenError, Result};
22
23/// Tracing target used by every profiling event.
24pub const TARGET: &str = "lgit";
25
26/// Owns the background tracing worker. Dropping it flushes the trace file.
27pub struct TraceGuard {
28   _guard: WorkerGuard,
29   path:   PathBuf,
30}
31
32impl TraceGuard {
33   pub fn path(&self) -> &Path {
34      &self.path
35   }
36}
37
38/// Initialize JSONL tracing to `path`.
39///
40/// The caller must keep the returned guard alive until shutdown so buffered
41/// events are flushed to disk.
42pub fn init_file_tracing(path: &Path) -> Result<TraceGuard> {
43   if let Some(parent) = path
44      .parent()
45      .filter(|parent| !parent.as_os_str().is_empty())
46   {
47      std::fs::create_dir_all(parent)?;
48   }
49
50   let file = OpenOptions::new().create(true).append(true).open(path)?;
51   let (writer, guard) = tracing_appender::non_blocking(file);
52
53   let filter = Targets::new().with_target(TARGET, LevelFilter::TRACE);
54   let layer = tracing_subscriber::fmt::layer()
55      .json()
56      .with_ansi(false)
57      .with_current_span(true)
58      .with_span_list(true)
59      .with_target(true)
60      .with_level(true)
61      .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
62      .with_writer(writer);
63
64   let subscriber = tracing_subscriber::registry().with(filter).with(layer);
65   tracing::subscriber::set_global_default(subscriber).map_err(|error| {
66      CommitGenError::Other(format!("Failed to initialize profiling trace subscriber: {error}"))
67   })?;
68
69   tracing::info!(
70      target: TARGET,
71      event = "trace_started",
72      path = %path.display(),
73      pid = std::process::id(),
74   );
75
76   Ok(TraceGuard { _guard: guard, path: path.to_path_buf() })
77}
78
79#[inline]
80pub fn enabled() -> bool {
81   tracing::enabled!(target: TARGET, Level::INFO)
82}
83
84/// Build a profiling span for a named logical section.
85///
86/// Callers enter the returned span with `.entered()` for synchronous sections
87/// or use `Future::instrument(span)` for async blocks. Function-level profiling
88/// should prefer `#[tracing::instrument]`.
89#[inline]
90pub fn section(section: &'static str) -> tracing::Span {
91   tracing::info_span!(target: TARGET, "profile.section", section)
92}