scx_pandemonium/cli/
run.rs1use 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_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 std::thread::sleep(Duration::from_millis(200));
180 let dmesg = capture_dmesg_after(cursor.as_deref());
181
182 let (sched_path, dmesg_path, report_path) = save_logs(&scheduler_output, &dmesg, returncode)?;
184
185 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}