crates/knut-popviz/src/main.rs
//! `knut-popviz` — a desktop egui driver for the population bulk-loader (feature
//! `gui`). Watch the 12-label graph fill FalkorDB live, per label, with
//! throughput — and the same state is written to `$KNUT_POPVIZ_STATE` every
//! ~300 ms so the GUI can be robot-tested headlessly (see `lib.rs`).
//!
//! ```bash
//! cargo run -p knut-popviz --features gui -- --url redis://127.0.0.1:6379 --graph pop --scale 0.001 --drop
//! ```
//!
//! ## Flags
//!
//! - `--url <redis://…>` — FalkorDB to load into (default `redis://127.0.0.1:6379`).
//! - `--graph <name>` / `--scale <f64>` / `--drop` — graph name, size, wipe-first.
//! - `--ingest <auto|bulk|unwind>` — FalkorDB lane (default `auto`): native
//! `GRAPH.BULK` fast lane on an empty/`--drop`ped graph, parallel-`UNWIND`
//! otherwise. `bulk`/`unwind` pin the lane (the parity test uses both).
//! - `--tls-cert <pem> --tls-key <pem> [--tls-ca <pem>]` — mutual TLS, for a FalkorDB that
//! takes nothing else (`--url rediss://host:6379`). With `tls-auth-clients-user CN` on the
//! server the certificate's CN is the user; no password. `--tls-ca` verifies the server
//! (default: the system roots).
//! - `--autostart` / `KNUT_POPVIZ_AUTOSTART` — kick off the *load* on the first
//! frame (for headless robot tests). Does **not** touch the DB.
//! - `--start-db` / `KNUT_FALKORDB_AUTOSTART` — if `--url` is unreachable, bring
//! up a FalkorDB container from Rust (testcontainers → Podman, LAW 7), wait
//! until ready, and connect to its mapped port. Opt-in so a user with their own
//! DB is never surprised. Image from `KNUT_FALKORDB_IMAGE`
//! (default `localhost/knut-falkordb-valkey9:latest` — valkey-9, never stock redis).
use knut_popviz::app::PopvizApp;
use knut_popviz::{IngestMode, LoadParams, Target};
fn main() -> eframe::Result<()> {
let args: Vec<String> = std::env::args().collect();
// `--version`/`-V` — the suite identity `knut-popviz v<ver> (<git-sha>)`,
// stamped pure-fs by build.rs. popviz hand-rolls its arg parsing (no clap),
// so this is checked before anything else and exits.
if args.iter().any(|a| a == "--version" || a == "-V") {
println!("knut-popviz {}", knut_popviz::VERSION_LINE);
return Ok(());
}
let opt = |k: &str, d: &str| {
args.windows(2)
.find(|w| w[0] == k)
.map(|w| w[1].clone())
.unwrap_or_else(|| d.into())
};
let target = match opt("--target", "falkor").as_str() {
"iceberg" => Target::Iceberg {
warehouse: opt("--warehouse", "/tmp/knut_popviz_warehouse"),
},
_ => Target::Falkor,
};
let mut params = LoadParams {
url: opt("--url", "redis://127.0.0.1:6379"),
graph: opt("--graph", "pop"),
scale: opt("--scale", "0.001").parse().unwrap_or(0.001),
drop: args.iter().any(|a| a == "--drop"),
target,
// `--ingest auto|bulk|unwind` (default auto): an empty/`--drop`ped graph
// takes the native GRAPH.BULK fast lane, else the parallel-UNWIND path.
ingest: IngestMode::from_str_lenient(&opt("--ingest", "auto")),
tls: None,
};
// `--tls-cert` + `--tls-key` together or not at all: half an identity is a mistake to
// stop on, not a plain connect to fall back to.
let (cert, key) = (opt("--tls-cert", ""), opt("--tls-key", ""));
if !cert.is_empty() || !key.is_empty() {
if cert.is_empty() || key.is_empty() {
eprintln!("knut-popviz: --tls-cert and --tls-key go together");
std::process::exit(2);
}
let ca = opt("--tls-ca", "");
let ca = (!ca.is_empty()).then(|| std::path::PathBuf::from(ca));
match graphar_falkordb::FalkorTls::from_files(cert.as_ref(), key.as_ref(), ca.as_deref()) {
Ok(id) => params.tls = Some(id),
Err(e) => {
eprintln!("knut-popviz: {e}");
std::process::exit(2);
}
}
}
// FalkorDB autostart (opt-in via `--start-db` / `KNUT_FALKORDB_AUTOSTART`):
// when the configured `--url` is unreachable, bring up a FalkorDB container
// from Rust (testcontainers → Podman, LAW 7), wait until ready, and rewrite
// the URL to the container's mapped port. The guard must outlive the app, so
// it's held until `run_native` returns. Distinct from the pre-existing
// `--autostart` flag, which kicks off the *load* on the first frame.
let want_db_autostart =
std::env::var("KNUT_FALKORDB_AUTOSTART").is_ok() || args.iter().any(|a| a == "--start-db");
let _db_guard = if want_db_autostart && matches!(params.target, Target::Falkor) {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("tokio runtime for FalkorDB autostart");
match rt.block_on(graphar_falkordb::falkordb_container::ensure_falkordb(
¶ms.url,
)) {
Ok((url, guard)) => {
if guard.is_some() {
eprintln!("knut-popviz: started FalkorDB container at {url}");
}
params.url = url;
guard
}
Err(e) => {
eprintln!("knut-popviz: FalkorDB autostart failed: {e}");
None
}
}
} else {
None
};
eframe::run_native(
"knut-popviz",
eframe::NativeOptions::default(),
Box::new(|_cc| Ok(Box::new(PopvizApp::new(params)))),
)
}