2026-02-11 15:23:26 +00:00
|
|
|
//! Line-oriented diagnostic protocol for LLM-driven runtime investigation.
|
|
|
|
|
//!
|
2026-02-13 17:34:01 +00:00
|
|
|
//! Delegates all command logic to the `command` module.
|
2026-02-13 07:42:44 +00:00
|
|
|
//!
|
2026-02-11 15:23:26 +00:00
|
|
|
//! Send text commands on stdin, receive JSON responses on stdout (one per line).
|
|
|
|
|
//! All human-readable diagnostics go to stderr.
|
|
|
|
|
//!
|
|
|
|
|
//! # Protocol
|
|
|
|
|
//!
|
|
|
|
|
//! ```text
|
|
|
|
|
//! → overview
|
|
|
|
|
//! ← {"ok":true,"command":"overview","data":{"workers":4,"actors":120,...}}
|
|
|
|
|
//!
|
|
|
|
|
//! → hot 5
|
|
|
|
|
//! ← {"ok":true,"command":"hot","data":[{"address":"a1b2...","worker_id":2,"mailbox_depth":47},..]}
|
|
|
|
|
//!
|
|
|
|
|
//! → diff 2
|
|
|
|
|
//! ← {"ok":true,"command":"diff","data":{"elapsed_s":2.0,"delta_messages":8432,"msg_per_sec":4216.0,...}}
|
|
|
|
|
//! ```
|
|
|
|
|
|
2026-02-12 09:13:50 +00:00
|
|
|
use std::collections::HashMap;
|
2026-02-11 15:23:26 +00:00
|
|
|
use std::io::{self, BufRead, Write};
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
|
|
use swactor::runtime::Runtime;
|
2026-02-13 17:34:01 +00:00
|
|
|
use crate::command::{CommandContext, CommandRouter};
|
2026-02-11 15:23:26 +00:00
|
|
|
|
|
|
|
|
use crate::collector::StatsCollector;
|
|
|
|
|
|
|
|
|
|
/// Run the investigate REPL. Blocks until stdin is closed or `quit` is received.
|
|
|
|
|
pub fn run_investigate(runtime: Arc<Runtime>, collector: Arc<StatsCollector>) -> io::Result<()> {
|
2026-02-13 07:42:44 +00:00
|
|
|
let router = CommandRouter::with_builtins();
|
|
|
|
|
let ctx = CommandContext::with_enricher(runtime, collector);
|
|
|
|
|
|
2026-02-11 15:23:26 +00:00
|
|
|
let stdin = io::stdin();
|
|
|
|
|
let mut stdout = io::stdout();
|
|
|
|
|
|
|
|
|
|
eprintln!("swactor investigate protocol ready — send `help` for commands");
|
|
|
|
|
|
|
|
|
|
for line in stdin.lock().lines() {
|
|
|
|
|
let line = line?;
|
|
|
|
|
let line = line.trim();
|
|
|
|
|
if line.is_empty() {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2026-02-13 07:42:44 +00:00
|
|
|
if line == "quit" || line == "exit" {
|
2026-02-12 09:13:50 +00:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-13 17:34:01 +00:00
|
|
|
let req = crate::command::parse_line(line);
|
2026-02-13 07:42:44 +00:00
|
|
|
let resp = router.dispatch(&req, &ctx);
|
2026-02-11 15:23:26 +00:00
|
|
|
|
2026-02-13 07:42:44 +00:00
|
|
|
stdout.write_all(resp.to_json_line().as_bytes())?;
|
2026-02-11 15:23:26 +00:00
|
|
|
stdout.write_all(b"\n")?;
|
|
|
|
|
stdout.flush()?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-12 09:13:50 +00:00
|
|
|
/// Dispatch an investigate command from HTTP query parameters.
|
|
|
|
|
///
|
2026-02-13 07:42:44 +00:00
|
|
|
/// Maps `?cmd=overview`, `?cmd=hot&n=10`, etc. to the appropriate command.
|
2026-02-12 09:13:50 +00:00
|
|
|
pub fn dispatch_command(
|
|
|
|
|
params: &HashMap<String, String>,
|
2026-02-13 07:42:44 +00:00
|
|
|
router: &CommandRouter,
|
|
|
|
|
ctx: &CommandContext,
|
2026-02-12 09:13:50 +00:00
|
|
|
) -> String {
|
2026-02-13 17:34:01 +00:00
|
|
|
let req = crate::command::from_query_params(params);
|
2026-02-13 07:42:44 +00:00
|
|
|
router.dispatch(&req, ctx).to_json_line()
|
2026-02-11 15:23:26 +00:00
|
|
|
}
|