locks: add process-wide lock hold time tracking

TrackedMutex and TrackedRwLock wrappers that record hold durations
by source location using #[track_caller]. Stats written to
~/.consciousness/lock-stats.json every second, sorted by max hold time.

Re-exported as crate::Mutex so all locks are instrumented. To disable,
swap the re-export back to tokio::sync::Mutex.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-12 20:27:42 -04:00
parent b94e056372
commit f56fc3a7c7
9 changed files with 286 additions and 17 deletions

View file

@ -268,8 +268,8 @@ pub struct Mind {
pub agent: Arc<Agent>,
pub shared: Arc<SharedMindState>,
pub config: SessionConfig,
pub subconscious: Arc<tokio::sync::Mutex<Subconscious>>,
pub unconscious: Arc<tokio::sync::Mutex<Unconscious>>,
pub subconscious: Arc<crate::Mutex<Subconscious>>,
pub unconscious: Arc<crate::Mutex<Unconscious>>,
turn_tx: mpsc::Sender<(Result<TurnResult>, StreamTarget)>,
turn_watch: tokio::sync::watch::Sender<bool>,
/// Signals conscious activity to the unconscious loop.
@ -309,10 +309,10 @@ impl Mind {
sup.load_config();
sup.ensure_running();
let subconscious = Arc::new(tokio::sync::Mutex::new(Subconscious::new()));
let subconscious = Arc::new(crate::Mutex::new(Subconscious::new()));
subconscious.lock().await.init_output_tool(subconscious.clone());
let unconscious = Arc::new(tokio::sync::Mutex::new(Unconscious::new()));
let unconscious = Arc::new(crate::Mutex::new(Unconscious::new()));
// Spawn the unconscious loop on its own task
if !config.no_agents {
@ -584,6 +584,28 @@ impl Mind {
mut input_rx: tokio::sync::mpsc::UnboundedReceiver<MindCommand>,
mut turn_rx: mpsc::Receiver<(Result<TurnResult>, StreamTarget)>,
) {
// Spawn lock stats logger
tokio::spawn(async {
let path = dirs::home_dir().unwrap_or_default()
.join(".consciousness/lock-stats.json");
let mut interval = tokio::time::interval(std::time::Duration::from_secs(1));
loop {
interval.tick().await;
let stats = crate::locks::lock_stats();
if stats.is_empty() { continue; }
let json: Vec<serde_json::Value> = stats.iter()
.map(|(loc, s)| serde_json::json!({
"location": loc,
"count": s.count,
"total_ms": s.total_ns as f64 / 1_000_000.0,
"avg_ms": s.avg_ns as f64 / 1_000_000.0,
"max_ms": s.max_ns as f64 / 1_000_000.0,
}))
.collect();
let _ = std::fs::write(&path, serde_json::to_string_pretty(&json).unwrap_or_default());
}
});
let mut bg_rx = self.bg_rx.lock().unwrap().take()
.expect("Mind::run() called twice");
let mut sub_handle: Option<tokio::task::JoinHandle<()>> = None;