Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/agentsight/scripts/check-arch-boundaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"logging", # logging init
"utils", # utility functions
"discovery", # process discovery (Cross in ARCHITECTURE.md)
"lineage", # process lineage tree (Cross: L8 populates, L7 reads)
"skill_metrics", # metric helpers
"token_breakdown", # token analysis helpers
}
Expand Down
6 changes: 5 additions & 1 deletion src/agentsight/src/background.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,10 @@ mod tests {
fn tmp_dir(tag: &str) -> PathBuf {
static C: AtomicU32 = AtomicU32::new(0);
let n = C.fetch_add(1, Ordering::SeqCst);
let dir = std::env::temp_dir().join(format!("bg-test-{tag}-{n}"));
let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("target")
.join("test-tmp")
.join(format!("bg-test-{tag}-{n}"));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
dir
Expand Down Expand Up @@ -297,6 +300,7 @@ mod tests {
model: None,
provider: None,
call_kind: "main".to_string(),
process_type: None,
})
.unwrap();

Expand Down
19 changes: 13 additions & 6 deletions src/agentsight/src/bin/cli/interruption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,19 +568,26 @@ fn print_record_detail(r: &InterruptionRecord) {
);
println!(" Agent: {}", r.agent_name.as_deref().unwrap_or("-"));
if let Some(ref detail) = r.detail {
// Pretty-print JSON detail
if let Ok(v) = serde_json::from_str::<serde_json::Value>(detail) {
println!(" Detail:");
println!(
"{}",
serde_json::to_string_pretty(&v).unwrap_or_else(|_| detail.clone())
);
if r.interruption_type == "agent_crash" && v.get("signal").is_some() {
print_crash_report(&v);
} else {
println!(" Detail:");
println!(
"{}",
serde_json::to_string_pretty(&v).unwrap_or_else(|_| detail.clone())
);
}
} else {
println!(" Detail: {detail}");
}
}
}

fn print_crash_report(detail: &serde_json::Value) {
print!("{}", agentsight::lineage::format_crash_report(detail));
}

