Skip to content
Merged
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
142 changes: 133 additions & 9 deletions eclipta-cli/src/commands/system/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ use tui::{

use crate::utils::paths::default_state_path;
use crate::utils::state::load_state;
use crate::utils::db::ensure_db_ready;
use crate::db::programs::list_programs;
use serde_json::Value;
use std::collections::HashMap;
use tokio::process::Command as TokioCommand;

struct AttachmentRow {
name: String,
Expand All @@ -32,6 +37,91 @@ fn proc_alive(pid: u32) -> bool {
p.exists()
}

#[derive(Debug, Clone)]
struct LinkInfo {
prog_id: u32,
pid: Option<u32>,
attach_type: String,
target: Option<String>,
hook: Option<String>,
}

async fn get_live_bpf_indices() -> Result<(HashMap<String, u32>, Vec<LinkInfo>), std::io::Error> {
let prog_out = TokioCommand::new("bpftool").args(["prog", "list", "-j"]).output().await;
let link_out = TokioCommand::new("bpftool").args(["link", "list", "-j"]).output().await;

let mut name_to_id: HashMap<String, u32> = HashMap::new();
let mut links: Vec<LinkInfo> = Vec::new();

if let Ok(o) = prog_out {
if o.status.success() {
if let Ok(v) = serde_json::from_slice::<Value>(&o.stdout) {
if let Some(arr) = v.as_array() {
for p in arr {
let id = p.get("id").and_then(|x| x.as_u64()).unwrap_or(0) as u32;
if id == 0 { continue; }
if let Some(name) = p.get("name").and_then(|x| x.as_str()) {
name_to_id.insert(name.to_string(), id);
} else if let Some(tag) = p.get("tag").and_then(|x| x.as_str()) {
name_to_id.insert(tag.to_string(), id);
}
}
}
}
}
}

if let Ok(o) = link_out {
if o.status.success() {
if let Ok(v) = serde_json::from_slice::<Value>(&o.stdout) {
if let Some(arr) = v.as_array() {
for l in arr {
let prog_id = l.get("prog_id").and_then(|x| x.as_u64()).unwrap_or(0) as u32;
if prog_id == 0 { continue; }
let attach_type = l.get("type").and_then(|x| x.as_str()).unwrap_or("").to_string();
let target = l.get("target_name").and_then(|x| x.as_str()).map(|s| s.to_string());
let hook = l.get("tp_name").and_then(|x| x.as_str()).map(|s| s.to_string());
let mut pid: Option<u32> = None;
if let Some(pid_val) = l.get("pid").and_then(|x| x.as_u64()) {
pid = Some(pid_val as u32);
} else if let Some(pids) = l.get("pids").and_then(|x| x.as_array()) {
for p in pids {
if let Some(pv) = p.get("pid").and_then(|x| x.as_u64()) {
pid = Some(pv as u32);
break;
}
}
}
links.push(LinkInfo { prog_id, pid, attach_type, target, hook });
}
}
}
}
}

Ok((name_to_id, links))
}

fn live_hook_for(
prog_index: &HashMap<String, u32>,
link_index: &Vec<LinkInfo>,
program_name: &str,
pid: u32,
) -> Option<String> {
let prog_id = prog_index.get(program_name).copied()?;
let mut candidates: Vec<&LinkInfo> = link_index.iter().filter(|l| l.prog_id == prog_id).collect();
if candidates.is_empty() { return None; }
if let Some(first_pid_match) = candidates.iter().find(|l| l.pid == Some(pid)) {
return Some(render_hook(first_pid_match));
}
Some(render_hook(candidates[0]))
}

fn render_hook(l: &LinkInfo) -> String {
let tgt = l.hook.clone().or_else(|| l.target.clone()).unwrap_or_default();
if tgt.is_empty() { l.attach_type.clone() } else { format!("{}:{}", l.attach_type, tgt) }
}

pub async fn handle_monitor() -> io::Result<()> {
enable_raw_mode()?;
let mut stdout = io::stdout();
Expand All @@ -49,7 +139,12 @@ pub async fn handle_monitor() -> io::Result<()> {
}

let st = load_state(&default_state_path());
let rows_data: Vec<AttachmentRow> = st

let (prog_index, link_index) = match get_live_bpf_indices().await {
Ok(v) => v,
Err(_) => (HashMap::new(), Vec::new()),
};
let mut rows_data: Vec<AttachmentRow> = st
.attachments
.iter()
.map(|r| {
Expand All @@ -62,6 +157,9 @@ pub async fn handle_monitor() -> io::Result<()> {
.as_ref()
.map(|p| if p.exists() { "yes" } else { "missing" })
.unwrap_or("no");
let live_hook = live_hook_for(&prog_index, &link_index, &r.name, r.pid)
.unwrap_or(hook);

let status = if proc_alive(r.pid) { "online" } else { "offline" };
let created = DateTime::from_timestamp(r.created_at, 0)
.unwrap_or(DateTime::<Utc>::UNIX_EPOCH)
Expand All @@ -71,7 +169,7 @@ pub async fn handle_monitor() -> io::Result<()> {
AttachmentRow {
name: r.name.clone(),
kind: r.kind.clone(),
hook,
hook: live_hook,
pid: r.pid.to_string(),
status: status.to_string(),
pinned: pinned.to_string(),
Expand All @@ -80,6 +178,32 @@ pub async fn handle_monitor() -> io::Result<()> {
})
.collect();

if let Ok(pool) = ensure_db_ready().await {
if let Ok(programs) = list_programs(&pool).await {
let state_names: std::collections::HashSet<String> = st.attachments.iter().map(|a| a.name.clone()).collect();
let any_attached = link_index.iter().next();
for p in programs {
if state_names.contains(&p.title) { continue; }
let (pid_str, hook_str, status_str) = if let Some(l) = any_attached {
let pid_str = l.pid.map(|x| x.to_string()).unwrap_or("-".to_string());
let hook_str = render_hook(l);
(pid_str, hook_str, "attached".to_string())
} else {
("-".to_string(), "-".to_string(), "detached".to_string())
};
rows_data.push(AttachmentRow {
name: p.title,
kind: p.status,
hook: hook_str,
pid: pid_str,
status: status_str,
pinned: "n/a".to_string(),
created: "-".to_string(),
});
}
}
}

terminal.draw(|f| {
let size = f.size();
let chunks = Layout::default()
Expand Down Expand Up @@ -134,13 +258,13 @@ pub async fn handle_monitor() -> io::Result<()> {
.borders(Borders::ALL),
)
.widths(&[
Constraint::Length(18), // Program
Constraint::Length(12), // Kind
Constraint::Length(24), // Hook
Constraint::Length(8), // PID
Constraint::Length(10), // Status
Constraint::Length(8), // Pinned
Constraint::Length(20), // Created
Constraint::Length(18),
Constraint::Length(12),
Constraint::Length(24),
Constraint::Length(8),
Constraint::Length(10),
Constraint::Length(8),
Constraint::Length(20),
])
.column_spacing(1);

Expand Down