dwarves-gui/src/tune_pane.rs
//! **Syscall profiles** — the review step of the Heimdall tuner in the UI.
//!
//! A THIN layer on the `dwarves tune` verbs (owner 2026-10-31: "dwarves verbs
//! first, the UI later as a thin layer on those verbs"). Pick a `<repo>:<row>`
//! and the pane asks `dwarves tune show` for it; the buttons ask `check`,
//! `probe` and `approve`. Every verb runs as a queued jera job
//! ([`dwarves::tune::jobs`]) — never on the frame — and when it lands the pane
//! draws the verb's own `dwarves.tune/1` document: per program × libc the
//! calls (near-miss and bare allows marked), what is not covered off a VM,
//! each reviewed file's approval, the drift the last check found, and the
//! kernel options each call needs when the row has a saved kernel join.
//!
//! Nothing here reads a profile or computes a finding: the document is
//! rendered as it came. The reviewer is `git config user.name` (the verb takes
//! it; an e-mail is refused there).
use std::path::PathBuf;
use std::sync::Arc;
use dwarves::tune::jobs::{JobState, TuneJob};
use dwarves::tune::{TuneArgs, TuneRequest, TuneVerb};
use egui::{Color32, RichText, Ui};
use facett_core::Facet;
use serde_json::{Value, json};
/// The tab's title.
pub const TITLE: &str = "Syscall profiles";
/// **The seam the buttons fire through.** The live one queues the verbs on
/// jera; a test injects a fake that records what was asked.
pub trait TuneRunner: Send + Sync {
/// The `<repo>:<row>` artifacts to offer.
fn artifacts(&self) -> Vec<String>;
/// Queue one verb (`note`: approve's note). `Err`: a refusal, shown verbatim.
fn submit(&self, verb: TuneVerb, artifact: &str, note: &str) -> Result<TuneJob, String>;
/// Where the job stands.
fn state(&self, job: &TuneJob) -> Option<JobState>;
}
/// The live runner: [`dwarves::tune::jobs::TuneJobs`] over the checkouts.
pub struct QueuedTune {
jobs: dwarves::tune::jobs::TuneJobs,
checkouts: PathBuf,
}
impl QueuedTune {
/// Start the queue's worker; `<repo>:<row>` resolves under `checkouts`.
pub fn start(checkouts: PathBuf) -> Result<Self, String> {
let jobs = dwarves::tune::jobs::TuneJobs::start(jera_sink()).map_err(|e| format!("{e:#}"))?;
Ok(Self { jobs, checkouts })
}
}
/// The tune jobs' ledger rows: the UI process holds no ledger of its own.
fn jera_sink() -> dwarves::tune::jobs::JobSink {
dwarves::tune::jobs::JobSink::noop()
}
impl TuneRunner for QueuedTune {
fn artifacts(&self) -> Vec<String> {
dwarves::tune::artifacts_under(&self.checkouts)
}
fn submit(&self, verb: TuneVerb, artifact: &str, note: &str) -> Result<TuneJob, String> {
let req = TuneRequest {
verb: Some(verb),
args: TuneArgs {
artifact: artifact.into(),
checkouts: Some(self.checkouts.clone()),
note: note.into(),
..Default::default()
},
tool: None,
cwd: self.checkouts.clone(),
};
self.jobs.submit(req).map_err(|e| format!("{e:#}"))
}
fn state(&self, job: &TuneJob) -> Option<JobState> {
self.jobs.state(&job.key)
}
}
/// A runner that could not start: every ask is refused with its reason.
pub struct NoRunner(pub String);
impl TuneRunner for NoRunner {
fn artifacts(&self) -> Vec<String> {
Vec::new()
}
fn submit(&self, _: TuneVerb, _: &str, _: &str) -> Result<TuneJob, String> {
Err(self.0.clone())
}
fn state(&self, _: &TuneJob) -> Option<JobState> {
None
}
}
/// The checkouts the shipped tab reads: `$DWARVES_CHECKOUTS`, else
/// `$DWARVES_CONSTELLATION`, else the parent of the checkout the UI runs in.
pub fn default_checkouts() -> PathBuf {
for k in ["DWARVES_CHECKOUTS", "DWARVES_CONSTELLATION"] {
if let Some(v) = std::env::var_os(k).filter(|v| !v.is_empty()) {
return PathBuf::from(v);
}
}
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let root = dwarves::tune::checkout_root(&cwd);
root.parent().map(PathBuf::from).unwrap_or(root)
}
struct Pending {
verb: TuneVerb,
job: TuneJob,
phase: &'static str,
}
/// **The pane.**
pub struct TunePane {
runner: Arc<dyn TuneRunner>,
artifacts: Option<Vec<String>>,
selected: Option<String>,
/// The last document a verb answered (drawn as it came).
doc: Option<Value>,
/// The last `check` document (its drift stays on screen after approve).
last_check: Option<Value>,
/// The last refusal's line.
error: Option<String>,
pending: Option<Pending>,
note: String,
/// Every verb asked, in order (`show`, `check`, …) — the robot's witness.
asked: Vec<String>,
}
impl TunePane {
pub fn new(runner: Arc<dyn TuneRunner>) -> Self {
Self {
runner,
artifacts: None,
selected: None,
doc: None,
last_check: None,
error: None,
pending: None,
note: String::new(),
asked: Vec::new(),
}
}
/// The shipped tab: the jera-queued verbs over [`default_checkouts`].
pub fn live() -> Self {
let runner: Arc<dyn TuneRunner> = match QueuedTune::start(default_checkouts()) {
Ok(q) => Arc::new(q),
Err(e) => Arc::new(NoRunner(format!("the tune queue did not start: {e}"))),
};
Self::new(runner)
}
fn ask(&mut self, verb: TuneVerb) {
let Some(a) = self.selected.clone() else { return };
if self.pending.is_some() {
return;
}
let note = if verb == TuneVerb::Approve { self.note.trim().to_string() } else { String::new() };
self.asked.push(verb.word().to_string());
match self.runner.submit(verb, &a, ¬e) {
Ok(job) => {
self.error = None;
self.pending = Some(Pending { verb, job, phase: "queued" });
}
Err(e) => self.error = Some(e),
}
}
fn select(&mut self, a: String) {
if self.selected.as_deref() == Some(a.as_str()) || self.pending.is_some() {
return;
}
self.selected = Some(a);
self.doc = None;
self.last_check = None;
self.error = None;
self.ask(TuneVerb::Show);
}
/// Land a finished job, if any.
fn poll(&mut self) {
let Some(p) = self.pending.as_mut() else { return };
match self.runner.state(&p.job) {
Some(JobState::Queued) => p.phase = "queued",
Some(JobState::Running) => p.phase = "running",
Some(JobState::Done(v)) => {
if p.verb == TuneVerb::Check {
self.last_check = Some(v.clone());
}
if p.verb == TuneVerb::Approve {
self.note.clear();
}
self.doc = Some(v);
self.error = None;
self.pending = None;
}
Some(JobState::Failed(v)) => {
self.error = Some(v["error"].as_str().unwrap_or("the verb failed").to_string());
self.pending = None;
}
None => {
self.error = Some(format!("job {} is not on the tune queue", p.job.job_id));
self.pending = None;
}
}
}
fn review(&self) -> Option<&Value> {
self.doc.as_ref().map(|d| &d["review"]).filter(|r| !r.is_null())
}
fn draw_head(&mut self, ui: &mut Ui) {
ui.heading(TITLE);
let artifacts = self.artifacts.get_or_insert_with(|| self.runner.artifacts()).clone();
ui.horizontal_wrapped(|ui| {
ui.label("Row:");
if artifacts.is_empty() {
ui.label(RichText::new("no row has profiles (.nornir/profiles/<row>/measure.toml) under the checkouts").italics());
}
for a in artifacts {
let on = self.selected.as_deref() == Some(a.as_str());
if ui.selectable_label(on, &a).clicked() {
self.select(a);
}
}
});
if self.selected.is_none() {
return;
}
let idle = self.pending.is_none();
ui.horizontal(|ui| {
if ui
.add_enabled(idle, egui::Button::new("Check"))
.on_hover_text("dwarves tune check: drift (a call outside a profile, naming the test) and the approval")
.clicked()
{
self.ask(TuneVerb::Check);
}
if ui
.add_enabled(idle, egui::Button::new("Probe"))
.on_hover_text("dwarves tune probe: re-measure the row's programs and regenerate the profiles")
.clicked()
{
self.ask(TuneVerb::Probe);
}
let caption = ui.label("Note");
ui.add(egui::TextEdit::singleline(&mut self.note).hint_text("why the profiles are fine").desired_width(260.0))
.labelled_by(caption.id);
if ui
.add_enabled(idle, egui::Button::new("Approve"))
.on_hover_text("dwarves tune approve: record the review (reviewer = git config user.name) in approved.toml")
.clicked()
{
self.ask(TuneVerb::Approve);
}
});
if let Some(p) = &self.pending {
ui.label(format!("dwarves tune {} — {} in jera (job {})", p.verb.word(), p.phase, p.job.job_id));
}
if let Some(e) = &self.error {
ui.colored_label(Color32::from_rgb(220, 70, 60), format!("refused: {e}"));
}
}
fn draw_doc(&mut self, ui: &mut Ui) {
let Some(doc) = self.doc.clone() else { return };
let Some(review) = self.review().cloned() else { return };
ui.label(RichText::new(format!("last verb: dwarves tune {} — {}", doc["verb"].as_str().unwrap_or("?"), if doc["ok"] == true { "ok" } else { "findings" })).weak());
ui.separator();
ui.strong("Approval");
let files = review["files"].as_array().cloned().unwrap_or_default();
if files.is_empty() {
ui.label("no reviewed files yet: probe the row first");
}
for f in &files {
let colour = match f["state"].as_str() {
Some("approved") => Color32::from_rgb(80, 170, 90),
Some("changed") => Color32::from_rgb(220, 160, 40),
_ => Color32::from_rgb(220, 70, 60),
};
ui.horizontal(|ui| {
ui.monospace(f["file"].as_str().unwrap_or("?"));
ui.colored_label(colour, f["line"].as_str().unwrap_or("?"));
});
}
if let Some(c) = &self.last_check {
ui.separator();
let ok = c["ok"] == true;
ui.strong(if ok { "Last check: no drift" } else { "Last check: findings" });
for d in c["drift"].as_array().into_iter().flatten() {
let tests: Vec<&str> = d["tests"].as_array().into_iter().flatten().filter_map(Value::as_str).collect();
ui.colored_label(
Color32::from_rgb(220, 70, 60),
format!(
"drift: {} ({}) new call {} ({}), made by test {}",
d["program"].as_str().unwrap_or("?"),
d["libc"].as_str().unwrap_or("?"),
d["call"].as_str().unwrap_or("?"),
d["action"].as_str().unwrap_or("?"),
tests.join(", ")
),
);
}
for p in c["problems"].as_array().into_iter().flatten().filter_map(Value::as_str) {
if !p.starts_with("drift: ") {
ui.colored_label(Color32::from_rgb(220, 160, 40), p);
}
}
}
ui.separator();
for p in review["programs"].as_array().into_iter().flatten() {
let calls = p["calls"].as_array().cloned().unwrap_or_default();
let near = calls.iter().filter(|c| c["near_miss"].as_array().is_some_and(|a| !a.is_empty())).count();
let bare = calls.iter().filter(|c| c["bare"] == true).count();
let head = format!(
"{} ({} {}) — {} calls, {} near-miss, {} bare",
p["program"].as_str().unwrap_or("?"),
p["arch"].as_str().unwrap_or("?"),
p["libc"].as_str().unwrap_or("?"),
calls.len(),
near,
bare
);
egui::CollapsingHeader::new(head).default_open(true).show(ui, |ui| {
ui.monospace(p["file"].as_str().unwrap_or("?"));
egui::Grid::new(format!("tune-calls-{}-{}", p["program"], p["libc"])).striped(true).show(ui, |ui| {
for c in &calls {
ui.monospace(c["syscall"].as_str().unwrap_or("?"));
ui.label(c["action"].as_str().unwrap_or("?"));
ui.horizontal(|ui| {
if c["conditional"] == true {
ui.label(RichText::new("args checked").weak());
}
for n in c["near_miss"].as_array().into_iter().flatten().filter_map(Value::as_str) {
ui.colored_label(Color32::from_rgb(220, 160, 40), format!("near-miss {n}"));
}
if c["bare"] == true {
ui.colored_label(Color32::from_rgb(220, 70, 60), "BARE allow");
}
let opts: Vec<&str> = c["options"].as_array().into_iter().flatten().filter_map(Value::as_str).collect();
if !opts.is_empty() {
ui.label(RichText::new(format!("needs {}", opts.join(", "))).weak());
}
});
ui.end_row();
}
});
for (what, key) in [("W^X", "wx"), ("unknown", "unknown"), ("fixed", "fixed"), ("note", "notes")] {
for x in p[key].as_array().into_iter().flatten().filter_map(Value::as_str) {
ui.label(format!("{what}: {x}"));
}
}
});
}
let not_covered = review["not_covered"].as_array().cloned().unwrap_or_default();
if !not_covered.is_empty() {
ui.separator();
ui.strong("Not covered off a VM");
for n in ¬_covered {
ui.label(format!("{}: {}", n["program"].as_str().unwrap_or("?"), n["why"].as_str().unwrap_or("?")));
}
}
ui.separator();
match review["kernel"].as_object() {
Some(k) => {
ui.strong(format!(
"Kernel {} {} {}",
k["kernel"].as_str().unwrap_or("?"),
k["version"].as_str().unwrap_or("?"),
k["arch"].as_str().unwrap_or("?")
));
for o in k["options"].as_array().into_iter().flatten().filter_map(Value::as_str) {
ui.monospace(o);
}
for v in k["vetoes"].as_array().into_iter().flatten() {
let line = v["line"].as_str().unwrap_or("?");
if v["near_miss"] == true {
ui.colored_label(Color32::from_rgb(220, 160, 40), format!("veto (warning, near-miss): {line}"));
} else {
ui.colored_label(Color32::from_rgb(220, 70, 60), format!("veto: {line}"));
}
}
}
None => {
ui.label(RichText::new(review["kernel_hint"].as_str().unwrap_or("")).italics());
}
}
}
}
impl Facet for TunePane {
fn title(&self) -> &str {
TITLE
}
fn component(&self) -> &'static str {
"tunepane"
}
fn ui(&mut self, ui: &mut Ui) {
self.poll();
if self.pending.is_some() {
ui.ctx().request_repaint_after(std::time::Duration::from_millis(100));
}
egui::ScrollArea::vertical().auto_shrink([false, false]).show(ui, |ui| {
self.draw_head(ui);
self.draw_doc(ui);
});
}
fn state_json(&self) -> Value {
json!({
"component": "tunepane",
"artifacts": self.artifacts,
"artifact": self.selected,
"asked": self.asked,
"pending": self.pending.as_ref().map(|p| json!({ "verb": p.verb.word(), "job_id": p.job.job_id, "phase": p.phase })),
"error": self.error,
"note": self.note,
"verb": self.doc.as_ref().map(|d| d["verb"].clone()),
"ok": self.doc.as_ref().map(|d| d["ok"].clone()),
"review": self.review().cloned(),
"last_check": self.last_check.as_ref().map(|c| json!({ "ok": c["ok"], "drift": c["drift"], "problems": c["problems"] })),
})
}
fn update_json(&mut self, msg: &str) {
let Ok(v) = serde_json::from_str::<Value>(msg) else { return };
if let Some(a) = v["select"].as_str() {
self.select(a.to_string());
}
match v["verb"].as_str() {
Some("check") => self.ask(TuneVerb::Check),
Some("probe") => self.ask(TuneVerb::Probe),
Some("approve") => {
if let Some(n) = v["note"].as_str() {
self.note = n.into();
}
self.ask(TuneVerb::Approve)
}
_ => {}
}
}
}