Skip to main content

scx_pandemonium/cli/
run.rs

1use std::io::{BufRead, BufReader};
2use std::os::unix::process::CommandExt;
3use std::path::Path;
4use std::process::{Command, Stdio};
5use std::time::Duration;
6
7use anyhow::{bail, Result};
8
9use super::{binary_path, LOG_DIR, TARGET_DIR};
10
11fn build_scheduler() -> Result<()> {
12    log_info!("Building PANDEMONIUM (release)...");
13
14    let project_root = env!("CARGO_MANIFEST_DIR");
15    let output = Command::new("cargo")
16        .args(["build", "--release"])
17        .env("CARGO_TARGET_DIR", TARGET_DIR)
18        .current_dir(project_root)
19        .stderr(Stdio::piped())
20        .stdout(Stdio::null())
21        .output()?;
22
23    if !output.status.success() {
24        let stderr = String::from_utf8_lossy(&output.stderr);
25        bail!("BUILD FAILED:\n{}", stderr);
26    }
27
28    let bin = binary_path();
29    let size = std::fs::metadata(&bin).map(|m| m.len() / 1024).unwrap_or(0);
30    log_info!("Build complete: {} ({} KB)", bin, size);
31    Ok(())
32}
33
34fn capture_dmesg_cursor() -> Option<String> {
35    let output = Command::new("journalctl")
36        .args(["-k", "--no-pager", "-n", "1", "--show-cursor"])
37        .output()
38        .ok()?;
39    if !output.status.success() {
40        return None;
41    }
42    let stdout = String::from_utf8_lossy(&output.stdout);
43    for line in stdout.trim().lines().rev() {
44        if line.starts_with("-- cursor:") {
45            return Some(line.splitn(2, ':').nth(1)?.trim().to_string());
46        }
47    }
48    None
49}
50
51fn capture_dmesg_after(cursor: Option<&str>) -> String {
52    let mut cmd = Command::new("journalctl");
53    cmd.args(["-k", "--no-pager"]);
54    if let Some(c) = cursor {
55        cmd.args(["--after-cursor", c]);
56    }
57    let output = match cmd.output() {
58        Ok(o) if o.status.success() => o,
59        _ => return String::new(),
60    };
61    let stdout = String::from_utf8_lossy(&output.stdout);
62    let mut relevant = Vec::new();
63    for line in stdout.lines() {
64        if line.is_empty() || line.starts_with("-- ") {
65            continue;
66        }
67        let low = line.to_lowercase();
68        if low.contains("sched_ext") || low.contains("scx") || low.contains("pandemonium") {
69            relevant.push(line.to_string());
70        }
71    }
72    relevant.join("\n")
73}
74
75fn save_logs(
76    scheduler_output: &str,
77    dmesg: &str,
78    returncode: i32,
79) -> Result<(String, String, String)> {
80    std::fs::create_dir_all(LOG_DIR)?;
81
82    let stamp = chrono_stamp();
83
84    let sched_path = format!("{}/run-{}.log", LOG_DIR, stamp);
85    std::fs::write(&sched_path, scheduler_output)?;
86
87    let dmesg_path = format!("{}/dmesg-{}.log", LOG_DIR, stamp);
88    std::fs::write(
89        &dmesg_path,
90        if dmesg.is_empty() {
91            "(NO RELEVANT KERNEL MESSAGES)\n"
92        } else {
93            dmesg
94        },
95    )?;
96
97    let report_path = format!("{}/report-{}.log", LOG_DIR, stamp);
98    let report = format!(
99        "PANDEMONIUM RUN -- {stamp}\n\
100         EXIT CODE: {returncode}\n\n\
101         SCHEDULER OUTPUT\n\
102         {scheduler_output}\n\n\
103         KERNEL LOG (DMESG)\n\
104         {dmesg_text}\n",
105        dmesg_text = if dmesg.is_empty() {
106            "(NO RELEVANT KERNEL MESSAGES)"
107        } else {
108            dmesg
109        },
110    );
111    std::fs::write(&report_path, &report)?;
112
113    let latest = format!("{}/latest.log", LOG_DIR);
114    let _ = std::fs::remove_file(&latest);
115    let _ = std::os::unix::fs::symlink(&report_path, &latest);
116
117    Ok((sched_path, dmesg_path, report_path))
118}
119
120fn chrono_stamp() -> String {
121    let output = Command::new("date").arg("+%Y%m%d-%H%M%S").output().ok();
122    match output {
123        Some(o) if o.status.success() => String::from_utf8_lossy(&o.stdout).trim().to_string(),
124        _ => "unknown".to_string(),
125    }
126}
127
128pub fn run_start(observe: bool, sched_args: &[String]) -> Result<()> {
129    // BUILD FIRST
130    build_scheduler()?;
131
132    let bin = binary_path();
133    if !Path::new(&bin).exists() {
134        bail!("BINARY NOT FOUND AT {}", bin);
135    }
136
137    let mut cmd_args = Vec::new();
138    if observe {
139        cmd_args.push("--verbose".to_string());
140        cmd_args.push("--dump-log".to_string());
141    }
142    cmd_args.extend(sched_args.iter().cloned());
143
144    let full_cmd = format!("sudo {} {}", bin, cmd_args.join(" "));
145    log_info!("Running: {}", full_cmd);
146
147    let cursor = capture_dmesg_cursor();
148
149    let mut child = Command::new("sudo")
150        .arg(&bin)
151        .args(&cmd_args)
152        .process_group(0)
153        .stdout(Stdio::piped())
154        .stderr(Stdio::piped())
155        .spawn()?;
156
157    let stdout = child.stdout.take().unwrap();
158    let reader = BufReader::new(stdout);
159    let mut output_lines = Vec::new();
160
161    for line in reader.lines() {
162        match line {
163            Ok(l) => {
164                println!("{}", l);
165                output_lines.push(l);
166            }
167            Err(_) => break,
168        }
169    }
170
171    let status = child.wait()?;
172
173    let scheduler_output = output_lines.join("\n");
174    let returncode = status.code().unwrap_or(-1);
175
176    log_info!("PANDEMONIUM exited with code {}", returncode);
177
178    // BRIEF PAUSE FOR KERNEL LOG FLUSH
179    std::thread::sleep(Duration::from_millis(200));
180    let dmesg = capture_dmesg_after(cursor.as_deref());
181
182    // SAVE LOGS
183    let (sched_path, dmesg_path, report_path) = save_logs(&scheduler_output, &dmesg, returncode)?;
184
185    // PRINT DMESG
186    if dmesg.is_empty() {
187        log_info!("Kernel log: no relevant sched_ext messages");
188    } else {
189        log_info!("Kernel log ({} lines):", dmesg.lines().count());
190        for line in dmesg.lines() {
191            println!("  {}", line);
192        }
193    }
194
195    match returncode {
196        0 => log_info!("Status: clean exit"),
197        130 => log_info!("Status: user interrupted (CTRL+C)"),
198        _ => log_warn!("Status: exit code {}", returncode),
199    }
200
201    log_info!("Logs saved to {}/", LOG_DIR);
202    log_info!("  scheduler: {}", sched_path);
203    log_info!("  dmesg:     {}", dmesg_path);
204    log_info!("  combined:  {}", report_path);
205    log_info!("  latest:    {}/latest.log", LOG_DIR);
206
207    Ok(())
208}
209
210pub fn run_dmesg() -> Result<()> {
211    let output = Command::new("journalctl")
212        .args(["-k", "--no-pager", "-n", "50"])
213        .output()?;
214
215    if !output.status.success() {
216        bail!("journalctl failed");
217    }
218
219    let stdout = String::from_utf8_lossy(&output.stdout);
220    let mut found = false;
221    for line in stdout.lines() {
222        if line.is_empty() || line.starts_with("-- ") {
223            continue;
224        }
225        let low = line.to_lowercase();
226        if low.contains("sched_ext") || low.contains("scx") || low.contains("pandemonium") {
227            println!("{}", line);
228            found = true;
229        }
230    }
231
232    if !found {
233        log_info!("No recent sched_ext/PANDEMONIUM kernel messages");
234    }
235
236    Ok(())
237}