2026-07-12 06:14:34 +00:00
|
|
|
#![cfg(target_os = "linux")]
|
|
|
|
|
|
|
|
|
|
use std::io::{BufRead, BufReader, Write};
|
|
|
|
|
use std::process::{Child, ChildStdin, Command, Stdio};
|
|
|
|
|
|
|
|
|
|
use serde_json::{Value, json};
|
|
|
|
|
|
|
|
|
|
struct WorkerProcess {
|
|
|
|
|
child: Child,
|
|
|
|
|
stdin: ChildStdin,
|
|
|
|
|
stdout: BufReader<std::process::ChildStdout>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl WorkerProcess {
|
2026-07-23 05:35:04 +00:00
|
|
|
fn spawn() -> Self {
|
2026-07-12 06:14:34 +00:00
|
|
|
let script = worker_script();
|
2026-07-23 05:35:04 +00:00
|
|
|
let mut child = Command::new("python3")
|
2026-07-12 06:14:34 +00:00
|
|
|
.arg(&script)
|
|
|
|
|
.env("DEV", "CPU")
|
2026-07-23 05:35:04 +00:00
|
|
|
.env("MVP_RUN_ID", "9")
|
|
|
|
|
.env("MVP_LOGICAL_NODE_ID", "3")
|
|
|
|
|
.env("MVP_STAGE_INDEX", "2")
|
2026-07-12 06:14:34 +00:00
|
|
|
.stdin(Stdio::piped())
|
|
|
|
|
.stdout(Stdio::piped())
|
2026-07-23 05:35:04 +00:00
|
|
|
.stderr(Stdio::piped())
|
2026-07-12 06:14:34 +00:00
|
|
|
.spawn()
|
|
|
|
|
.unwrap_or_else(|e| panic!("spawn {}: {e}", script.display()));
|
|
|
|
|
let stdin = child.stdin.take().expect("worker stdin");
|
|
|
|
|
let stdout = BufReader::new(child.stdout.take().expect("worker stdout"));
|
|
|
|
|
Self {
|
|
|
|
|
child,
|
|
|
|
|
stdin,
|
|
|
|
|
stdout,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-23 05:35:04 +00:00
|
|
|
fn send_collect_until(&mut self, command: Value, expected_type: &str) -> Vec<Value> {
|
2026-07-12 06:14:34 +00:00
|
|
|
writeln!(self.stdin, "{command}").expect("write worker command");
|
|
|
|
|
self.stdin.flush().expect("flush worker command");
|
2026-07-23 05:35:04 +00:00
|
|
|
let mut events = Vec::new();
|
2026-07-12 06:14:34 +00:00
|
|
|
loop {
|
|
|
|
|
let mut line = String::new();
|
|
|
|
|
let read = self.stdout.read_line(&mut line).expect("read worker event");
|
|
|
|
|
assert_ne!(read, 0, "worker exited before {expected_type}");
|
|
|
|
|
let event: Value = serde_json::from_str(line.trim_end()).expect("worker event JSON");
|
|
|
|
|
let actual_type = event.get("type").and_then(Value::as_str).unwrap_or("");
|
|
|
|
|
assert_ne!(
|
|
|
|
|
actual_type, "WorkerFatal",
|
|
|
|
|
"worker fatal while waiting for {expected_type}: {event}"
|
|
|
|
|
);
|
2026-07-23 05:35:04 +00:00
|
|
|
let done = actual_type == expected_type;
|
|
|
|
|
events.push(event);
|
|
|
|
|
if done {
|
|
|
|
|
return events;
|
2026-07-12 06:14:34 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Drop for WorkerProcess {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
let _ = self.child.kill();
|
|
|
|
|
let _ = self.child.wait();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-07-23 05:35:04 +00:00
|
|
|
fn benchmark_observability_worker_ready_includes_stamps_and_identity() {
|
|
|
|
|
if !tinygrad_available() {
|
|
|
|
|
eprintln!("skipping Python worker protocol check: tinygrad is unavailable");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-07-12 06:14:34 +00:00
|
|
|
|
2026-07-23 05:35:04 +00:00
|
|
|
let mut worker = WorkerProcess::spawn();
|
|
|
|
|
let events = worker.send_collect_until(
|
2026-07-12 06:14:34 +00:00
|
|
|
json!({"type":"InitializeWorker","helper_abi_version":1,"backend":{"device":"CPU"}}),
|
|
|
|
|
"WorkerReady",
|
|
|
|
|
);
|
|
|
|
|
|
2026-07-23 05:35:04 +00:00
|
|
|
assert!(
|
|
|
|
|
events
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|event| event.get("type").and_then(Value::as_str) == Some("TinygradImportStarted")),
|
|
|
|
|
"expected tinygrad import milestone in {events:?}"
|
2026-07-12 06:14:34 +00:00
|
|
|
);
|
2026-07-23 05:35:04 +00:00
|
|
|
assert!(
|
|
|
|
|
events
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|event| event.get("type").and_then(Value::as_str) == Some("WorkerReady")),
|
|
|
|
|
"expected WorkerReady in {events:?}"
|
|
|
|
|
);
|
|
|
|
|
for event in &events {
|
|
|
|
|
assert_eq!(
|
|
|
|
|
event
|
|
|
|
|
.get("benchmark")
|
|
|
|
|
.and_then(|benchmark| benchmark.get("schema"))
|
|
|
|
|
.and_then(Value::as_u64),
|
|
|
|
|
Some(1),
|
|
|
|
|
"{event}"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
event.get("run_id").and_then(Value::as_u64),
|
|
|
|
|
Some(9),
|
|
|
|
|
"{event}"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
event.get("node_id").and_then(Value::as_u64),
|
|
|
|
|
Some(3),
|
|
|
|
|
"{event}"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
event.get("stage_index").and_then(Value::as_u64),
|
|
|
|
|
Some(2),
|
|
|
|
|
"{event}"
|
|
|
|
|
);
|
2026-07-12 06:14:34 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-23 05:35:04 +00:00
|
|
|
fn tinygrad_available() -> bool {
|
|
|
|
|
Command::new("python3")
|
|
|
|
|
.args(["-c", "import tinygrad"])
|
|
|
|
|
.status()
|
|
|
|
|
.is_ok_and(|status| status.success())
|
2026-07-12 06:14:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn worker_script() -> std::path::PathBuf {
|
|
|
|
|
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
|
|
|
.join("../..")
|
|
|
|
|
.join("apps/mvp-node/tinygrad_worker.py")
|
|
|
|
|
.canonicalize()
|
|
|
|
|
.expect("tinygrad worker script exists")
|
|
|
|
|
}
|