1use scx_utils::compat;
7use scxtop::bpf_skel::types::bpf_event;
8use scxtop::cli::{generate_completions, Cli, Commands, TraceArgs, TuiArgs};
9use scxtop::config::Config;
10use scxtop::edm::{ActionHandler, BpfEventActionPublisher, BpfEventHandler, EventDispatchManager};
11use scxtop::mangoapp::poll_mangoapp;
12use scxtop::read_file_string;
13use scxtop::tracer::Tracer;
14use scxtop::Action;
15use scxtop::App;
16use scxtop::Event;
17use scxtop::Key;
18use scxtop::KeyMap;
19use scxtop::PerfettoTraceManager;
20use scxtop::Tui;
21use scxtop::SCHED_NAME_PATH;
22use scxtop::{bpf_skel::*, AppState};
23
24use anyhow::anyhow;
25use anyhow::Result;
26use clap::{CommandFactory, Parser};
27use libbpf_rs::skel::OpenSkel;
28use libbpf_rs::skel::SkelBuilder;
29use libbpf_rs::Link;
30use libbpf_rs::ProgramInput;
31use libbpf_rs::RingBufferBuilder;
32use libbpf_rs::UprobeOpts;
33use log::debug;
34use log::info;
35use ratatui::crossterm::event::{KeyCode::Char, KeyEvent};
36use simplelog::{
37 ColorChoice, Config as SimplelogConfig, LevelFilter, TermLogger, TerminalMode, WriteLogger,
38};
39use std::sync::atomic::AtomicBool;
40use tokio::sync::mpsc;
41
42use std::ffi::CString;
43use std::fs::File;
44use std::mem::MaybeUninit;
45use std::str::FromStr;
46use std::sync::atomic::Ordering;
47use std::sync::Arc;
48use std::time::Duration;
49
50fn get_action(app: &App, keymap: &KeyMap, event: Event) -> Action {
51 match event {
52 Event::Error => Action::None,
53 Event::Tick => Action::Tick,
54 Event::TickRateChange(tick_rate_ms) => {
55 Action::TickRateChange(std::time::Duration::from_millis(tick_rate_ms))
56 }
57 Event::Key(key) => handle_key_event(app, keymap, key),
58 Event::Paste(paste) => match app.state() {
59 AppState::Event => Action::InputEntry(paste),
60 _ => Action::None,
61 },
62 _ => Action::None,
63 }
64}
65
66fn handle_key_event(app: &App, keymap: &KeyMap, key: KeyEvent) -> Action {
67 match key.code {
68 Char(c) => match app.state() {
69 AppState::Event => Action::InputEntry(c.to_string()),
70 _ => keymap.action(&Key::Char(c)),
71 },
72 _ => keymap.action(&Key::Code(key.code)),
73 }
74}
75
76fn attach_progs(skel: &mut BpfSkel) -> Result<Vec<Link>> {
78 let mut links = vec![
80 skel.progs.on_sched_cpu_perf.attach()?,
81 skel.progs.scx_sched_reg.attach()?,
82 skel.progs.scx_sched_unreg.attach()?,
83 skel.progs.on_sched_switch.attach()?,
84 skel.progs.on_sched_wakeup.attach()?,
85 skel.progs.on_sched_wakeup_new.attach()?,
86 skel.progs.on_sched_waking.attach()?,
87 ];
88
89 if compat::ksym_exists("scx_bpf_dsq_insert_vtime")? {
91 if let Ok(link) = skel.progs.scx_insert_vtime.attach() {
92 links.push(link);
93 }
94 if let Ok(link) = skel.progs.scx_insert.attach() {
95 links.push(link);
96 }
97 if let Ok(link) = skel.progs.scx_dsq_move.attach() {
98 links.push(link);
99 }
100 if let Ok(link) = skel.progs.scx_dsq_move_set_vtime.attach() {
101 links.push(link);
102 }
103 if let Ok(link) = skel.progs.scx_dsq_move_set_slice.attach() {
104 links.push(link);
105 }
106 } else {
107 if let Ok(link) = skel.progs.scx_dispatch.attach() {
108 links.push(link);
109 }
110 if let Ok(link) = skel.progs.scx_dispatch_vtime.attach() {
111 links.push(link);
112 }
113 if let Ok(link) = skel.progs.scx_dispatch_from_dsq_set_vtime.attach() {
114 links.push(link);
115 }
116 if let Ok(link) = skel.progs.scx_dispatch_from_dsq_set_slice.attach() {
117 links.push(link);
118 }
119 if let Ok(link) = skel.progs.scx_dispatch_from_dsq.attach() {
120 links.push(link);
121 }
122 }
123 if let Ok(link) = skel.progs.on_cpuhp_enter.attach() {
124 links.push(link);
125 }
126 if let Ok(link) = skel.progs.on_cpuhp_exit.attach() {
127 links.push(link);
128 }
129 if let Ok(link) = skel.progs.on_pstate_sample.attach() {
130 links.push(link);
131 }
132
133 Ok(links)
134}
135
136fn run_trace(trace_args: &TraceArgs) -> Result<()> {
137 TermLogger::init(
138 match trace_args.verbose {
139 0 => simplelog::LevelFilter::Info,
140 1 => simplelog::LevelFilter::Debug,
141 _ => simplelog::LevelFilter::Trace,
142 },
143 SimplelogConfig::default(),
144 TerminalMode::Mixed,
145 ColorChoice::Auto,
146 )?;
147
148 let config = Config::default_config();
149 let worker_threads = config.worker_threads() as usize;
150 tokio::runtime::Builder::new_multi_thread()
151 .enable_all()
152 .worker_threads(if worker_threads > 2 {
153 worker_threads
154 } else {
155 4
156 })
157 .build()
158 .unwrap()
159 .block_on(async {
160 let (action_tx, mut action_rx) = mpsc::unbounded_channel();
161
162 let mut open_object = MaybeUninit::uninit();
163 let mut builder = BpfSkelBuilder::default();
164 if trace_args.verbose > 2 {
165 builder.obj_builder.debug(true);
166 }
167
168 let skel = builder.open(&mut open_object)?;
169 skel.maps.data_data.sample_rate = 1;
171 compat::cond_kprobe_enable("gpu_memory_total", &skel.progs.on_gpu_memory_total)?;
172 compat::cond_kprobe_enable("hw_pressure_update", &skel.progs.on_hw_pressure_update)?;
173
174 let mut skel = skel.load()?;
175 let mut links = attach_progs(&mut skel)?;
176 links.push(skel.progs.on_sched_fork.attach()?);
177 links.push(skel.progs.on_sched_exec.attach()?);
178 links.push(skel.progs.on_sched_exit.attach()?);
179
180 let trace_dur = std::time::Duration::from_millis(trace_args.trace_ms);
181 let bpf_publisher = BpfEventActionPublisher::new(action_tx.clone());
182
183 let mut event_rbb = RingBufferBuilder::new();
184 let mut edm = EventDispatchManager::new(None, None);
185 let warmup_done = Arc::new(AtomicBool::new(false));
186 edm.register_bpf_handler(Box::new(bpf_publisher));
187 let event_handler = move |data: &[u8]| {
188 let mut event = bpf_event::default();
189 plain::copy_from_bytes(&mut event, data).expect("Event data buffer was too short");
190 let _ = edm.on_event(&event);
191 0
192 };
193
194 event_rbb.add(&skel.maps.events, event_handler)?;
195 let event_rb = event_rbb.build()?;
196 let shutdown = Arc::new(AtomicBool::new(false));
197 let stop_poll = shutdown.clone();
198 let stop_main = shutdown.clone();
199
200 tokio::spawn(async move {
201 loop {
202 let _ = event_rb.poll(Duration::from_millis(1));
203 if stop_poll.load(Ordering::Relaxed) {
204 debug!("polling stopped");
205 break;
206 }
207 }
208 });
209
210 let (complete_tx, mut complete_rx) = mpsc::channel(1);
211 let trace_file_prefix = config.trace_file_prefix().to_string();
212 let trace_file = trace_args.output_file.clone();
213 let mut trace_manager = PerfettoTraceManager::new(trace_file_prefix, None);
214 info!("warming up for {}ms", trace_args.warmup_ms);
215 tokio::time::sleep(Duration::from_millis(trace_args.warmup_ms)).await;
216 debug!("starting trace");
217 let thread_warmup = warmup_done.clone();
218 trace_manager.start()?;
219 thread_warmup.store(true, Ordering::Relaxed);
220 tokio::spawn(async move {
221 let mut count = 0;
222 loop {
223 let action = action_rx.recv().await;
224 if let Some(a) = action {
225 if thread_warmup.load(Ordering::Relaxed) {
226 count += 1;
227 trace_manager.on_action(&a).unwrap();
228 }
229 }
230 if stop_main.load(Ordering::Relaxed) {
231 trace_manager.stop(trace_file, None).unwrap();
232 info!("trace complete, collected {} events", count);
233 let _ = complete_tx.send(1).await;
234 break;
235 }
236 }
237 });
238
239 let mut tracer = Tracer::new(skel);
240 tracer.trace_async(trace_dur).await?;
241
242 drop(links);
247 shutdown.store(true, Ordering::Relaxed);
248 let _ = complete_rx.recv().await;
249
250 let stats = tracer.stats()?;
251 info!("{:?}", stats);
252
253 Ok(())
254 })
255}
256
257fn run_tui(tui_args: &TuiArgs) -> Result<()> {
258 if let Ok(log_path) = std::env::var("RUST_LOG_PATH") {
259 let log_level = match std::env::var("RUST_LOG") {
260 Ok(v) => LevelFilter::from_str(&v)?,
261 Err(_) => LevelFilter::Info,
262 };
263
264 WriteLogger::init(
265 log_level,
266 simplelog::Config::default(),
267 File::create(log_path)?,
268 )?;
269
270 log_panics::Config::new()
271 .backtrace_mode(log_panics::BacktraceMode::Resolved)
272 .install_panic_hook();
273 };
274
275 let config = Config::merge([
276 Config::from(tui_args.clone()),
277 Config::load().unwrap_or(Config::default_config()),
278 ]);
279 let keymap = config.active_keymap.clone();
280
281 tokio::runtime::Builder::new_multi_thread()
282 .enable_all()
283 .worker_threads(config.worker_threads() as usize)
284 .build()
285 .unwrap()
286 .block_on(async {
287 let (action_tx, mut action_rx) = mpsc::unbounded_channel();
288
289 let mut open_object = MaybeUninit::uninit();
290 let mut builder = BpfSkelBuilder::default();
291 if config.debug() {
292 builder.obj_builder.debug(true);
293 }
294 let bpf_publisher = BpfEventActionPublisher::new(action_tx.clone());
295 let mut edm = EventDispatchManager::new(None, None);
296 edm.register_bpf_handler(Box::new(bpf_publisher));
297
298 let skel = builder.open(&mut open_object)?;
299 skel.maps.rodata_data.long_tail_tracing_min_latency_ns =
300 tui_args.experimental_long_tail_tracing_min_latency_ns;
301
302 compat::cond_kprobe_enable("gpu_memory_total", &skel.progs.on_gpu_memory_total)?;
303 compat::cond_kprobe_enable("hw_pressure_update", &skel.progs.on_hw_pressure_update)?;
304 let mut skel = skel.load()?;
305 let mut links = attach_progs(&mut skel)?;
306 skel.progs.scxtop_init.test_run(ProgramInput::default())?;
307
308 if tui_args.experimental_long_tail_tracing {
309 skel.maps.data_data.trace_duration_ns = config.trace_duration_ns();
310 skel.maps.data_data.trace_warmup_ns = config.trace_warmup_ns();
311
312 let binary = tui_args
313 .experimental_long_tail_tracing_binary
314 .clone()
315 .unwrap();
316 let symbol = tui_args
317 .experimental_long_tail_tracing_symbol
318 .clone()
319 .unwrap();
320
321 links.extend([
322 skel.progs.long_tail_tracker_exit.attach_uprobe_with_opts(
323 -1, binary.clone(),
325 0,
326 UprobeOpts {
327 retprobe: true,
328 func_name: symbol.clone(),
329 ..Default::default()
330 },
331 )?,
332 skel.progs.long_tail_tracker_entry.attach_uprobe_with_opts(
333 -1, binary.clone(),
335 0,
336 UprobeOpts {
337 retprobe: false,
338 func_name: symbol.clone(),
339 ..Default::default()
340 },
341 )?,
342 ]);
343 };
344
345 let mut tui = Tui::new(keymap.clone(), config.tick_rate_ms())?;
346 let mut event_rbb = RingBufferBuilder::new();
347 let event_handler = move |data: &[u8]| {
348 let mut event = bpf_event::default();
349 plain::copy_from_bytes(&mut event, data).expect("Event data buffer was too short");
350 let _ = edm.on_event(&event);
351 0
352 };
353 event_rbb.add(&skel.maps.events, event_handler)?;
354 let event_rb = event_rbb.build()?;
355 let scheduler = read_file_string(SCHED_NAME_PATH).unwrap_or("".to_string());
356
357 let mut app = App::new(
358 config,
359 scheduler,
360 100,
361 tui_args.process_id,
362 action_tx.clone(),
363 skel,
364 )?;
365
366 tui.enter()?;
367
368 let shutdown = app.should_quit.clone();
369 tokio::spawn(async move {
370 loop {
371 let _ = event_rb.poll(Duration::from_millis(1));
372 if shutdown.load(Ordering::Relaxed) {
373 break;
374 }
375 }
376 });
377
378 if tui_args.mangoapp_tracing {
379 let stop_mangoapp = app.should_quit.clone();
380 let mangoapp_path = CString::new(tui_args.mangoapp_path.clone()).unwrap();
381 let poll_intvl_ms = tui_args.mangoapp_poll_intvl_ms;
382 let tx = action_tx.clone();
383 tokio::spawn(async move {
384 poll_mangoapp(
385 mangoapp_path,
386 poll_intvl_ms,
387 tx,
388 stop_mangoapp,
389 )
390 .await
391 });
392 }
393
394
395 loop {
396 tokio::select! {
397 ev = tui.next() => {
398 let ev = ev?;
399 match ev {
400 Event::Quit => { action_tx.send(Action::Quit)?; },
401 Event::Tick => action_tx.send(Action::Tick)?,
402 Event::TickRateChange(tick_rate_ms) => action_tx.send(
403 Action::TickRateChange(std::time::Duration::from_millis(tick_rate_ms)),
404 )?,
405 Event::Render => {
406 if app.should_quit.load(Ordering::Relaxed) {
407 break;
408 }
409 tui.draw(|f| app.render(f).expect("Failed to render application"))?;
410 }
411 Event::Key(_) => {
412 let action = get_action(&app, &keymap, ev);
413 action_tx.send(action)?;
414 }
415 _ => {}
416 }}
417
418 ac = action_rx.recv() => {
419 let ac = ac.ok_or(anyhow!("actions channel closed"))?;
420 app.handle_action(&ac)?;
421 }
422 }
423 }
424 tui.exit()?;
425 drop(links);
426
427 Ok(())
428 })
429}
430
431fn main() -> Result<()> {
432 let args = Cli::parse();
433
434 match &args.command.unwrap_or(Commands::Tui(args.tui)) {
435 Commands::Tui(tui_args) => {
436 run_tui(tui_args)?;
437 }
438 Commands::Trace(trace_args) => {
439 run_trace(trace_args)?;
440 }
441 Commands::GenerateCompletions { shell, output } => {
442 generate_completions(Cli::command(), *shell, output.clone())
443 .unwrap_or_else(|_| panic!("Failed to generate completions for {}", shell));
444 }
445 }
446 Ok(())
447}