/// Print any Serialize value as JSON.
fn print_json<T: serde::Serialize>(value: &T) {
match serde_json::to_string_pretty(value) {
Expand Down
54 changes: 51 additions & 3 deletions src/agentsight/src/bin/cli/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,21 @@ pub struct TokenCommand {
#[structopt(long)]
pub json: bool,

/// Group token usage by process type (agent/sub_agent/tool)
#[structopt(long)]
pub by_type: bool,

/// Custom data file path
#[structopt(long)]
pub data_file: Option<String>,
}

impl TokenCommand {
pub fn execute(&self) {
// Determine data file path
// Use the unified database path (agentsight.db) as default,
// which is where Storage writes all tables.
if self.by_type {
self.execute_by_type();
return;
}
let data_path = self
.data_file
.as_ref()
Expand All @@ -43,6 +48,49 @@ impl TokenCommand {
self.execute_summary(&data_path);
}

fn execute_by_type(&self) {
use agentsight::storage::sqlite::GenAISqliteStore;

let genai_path = self
.data_file
.as_ref()
.map(std::path::PathBuf::from)
.unwrap_or_else(GenAISqliteStore::default_path);
let store = match GenAISqliteStore::new_with_path(&genai_path) {
Ok(s) => s,
Err(e) => {
eprintln!("Failed to open GenAI store: {e}");
return;
}
};

let hours = self.hours.unwrap_or(24);
let now_ns = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as i64)
.unwrap_or(0);
let start_ns = now_ns - (hours as i64) * 3_600_000_000_000;

match store.token_usage_by_process_type(start_ns, now_ns) {
Ok(rows) if rows.is_empty() => {
println!("No LLM calls with process_type in the last {hours} hours.");
}
Ok(rows) => {
if self.json {
println!("{}", agentsight::lineage::format_token_by_type_json(&rows));
} else {
print!(
"{}",
agentsight::lineage::format_token_by_type_table(&rows, hours)
);
}
}
Err(e) => {
eprintln!("Query failed: {e}");
}
}
}

fn execute_summary(&self, data_path: &std::path::Path) {
// Open token store
let store = TokenStore::new(data_path);
Expand Down
7 changes: 5 additions & 2 deletions src/agentsight/src/bpf/procmon.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ int trace_execve_exit(struct trace_event_raw_sys_exit *ctx)
// Fill event
event->source = EVENT_SOURCE_PROCMON;
event->timestamp_ns = ts;
event->pid = get_task_ns_pid(task);
event->pid = pid;
event->tid = tid;
event->ppid = ppid;
event->uid = uid;
event->event_type = PROCMON_EVENT_EXEC;
event->exit_code = 0;
bpf_get_current_comm(&event->comm, sizeof(event->comm));

bpf_ringbuf_submit(event, 0);
Expand Down Expand Up @@ -79,11 +80,13 @@ int trace_process_exit(void *ctx)
// Fill event
event->source = EVENT_SOURCE_PROCMON;
event->timestamp_ns = ts;
event->pid = current_ns_pid();
event->pid = pid;
event->tid = tid;
event->ppid = 0;
event->uid = uid;
event->event_type = PROCMON_EVENT_EXIT;
struct task_struct *exit_task = (struct task_struct *)bpf_get_current_task();
event->exit_code = BPF_CORE_READ(exit_task, exit_code);
bpf_get_current_comm(&event->comm, sizeof(event->comm));

bpf_ringbuf_submit(event, 0);
Expand Down
1 change: 1 addition & 0 deletions src/agentsight/src/bpf/procmon.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ struct procmon_event {
u32 ppid; // Parent PID
u32 uid;
u32 event_type; // enum procmon_event_type
s32 exit_code; // kernel wait-status (signal|coredump|exit_status)
char comm[TASK_COMM_LEN];
};

Expand Down
3 changes: 2 additions & 1 deletion src/agentsight/src/bpf/proctrace.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,8 @@ int trace_process_exit(void *ctx)

// Fill exit_data
struct proc_exit_data *exit_data = (void *)(event + 1);
exit_data->exit_code = 0; // TODO: Get actual exit code from sched_process_exit
struct task_struct *exit_task = (struct task_struct *)bpf_get_current_task();
exit_data->exit_code = BPF_CORE_READ(exit_task, exit_code);

bpf_ringbuf_submit(event, 0);
return 0;
Expand Down
2 changes: 2 additions & 0 deletions src/agentsight/src/genai/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ impl GenAIBuilder {
.get("call_kind")
.cloned()
.unwrap_or_else(|| "main".to_string()),
process_type: None,
});

events.push(GenAISemanticEvent::LLMCall(llm_call));
Expand Down Expand Up @@ -364,6 +365,7 @@ impl GenAIBuilder {
model,
provider,
call_kind: call_kind.to_string(),
process_type: None,
})
}

Expand Down
1 change: 1 addition & 0 deletions src/agentsight/src/genai/call_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ impl GenAIBuilder {
pid: pid_i32,
process_name: http.comm.clone(),
agent_name: Some(agent_name.clone()),
process_type: None,
metadata: {
let mut meta = HashMap::new();
meta.insert("method".to_string(), http.method);
Expand Down
3 changes: 3 additions & 0 deletions src/agentsight/src/genai/logtail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,9 @@ pub fn events_to_flat_records(
if let Some(ref name) = call.agent_name {
m.insert("agentsight.agent.name".to_string(), name.clone());
}
if let Some(ref ptype) = call.process_type {
m.insert("agentsight.process_type".to_string(), ptype.clone());
}
m.insert(
"agentsight.duration_ns".to_string(),
call.duration_ns.to_string(),
Expand Down
4 changes: 4 additions & 0 deletions src/agentsight/src/genai/semantic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ pub struct LLMCall {
pub process_name: String,
/// Resolved agent name from discovery registry (e.g. "OpenClaw", "Cosh")
pub agent_name: Option<String>,
/// Process type from lineage tree (agent/sub_agent/tool)
#[serde(skip_serializing_if = "Option::is_none")]
pub process_type: Option<String>,
/// Additional metadata
pub metadata: HashMap<String, String>,
}
Expand Down Expand Up @@ -304,6 +307,7 @@ impl LLMCall {
pid,
process_name,
agent_name: None,
process_type: None,
metadata: HashMap::new(),
}
}
Expand Down
1 change: 1 addition & 0 deletions src/agentsight/src/interruption/detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ mod tests {
pid: 1234,
process_name: "agent".to_string(),
agent_name: Some("TestAgent".to_string()),
process_type: None,
metadata: HashMap::from([("status_code".to_string(), "200".to_string())]),
}
}
Expand Down
1 change: 1 addition & 0 deletions src/agentsight/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub mod ffi;
pub mod genai;
pub mod health;
pub mod interruption;
pub mod lineage;
pub mod parser;
pub mod response_map;
#[cfg(feature = "server")]
Expand Down
Loading
Loading