use std::sync::Mutex;
use tau_proto::{CborValue, ToolCallId};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub(crate) struct OwnershipWork {
pub(crate) identity_clones: usize,
pub(crate) argument_clones: usize,
pub(crate) queued_argument_bytes: usize,
pub(crate) ingress_text_ptr: usize,
pub(crate) execution_text_ptr: usize,
pub(crate) lock_wait_snapshot_bytes: usize,
}
static PROBES: Mutex<Vec<(String, OwnershipWork)>> = Mutex::new(Vec::new());
pub(crate) fn start(call_id: &str) {
let mut probes = PROBES.lock().expect("ownership probe poisoned");
probes.retain(|(existing, _)| existing != call_id);
probes.push((call_id.to_owned(), OwnershipWork::default()));
}
pub(crate) fn finish(call_id: &str) -> OwnershipWork {
let mut probes = PROBES.lock().expect("ownership probe poisoned");
let position = probes
.iter()
.position(|(existing, _)| existing == call_id)
.expect("ownership probe was started");
probes.swap_remove(position).1
}
pub(super) fn record_split(call_id: &ToolCallId, arguments: &CborValue) {
update(call_id, |work| {
work.ingress_text_ptr = large_text_ptr(arguments);
});
}
pub(super) fn record_reassembly(call_id: &ToolCallId, arguments: &CborValue) {
update(call_id, |work| {
work.execution_text_ptr = large_text_ptr(arguments);
work.argument_clones += usize::from(work.execution_text_ptr != work.ingress_text_ptr);
});
}
pub(super) fn record_identity_clone(call_id: &ToolCallId) {
update(call_id, |work| work.identity_clones += 1);
}
pub(crate) fn record_queued_bytes(call_id: &ToolCallId, bytes: usize) {
update(call_id, |work| work.queued_argument_bytes = bytes);
}
pub(crate) fn record_wait_snapshot(call_id: &ToolCallId, bytes: usize) {
update(call_id, |work| work.lock_wait_snapshot_bytes = bytes);
}
fn update(call_id: &ToolCallId, update: impl FnOnce(&mut OwnershipWork)) {
let mut probes = PROBES.lock().expect("ownership probe poisoned");
if let Some((_, work)) = probes
.iter_mut()
.find(|(expected, _)| expected == call_id.as_str())
{
update(work);
}
}
fn large_text_ptr(value: &CborValue) -> usize {
largest_text(value).map_or(0, |text| text.as_ptr() as usize)
}
fn largest_text(value: &CborValue) -> Option<&str> {
match value {
CborValue::Text(text) => Some(text),
CborValue::Array(values) => values
.iter()
.filter_map(largest_text)
.max_by_key(|text| text.len()),
CborValue::Map(entries) => entries
.iter()
.flat_map(|(key, value)| [largest_text(key), largest_text(value)])
.flatten()
.max_by_key(|text| text.len()),
_ => None,
}
}