1use scx_chaos::Builder;
6use scx_chaos::RequiresPpid;
7use scx_chaos::Scheduler;
8use scx_chaos::Trait;
9
10use scx_p2dq::SchedulerOpts as P2dqOpts;
11
12use anyhow::{Context, Result, anyhow};
13use clap::Parser;
14use log::info;
15use nix::unistd::Pid;
16
17use std::panic;
18use std::pin::Pin;
19use std::process::Command;
20use std::sync::Arc;
21use std::sync::Condvar;
22use std::sync::Mutex;
23use std::thread;
24use std::time::Duration;
25
26#[derive(Debug, Parser)]
28pub struct RandomDelayArgs {
29 #[clap(long, requires = "random_delay_min_us")]
31 pub random_delay_frequency: Option<f64>,
32
33 #[clap(long, requires = "random_delay_max_us")]
35 pub random_delay_min_us: Option<u64>,
36
37 #[clap(long, requires = "random_delay_frequency")]
39 pub random_delay_max_us: Option<u64>,
40}
41
42#[derive(Debug, Parser)]
58pub struct Args {
59 #[clap(long, action = clap::ArgAction::SetTrue, requires = "args")]
61 pub repeat_failure: bool,
62
63 #[clap(long, action = clap::ArgAction::SetTrue, requires = "args")]
65 pub repeat_success: bool,
66
67 #[clap(long, default_value = "true", action = clap::ArgAction::Set)]
70 pub ppid_targeting: bool,
71
72 #[clap(short = 'v', long, action = clap::ArgAction::Count)]
75 pub verbose: u8,
76
77 #[command(flatten, next_help_heading = "Random Delays")]
78 pub random_delay: RandomDelayArgs,
79
80 #[command(flatten, next_help_heading = "General Scheduling")]
81 pub p2dq: P2dqOpts,
82
83 #[arg(
85 long,
86 short = 'p',
87 help_heading = "Test Command",
88 conflicts_with = "args"
89 )]
90 pub pid: Option<libc::pid_t>,
91
92 #[arg(
97 trailing_var_arg = true,
98 allow_hyphen_values = true,
99 help_heading = "Test Command"
100 )]
101 pub args: Vec<String>,
102}
103
104struct BuilderIterator<'a> {
105 args: &'a Args,
106 idx: u32,
107}
108
109impl<'a> From<&'a Args> for BuilderIterator<'a> {
110 fn from(args: &'a Args) -> BuilderIterator<'a> {
111 BuilderIterator { args, idx: 0 }
112 }
113}
114
115impl<'a> Iterator for BuilderIterator<'a> {
116 type Item = Builder<'a>;
117
118 fn next(&mut self) -> Option<Self::Item> {
119 self.idx += 1;
120
121 if self.idx > 1 {
122 None
123 } else {
124 let mut traits = vec![];
125
126 if let RandomDelayArgs {
127 random_delay_frequency: Some(frequency),
128 random_delay_min_us: Some(min_us),
129 random_delay_max_us: Some(max_us),
130 } = self.args.random_delay
131 {
132 traits.push(Trait::RandomDelays {
133 frequency,
134 min_us,
135 max_us,
136 });
137 };
138
139 let requires_ppid = if self.args.ppid_targeting {
140 if let Some(p) = self.args.pid {
141 Some(RequiresPpid::IncludeParent(Pid::from_raw(p)))
142 } else if !self.args.args.is_empty() {
143 Some(RequiresPpid::ExcludeParent(Pid::this()))
144 } else {
145 None
146 }
147 } else {
148 None
149 };
150
151 Some(Builder {
152 traits,
153 verbose: self.args.verbose,
154 p2dq_opts: &self.args.p2dq,
155 requires_ppid,
156 })
157 }
158 }
159}
160
161fn main() -> Result<()> {
162 let args = Arc::new(Args::parse());
163
164 let llv = match &args.verbose {
165 0 => simplelog::LevelFilter::Info,
166 1 => simplelog::LevelFilter::Debug,
167 _ => simplelog::LevelFilter::Trace,
168 };
169 simplelog::TermLogger::init(
170 llv,
171 simplelog::ConfigBuilder::new()
172 .set_time_level(simplelog::LevelFilter::Error)
173 .set_location_level(simplelog::LevelFilter::Off)
174 .set_target_level(simplelog::LevelFilter::Off)
175 .set_thread_level(simplelog::LevelFilter::Off)
176 .build(),
177 simplelog::TerminalMode::Stderr,
178 simplelog::ColorChoice::Auto,
179 )?;
180
181 if args.pid.is_some() {
182 return Err(anyhow!("args.pid is not yet implemented"));
183 }
184
185 let shutdown = Arc::new((Mutex::new(false), Condvar::new()));
186
187 ctrlc::set_handler({
188 let shutdown = shutdown.clone();
189 move || {
190 let (lock, cvar) = &*shutdown;
191 *lock.lock().unwrap() = true;
192 cvar.notify_all();
193 }
194 })
195 .context("Error setting Ctrl-C handler")?;
196
197 let scheduler_thread = thread::spawn({
198 let args = args.clone();
199 let shutdown = shutdown.clone();
200
201 move || -> Result<()> {
202 for builder in BuilderIterator::from(&*args) {
203 info!("{:?}", &builder);
204
205 let sched: Pin<Box<Scheduler>> = builder.try_into()?;
206
207 sched.observe(&shutdown, None)?;
208 }
209
210 Ok(())
211 }
212 });
213
214 let mut should_run_app = !args.args.is_empty();
215 while should_run_app {
216 let (cmd, vargs) = args.args.split_first().unwrap();
217
218 let mut child = Command::new(cmd).args(vargs).spawn()?;
219 loop {
220 should_run_app &= !*shutdown.0.lock().unwrap();
221
222 if scheduler_thread.is_finished() {
223 child.kill()?;
224 break;
225 }
226 if let Some(s) = child.try_wait()? {
227 if s.success() && args.repeat_success {
228 should_run_app &= !*shutdown.0.lock().unwrap();
229 if should_run_app {
230 info!("app under test terminated successfully, restarting...");
231 };
232 } else if s.success() {
233 info!("app under test terminated successfully, exiting...");
234 should_run_app = false;
235 } else {
236 info!("TODO: report what the scheduler was doing when it crashed");
237 should_run_app &= !*shutdown.0.lock().unwrap() && args.repeat_failure;
238 };
239
240 break;
241 };
242
243 thread::sleep(Duration::from_millis(100));
244 }
245 }
246
247 if !args.args.is_empty() {
248 let (lock, cvar) = &*shutdown;
249 *lock.lock().unwrap() = true;
250 cvar.notify_all();
251 }
252
253 match scheduler_thread.join() {
254 Ok(_) => {}
255 Err(e) => panic::resume_unwind(e),
256 };
257
258 Ok(())
259}