1use scx_utils::compat;
7use scxtop::Action;
8use scxtop::App;
9use scxtop::CpuStatTracker;
10use scxtop::Event;
11use scxtop::Key;
12use scxtop::KeyMap;
13use scxtop::MemStatSnapshot;
14use scxtop::PerfettoTraceManager;
15use scxtop::SCHED_NAME_PATH;
16use scxtop::SystemStatAction;
17use scxtop::Tui;
18use scxtop::bpf_skel::types::bpf_event;
19use scxtop::cli::{Cli, Commands, TraceArgs, TuiArgs, generate_completions};
20use scxtop::config::Config;
21use scxtop::edm::{ActionHandler, BpfEventActionPublisher, BpfEventHandler, EventDispatchManager};
22use scxtop::layered_util;
23use scxtop::mangoapp::poll_mangoapp;
24use scxtop::search;
25use scxtop::tracer::Tracer;
26use scxtop::util::{
27 check_bpf_capability, get_capability_warning_message, get_clock_value, is_root,
28 read_file_string,
29};
30use scxtop::{AppState, bpf_skel::*};
31use scxtop::{UpdateColVisibilityAction, available_kprobe_events};
32
33use anyhow::Result;
34use anyhow::anyhow;
35use anyhow::bail;
36use clap::{CommandFactory, Parser};
37use futures::future::join_all;
38use libbpf_rs::Link;
39use libbpf_rs::MapCore;
40use libbpf_rs::ProgramInput;
41use libbpf_rs::UprobeOpts;
42use libbpf_rs::libbpf_sys;
43use libbpf_rs::num_possible_cpus;
44use libbpf_rs::skel::OpenSkel;
45use libbpf_rs::skel::SkelBuilder;
46use log::debug;
47use log::info;
48use ratatui::crossterm::event::{KeyCode::Char, KeyEvent};
49use simplelog::{
50 ColorChoice, Config as SimplelogConfig, LevelFilter, TermLogger, TerminalMode, WriteLogger,
51};
52use std::ffi::CString;
53use std::fs::File;
54use std::mem::MaybeUninit;
55use std::os::fd::AsFd;
56use std::os::fd::AsRawFd;
57use std::str::FromStr;
58use std::sync::Arc;
59use std::sync::atomic::AtomicBool;
60use std::sync::atomic::Ordering;
61use std::time::Duration;
62use sysinfo::System;
63use tokio::sync::mpsc;
64
65struct SendRingBuffer(*mut libbpf_sys::ring_buffer);
68unsafe impl Send for SendRingBuffer {}
69
70impl SendRingBuffer {
71 fn poll(&self, timeout: i32) -> i32 {
72 unsafe { libbpf_sys::ring_buffer__poll(self.0, timeout) }
73 }
74
75 fn consume(&self) -> i32 {
76 unsafe { libbpf_sys::ring_buffer__consume(self.0) }
77 }
78
79 fn free(self) {
80 unsafe { libbpf_sys::ring_buffer__free(self.0) }
81 }
82}
83
84fn get_action(app: &App, keymap: &KeyMap, event: Event) -> Action {
85 match event {
86 Event::Error => Action::None,
87 Event::Tick => Action::Tick,
88 Event::TickRateChange(tick_rate_ms) => {
89 Action::TickRateChange(std::time::Duration::from_millis(tick_rate_ms))
90 }
91 Event::Key(key) => handle_key_event(app, keymap, key),
92 Event::Paste(paste) => handle_input_entry(app, paste).unwrap_or(Action::None),
93 _ => Action::None,
94 }
95}
96
97fn handle_key_event(app: &App, keymap: &KeyMap, key: KeyEvent) -> Action {
98 match key.code {
99 Char(c) => {
100 if let Some(action) = handle_input_entry(app, c.to_string()) {
102 action
103 } else {
104 match (app.state(), c) {
106 (AppState::BpfProgramDetail, 'p') => Action::ToggleBpfPerfSampling,
108 _ => keymap.action(&Key::Char(c)),
110 }
111 }
112 }
113 _ => keymap.action(&Key::Code(key.code)),
114 }
115}
116
117fn handle_input_entry(app: &App, s: String) -> Option<Action> {
118 match app.state() {
119 AppState::PerfEvent | AppState::KprobeEvent => Some(Action::InputEntry(s)),
120 AppState::Default
121 | AppState::Llc
122 | AppState::Node
123 | AppState::Process
124 | AppState::Memory
125 | AppState::PerfTop
126 | AppState::BpfPrograms
127 | AppState::Scheduler
128 if app.filtering() =>
129 {
130 Some(Action::InputEntry(s))
131 }
132 _ => None,
133 }
134}
135
136fn attach_progs(skel: &mut BpfSkel) -> Result<(Vec<Link>, Vec<String>)> {
138 attach_progs_selective(skel, &[])
139}
140
141fn attach_progs_selective(
144 skel: &mut BpfSkel,
145 program_names: &[&str],
146) -> Result<(Vec<Link>, Vec<String>)> {
147 let mut links = Vec::new();
148 let mut warnings = Vec::new();
149
150 let has_bpf_cap = check_bpf_capability();
152
153 if !has_bpf_cap {
154 warnings
155 .push("BPF programs cannot be attached - scheduler monitoring disabled".to_string());
156 warnings.push("Try running as root or configure BPF permissions".to_string());
157 return Ok((links, warnings));
158 }
159
160 let attach_all = program_names.is_empty();
161
162 let should_attach = |name: &str| -> bool { attach_all || program_names.contains(&name) };
164
165 macro_rules! safe_attach {
167 ($prog:expr, $name:literal) => {
168 if should_attach($name) {
169 match $prog.attach() {
170 Ok(link) => {
171 links.push(link);
172 }
173 Err(e) => {
174 if is_root() {
175 return Err(anyhow!(
177 "Failed to attach {} (running as root): {}",
178 $name,
179 e
180 ));
181 } else {
182 warnings.push(format!("Failed to attach {}: {}", $name, e));
183 }
184 }
185 }
186 }
187 };
188 }
189
190 safe_attach!(skel.progs.on_sched_cpu_perf, "sched_cpu_perf");
192 safe_attach!(skel.progs.scx_sched_reg, "scx_sched_reg");
193 safe_attach!(skel.progs.scx_sched_unreg, "scx_sched_unreg");
194 safe_attach!(skel.progs.on_sched_switch, "on_sched_switch");
195 safe_attach!(skel.progs.on_sched_wakeup, "on_sched_wakeup");
196 safe_attach!(skel.progs.on_sched_wakeup_new, "sched_wakeup_new");
197 safe_attach!(skel.progs.on_sched_waking, "on_sched_waking");
198 safe_attach!(skel.progs.on_sched_migrate_task, "on_sched_migrate_task");
199 safe_attach!(skel.progs.on_sched_fork, "sched_fork");
200 safe_attach!(skel.progs.on_sched_exec, "sched_exec");
201 safe_attach!(skel.progs.on_sched_exit, "sched_exit");
202
203 if compat::ksym_exists("scx_bpf_dsq_insert_vtime")? {
205 safe_attach!(skel.progs.scx_insert_vtime, "scx_insert_vtime");
206 safe_attach!(skel.progs.scx_insert, "scx_insert");
207 safe_attach!(skel.progs.scx_dsq_move, "scx_dsq_move");
208 safe_attach!(skel.progs.scx_dsq_move_vtime, "scx_dsq_move_vtime");
209 safe_attach!(skel.progs.scx_dsq_move_set_vtime, "scx_dsq_move_set_vtime");
210 safe_attach!(skel.progs.scx_dsq_move_set_slice, "scx_dsq_move_set_slice");
211 if compat::ksym_exists("scx_bpf_dsq_insert___v2")? {
213 safe_attach!(skel.progs.scx_insert_v2, "scx_insert_v2");
214 }
215 if compat::ksym_exists("__scx_bpf_dsq_insert_vtime")? {
216 safe_attach!(skel.progs.scx_insert_vtime_args, "scx_insert_vtime_args");
217 }
218 } else {
219 safe_attach!(skel.progs.scx_dispatch, "scx_dispatch");
220 safe_attach!(skel.progs.scx_dispatch_vtime, "scx_dispatch_vtime");
221 safe_attach!(
222 skel.progs.scx_dispatch_from_dsq_set_vtime,
223 "scx_dispatch_from_dsq_set_vtime"
224 );
225 safe_attach!(
226 skel.progs.scx_dispatch_from_dsq_set_slice,
227 "scx_dispatch_from_dsq_set_slice"
228 );
229 safe_attach!(skel.progs.scx_dispatch_from_dsq, "scx_dispatch_from_dsq");
230 safe_attach!(
231 skel.progs.scx_dispatch_vtime_from_dsq,
232 "scx_dispatch_vtime_from_dsq"
233 );
234 }
235
236 safe_attach!(skel.progs.on_cpuhp_enter, "cpuhp_enter");
238 safe_attach!(skel.progs.on_cpuhp_exit, "cpuhp_exit");
239 safe_attach!(skel.progs.on_softirq_entry, "on_softirq_entry");
240 safe_attach!(skel.progs.on_softirq_exit, "on_softirq_exit");
241
242 if links.is_empty() && !is_root() {
244 warnings.extend(get_capability_warning_message());
245 }
246
247 Ok((links, warnings))
248}
249
250fn run_trace(trace_args: &TraceArgs) -> Result<()> {
251 if !is_root() {
253 return Err(anyhow!(
254 "Trace functionality requires root privileges. Please run as root"
255 ));
256 }
257
258 TermLogger::init(
259 match trace_args.verbose {
260 0 => simplelog::LevelFilter::Info,
261 1 => simplelog::LevelFilter::Debug,
262 _ => simplelog::LevelFilter::Trace,
263 },
264 SimplelogConfig::default(),
265 TerminalMode::Mixed,
266 ColorChoice::Auto,
267 )?;
268
269 let mut kprobe_events = available_kprobe_events()?;
270 kprobe_events.sort();
271 search::sorted_contains_all(&kprobe_events, &trace_args.kprobes)
272 .then_some(())
273 .ok_or_else(|| anyhow!("Invalid kprobe events"))?;
274
275 let config = Config::default_config();
276 let worker_threads = config.worker_threads() as usize;
277
278 let num_cpus = num_possible_cpus()?;
280 let rb_cnt = scxtop::topology::calculate_default_ringbuf_count(num_cpus);
281
282 let required_threads = std::cmp::max(rb_cnt + 4, worker_threads);
285
286 info!(
287 "Creating tokio runtime with {} worker threads for {} ringbuffers",
288 required_threads, rb_cnt
289 );
290
291 tokio::runtime::Builder::new_multi_thread()
292 .enable_all()
293 .worker_threads(required_threads)
294 .build()
295 .unwrap()
296 .block_on(async {
297 let (action_tx, mut action_rx) = mpsc::unbounded_channel();
298
299 let mut open_object = MaybeUninit::uninit();
301 let mut builder = BpfSkelBuilder::default();
302 if trace_args.verbose > 2 {
303 builder.obj_builder.debug(true);
304 }
305
306 let mut skel = builder.open(&mut open_object)?;
307 compat::cond_kprobe_enable("gpu_memory_total", &skel.progs.on_gpu_memory_total)?;
308 compat::cond_kprobe_enable("hw_pressure_update", &skel.progs.on_hw_pressure_update)?;
309 compat::cond_tracepoint_enable("sched:sched_process_wait", &skel.progs.on_sched_wait)?;
310 compat::cond_tracepoint_enable("sched:sched_process_hang", &skel.progs.on_sched_hang)?;
311
312 let num_cpus = num_possible_cpus()?;
314 let rb_cnt = scxtop::topology::calculate_default_ringbuf_count(num_cpus);
315 let rb_cpu_mapping = scxtop::topology::setup_cpu_to_ringbuf_mapping(rb_cnt, num_cpus)?;
316
317 log::info!("Using {} ringbuffers for {} CPUs", rb_cnt, num_cpus);
318
319 let cpu_cnt_pow2 = num_cpus.next_power_of_two();
321 skel.maps.rodata_data.as_mut().unwrap().rb_cpu_map_mask = (cpu_cnt_pow2 - 1) as u64;
322
323 skel.maps
325 .data_rb_cpu_map
326 .set_max_entries(cpu_cnt_pow2 as u32)?;
327
328 skel.maps.events.set_max_entries(rb_cnt as u32)?;
330
331 let mut skel = skel.load()?;
333
334 for (cpu_id, &rb_id) in rb_cpu_mapping.iter().enumerate() {
336 if cpu_id < cpu_cnt_pow2 {
337 skel.maps.data_rb_cpu_map.update(
338 &(cpu_id as u32).to_ne_bytes(),
339 &rb_id.to_ne_bytes(),
340 libbpf_rs::MapFlags::ANY,
341 )?;
342 }
343 }
344
345 skel.maps.data_data.as_mut().unwrap().enable_bpf_events = false;
346
347 let mut links = vec![
349 skel.progs.on_sched_cpu_perf.attach()?,
350 skel.progs.scx_sched_reg.attach()?,
351 skel.progs.scx_sched_unreg.attach()?,
352 skel.progs.on_sched_switch.attach()?,
353 skel.progs.on_sched_wakeup.attach()?,
354 skel.progs.on_sched_wakeup_new.attach()?,
355 skel.progs.on_sched_waking.attach()?,
356 skel.progs.on_sched_migrate_task.attach()?,
357 skel.progs.on_sched_fork.attach()?,
358 skel.progs.on_sched_exec.attach()?,
359 skel.progs.on_sched_exit.attach()?,
360 ];
361
362 if compat::ksym_exists("scx_bpf_dsq_insert_vtime")? {
364 if let Ok(link) = skel.progs.scx_insert_vtime.attach() {
365 links.push(link);
366 }
367 if let Ok(link) = skel.progs.scx_insert.attach() {
368 links.push(link);
369 }
370 if let Ok(link) = skel.progs.scx_dsq_move.attach() {
371 links.push(link);
372 }
373 if let Ok(link) = skel.progs.scx_dsq_move_vtime.attach() {
374 links.push(link);
375 }
376 if let Ok(link) = skel.progs.scx_dsq_move_set_vtime.attach() {
377 links.push(link);
378 }
379 if let Ok(link) = skel.progs.scx_dsq_move_set_slice.attach() {
380 links.push(link);
381 }
382 if compat::ksym_exists("scx_bpf_dsq_insert___v2")?
384 && let Ok(link) = skel.progs.scx_insert_v2.attach()
385 {
386 links.push(link);
387 }
388 if compat::ksym_exists("__scx_bpf_dsq_insert_vtime")?
389 && let Ok(link) = skel.progs.scx_insert_vtime_args.attach()
390 {
391 links.push(link);
392 }
393 } else {
394 if let Ok(link) = skel.progs.scx_dispatch.attach() {
395 links.push(link);
396 }
397 if let Ok(link) = skel.progs.scx_dispatch_vtime.attach() {
398 links.push(link);
399 }
400 if let Ok(link) = skel.progs.scx_dispatch_from_dsq_set_vtime.attach() {
401 links.push(link);
402 }
403 if let Ok(link) = skel.progs.scx_dispatch_from_dsq_set_slice.attach() {
404 links.push(link);
405 }
406 if let Ok(link) = skel.progs.scx_dispatch_from_dsq.attach() {
407 links.push(link);
408 }
409 if let Ok(link) = skel.progs.scx_dispatch_vtime_from_dsq.attach() {
410 links.push(link);
411 }
412 }
413 if let Ok(link) = skel.progs.on_cpuhp_enter.attach() {
414 links.push(link);
415 }
416 if let Ok(link) = skel.progs.on_cpuhp_exit.attach() {
417 links.push(link);
418 }
419 if let Ok(link) = skel.progs.on_softirq_entry.attach() {
420 links.push(link);
421 }
422 if let Ok(link) = skel.progs.on_softirq_exit.attach() {
423 links.push(link);
424 }
425
426 let dropped_invalid_ts = Arc::new(std::sync::atomic::AtomicU64::new(0));
428
429 let shutdown = Arc::new(AtomicBool::new(false));
431
432 let events_map_fd = skel.maps.events.as_fd().as_raw_fd();
434 let mut rb_fds = Vec::new();
435 let mut rb_managers: Vec<SendRingBuffer> = Vec::new();
436
437 for rb_id in 0..rb_cnt {
438 let rb_fd = unsafe {
440 libbpf_sys::bpf_map_create(
441 libbpf_sys::BPF_MAP_TYPE_RINGBUF,
442 std::ptr::null(),
443 0,
444 0,
445 (32 * 1024 * 1024) as u32, std::ptr::null(),
447 )
448 };
449
450 if rb_fd < 0 {
451 bail!(
452 "Failed to create ringbuffer #{}: {}",
453 rb_id,
454 std::io::Error::last_os_error()
455 );
456 }
457
458 let rb_id_u32 = rb_id as u32;
460 let ret = unsafe {
461 libbpf_sys::bpf_map_update_elem(
462 events_map_fd,
463 &rb_id_u32 as *const u32 as *const std::ffi::c_void,
464 &rb_fd as *const i32 as *const std::ffi::c_void,
465 libbpf_sys::BPF_NOEXIST.into(),
466 )
467 };
468
469 if ret < 0 {
470 bail!(
471 "Failed to add ringbuffer #{} to hash-of-maps: {}",
472 rb_id,
473 std::io::Error::last_os_error()
474 );
475 }
476
477 rb_fds.push(rb_fd);
478 }
479
480 struct RingBufContext {
483 dropped_invalid_ts: Arc<std::sync::atomic::AtomicU64>,
484 action_tx: mpsc::UnboundedSender<Action>,
485 shutdown: Arc<AtomicBool>,
486 }
487
488 extern "C" fn ring_buffer_sample_callback(
489 ctx: *mut std::ffi::c_void,
490 data: *mut std::ffi::c_void,
491 size: u64,
492 ) -> std::ffi::c_int {
493 unsafe {
494 let ctx = &*(ctx as *const RingBufContext);
495
496 if ctx.shutdown.load(std::sync::atomic::Ordering::Relaxed) {
498 return 0;
499 }
500
501 let mut event = bpf_event::default();
502 let copy_size = std::cmp::min(size as usize, std::mem::size_of::<bpf_event>());
503 std::ptr::copy_nonoverlapping(
504 data as *const u8,
505 &mut event as *mut bpf_event as *mut u8,
506 copy_size,
507 );
508
509 if event.ts == 0 {
511 ctx.dropped_invalid_ts
512 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
513 return 0;
514 }
515
516 let mut edm = EventDispatchManager::new(None, None);
517 edm.register_bpf_handler(Box::new(BpfEventActionPublisher::new(
518 ctx.action_tx.clone(),
519 )));
520 let _ = edm.on_event(&event);
521 }
522 0
523 }
524
525 for rb_fd in &rb_fds {
526 let ctx = Box::new(RingBufContext {
527 dropped_invalid_ts: dropped_invalid_ts.clone(),
528 action_tx: action_tx.clone(),
529 shutdown: shutdown.clone(),
530 });
531 let ctx_ptr = Box::into_raw(ctx) as *mut std::ffi::c_void;
532
533 let rb_ptr = unsafe {
534 libbpf_sys::ring_buffer__new(
535 *rb_fd,
536 Some(ring_buffer_sample_callback),
537 ctx_ptr,
538 std::ptr::null(),
539 )
540 };
541
542 if rb_ptr.is_null() {
543 unsafe {
544 let _ = Box::from_raw(ctx_ptr as *mut RingBufContext);
545 }
546 bail!("Failed to create ring buffer manager");
547 }
548
549 rb_managers.push(SendRingBuffer(rb_ptr));
550 }
551
552 let stop_poll = shutdown.clone();
554 let stop_stats = shutdown.clone();
555
556 let mut ringbuffer_handles = Vec::new();
557 let mut producer_handles = Vec::new();
558
559 for (rb_id, rb) in rb_managers.into_iter().enumerate() {
562 let stop_poll_clone = stop_poll.clone();
563 ringbuffer_handles.push(tokio::task::spawn_blocking(move || {
564 info!("ringbuffer #{} task started", rb_id);
565 let mut poll_count = 0;
566 loop {
567 rb.poll(1);
569 poll_count += 1;
570 if stop_poll_clone.load(Ordering::Relaxed) {
571 info!(
572 "ringbuffer #{} received shutdown after {} polls",
573 rb_id, poll_count
574 );
575 let consumed = rb.consume();
577 info!("ringbuffer #{} consumed {} events", rb_id, consumed);
578 rb.free();
580 info!("ringbuffer #{} freed", rb_id);
581 break;
582 }
583 }
584 info!("ringbuffer #{} exiting", rb_id);
585 }));
586 }
587 info!(
588 "spawned {} ringbuffer polling tasks",
589 ringbuffer_handles.len()
590 );
591
592 if trace_args.system_stats {
593 let mut cpu_stat_tracker = CpuStatTracker::default();
594 let mut mem_stats = MemStatSnapshot::default();
595 let mut system = System::new_all();
596 let action_tx_clone = action_tx.clone();
597
598 producer_handles.push(tokio::spawn(async move {
599 info!("stats task started");
600 let mut stats_count = 0;
601 loop {
602 if stop_stats.load(Ordering::Relaxed) {
603 info!("stats task received shutdown after {} samples", stats_count);
604 break;
605 }
606 let ts = get_clock_value(libc::CLOCK_BOOTTIME);
607
608 cpu_stat_tracker
609 .update(&mut system)
610 .expect("Failed to update cpu stats");
611
612 mem_stats.update().expect("Failed to update mem stats");
613
614 let sys_stat_action = Action::SystemStat(SystemStatAction {
615 ts,
616 cpu_data_prev: cpu_stat_tracker.prev.clone(),
617 cpu_data_current: cpu_stat_tracker.current.clone(),
618 mem_info: mem_stats.clone(),
619 });
620 action_tx_clone
621 .send(sys_stat_action)
622 .expect("Failed to send CpuStat action");
623
624 stats_count += 1;
625 tokio::time::sleep(Duration::from_millis(100)).await;
626 }
627 info!("stats task exiting");
628 }));
629 }
630
631 let trace_file_prefix = config.trace_file_prefix().to_string();
632 let trace_file = trace_args.output_file.clone();
633 let mut trace_manager = PerfettoTraceManager::new(trace_file_prefix, None);
634
635 {
637 use scx_utils::Topology;
638 if let Ok(topo) = Topology::new() {
639 let mut cpu_to_llc = std::collections::HashMap::new();
640 let mut cpu_to_numa = std::collections::HashMap::new();
641 let mut cpu_to_core = std::collections::HashMap::new();
642 for cpu in topo.all_cpus.values() {
643 cpu_to_llc.insert(cpu.id as u32, cpu.llc_id as u32);
644 cpu_to_numa.insert(cpu.id as u32, cpu.node_id as u32);
645 cpu_to_core.insert(cpu.id as u32, cpu.core_id as u32);
646 }
647 trace_manager.set_topology(cpu_to_llc, cpu_to_numa, cpu_to_core);
648 }
649 }
650
651 info!("starting trace for {}ms", trace_args.trace_ms);
652 trace_manager.start()?;
653 let mut tracer = Tracer::new(skel);
654 tracer.trace(&trace_args.kprobes)?;
655
656 let shutdown_trace = shutdown.clone();
657 let trace_handle = tokio::spawn(async move {
658 debug!("trace generation task started");
659 let mut count = 0;
660 let mut last_log = std::time::Instant::now();
661 loop {
662 tokio::select! {
663 _ = tokio::time::sleep(Duration::from_millis(100)) => {
665 if shutdown_trace.load(Ordering::Relaxed) {
666 break;
667 }
668 }
669 action = action_rx.recv() => {
670 if let Some(a) = action {
671 count += 1;
672 if last_log.elapsed() > std::time::Duration::from_secs(1) {
673 debug!("trace task: {} events processed", count);
674 last_log = std::time::Instant::now();
675 }
676 trace_manager
677 .on_action(&a)
678 .expect("Action should have been resolved");
679 if shutdown_trace.load(Ordering::Relaxed) {
683 break;
684 }
685 } else {
686 break;
687 }
688 }
689 }
690 }
691 info!("trace task: draining remaining events");
694 while let Ok(a) = action_rx.try_recv() {
695 count += 1;
696 trace_manager
697 .on_action(&a)
698 .expect("Action should have been resolved");
699 }
700 info!("trace task: stopping trace manager");
701 trace_manager.stop(trace_file, None).unwrap();
702 info!("trace file compiled, collected {count} events");
703 });
704
705 info!("waiting for trace duration ({}ms)", trace_args.trace_ms);
706 tokio::time::sleep(Duration::from_millis(trace_args.trace_ms)).await;
707 info!("trace duration complete, beginning shutdown");
708
709 info!("shutdown: clearing BPF links");
717 tracer.clear_links()?;
718 info!("shutdown: BPF links cleared");
719 drop(links);
720 info!("shutdown: links dropped");
721
722 info!("shutdown: setting shutdown flag");
723 shutdown.store(true, Ordering::Relaxed);
724 info!(
725 "shutdown: flag set, waiting for {} ringbuffer tasks",
726 ringbuffer_handles.len()
727 );
728
729 let results = join_all(ringbuffer_handles).await;
731 info!("shutdown: all {} ringbuffer tasks joined", results.len());
732 for (idx, result) in results.iter().enumerate() {
733 if let Err(e) = result {
734 eprintln!("Ringbuffer task {} panicked: {e}", idx);
735 } else {
736 debug!("ringbuffer task {} exited successfully", idx);
737 }
738 }
739 info!("shutdown: ringbuffer tasks complete");
740
741 info!(
743 "shutdown: waiting for {} producer tasks",
744 producer_handles.len()
745 );
746 let results = join_all(producer_handles).await;
747 info!("shutdown: all {} producer tasks joined", results.len());
748 for (idx, result) in results.iter().enumerate() {
749 if let Err(e) = result {
750 eprintln!("Producer task {} panicked: {e}", idx);
751 } else {
752 debug!("producer task {} exited successfully", idx);
753 }
754 }
755 info!("shutdown: producer tasks complete");
756
757 info!("shutdown: dropping action_tx");
759 drop(action_tx);
760 info!("shutdown: action_tx dropped, waiting for trace generation");
761
762 if let Err(e) = trace_handle.await {
764 eprintln!("Trace generation task panicked: {e}");
765 }
766 info!("shutdown: trace generation complete");
767
768 info!("shutdown: collecting final stats");
769 let stats = tracer.stats()?;
770 info!("shutdown: {stats:?}");
771
772 info!("shutdown: complete");
773 Ok(())
774 })
775}
776
777fn run_tui(tui_args: &TuiArgs) -> Result<()> {
778 if let Ok(log_path) = std::env::var("RUST_LOG_PATH") {
779 let log_level = match std::env::var("RUST_LOG") {
780 Ok(v) => LevelFilter::from_str(&v)?,
781 Err(_) => LevelFilter::Info,
782 };
783
784 WriteLogger::init(
785 log_level,
786 simplelog::Config::default(),
787 File::create(log_path)?,
788 )?;
789
790 log_panics::Config::new()
791 .backtrace_mode(log_panics::BacktraceMode::Resolved)
792 .install_panic_hook();
793 };
794
795 let config = Config::merge([
796 Config::from(tui_args.clone()),
797 Config::load_or_default().expect("Failed to load config or load default config"),
798 ]);
799 let keymap = config.active_keymap.clone();
800
801 let worker_threads = config.worker_threads() as usize;
803 let num_cpus = num_possible_cpus()?;
804 let rb_cnt = scxtop::topology::calculate_default_ringbuf_count(num_cpus);
805
806 let required_threads = std::cmp::max(rb_cnt + 4, worker_threads);
809
810 tokio::runtime::Builder::new_multi_thread()
811 .enable_all()
812 .worker_threads(required_threads)
813 .build()
814 .unwrap()
815 .block_on(async {
816 let mut open_object = MaybeUninit::uninit();
818
819 let (action_tx, mut action_rx) = mpsc::unbounded_channel();
820
821 let has_bpf_cap = check_bpf_capability();
823 let mut capability_warnings = Vec::new();
824 let mut _bpf_enabled = false;
825 let mut links = Vec::new();
826 let mut event_rb_data_opt: Option<(
827 Vec<i32>, Arc<std::sync::atomic::AtomicU64>, mpsc::UnboundedSender<Action>, )> = None;
831 let mut skel_opt = None;
832
833 if has_bpf_cap {
834 let mut builder = BpfSkelBuilder::default();
836 if config.debug() {
837 builder.obj_builder.debug(true);
838 }
839 let bpf_publisher = BpfEventActionPublisher::new(action_tx.clone());
840 let mut edm = EventDispatchManager::new(None, None);
841 edm.register_bpf_handler(Box::new(bpf_publisher));
842
843 match builder.open(&mut open_object) {
845 Ok(mut skel) => {
846 skel.maps.rodata_data.as_mut().unwrap().long_tail_tracing_min_latency_ns =
847 tui_args.experimental_long_tail_tracing_min_latency_ns;
848
849 let _map_handle = if tui_args.layered {
850 skel.maps.rodata_data.as_mut().unwrap().layered = true;
851 action_tx.send(Action::UpdateColVisibility(UpdateColVisibilityAction {
852 table: "Process".to_string(),
853 col: "Layer ID".to_string(),
854 visible: true,
855 }))?;
856 action_tx.send(Action::UpdateColVisibility(UpdateColVisibilityAction {
857 table: "Thread".to_string(),
858 col: "Layer ID".to_string(),
859 visible: true,
860 }))?;
861 match layered_util::attach_to_existing_map("task_ctxs", &mut skel.maps.task_ctxs) {
862 Ok(handle) => Some(handle),
863 Err(e) => {
864 capability_warnings.push(format!("Failed to attach to layered map: {e}"));
865 None
866 }
867 }
868 } else {
869 None
870 };
871
872 let num_cpus = num_possible_cpus()?;
874 let rb_cnt = scxtop::topology::calculate_default_ringbuf_count(num_cpus);
875 let rb_cpu_mapping = scxtop::topology::setup_cpu_to_ringbuf_mapping(rb_cnt, num_cpus)?;
876
877 log::info!("Using {} ringbuffers for {} CPUs", rb_cnt, num_cpus);
878
879 let cpu_cnt_pow2 = num_cpus.next_power_of_two();
881 skel.maps.rodata_data.as_mut().unwrap().rb_cpu_map_mask = (cpu_cnt_pow2 - 1) as u64;
882
883 if let Err(e) = skel.maps.data_rb_cpu_map.set_max_entries(cpu_cnt_pow2 as u32) {
885 capability_warnings.push(format!("Failed to set CPU-to-ringbuf map size: {e}"));
886 }
887
888 if let Err(e) = skel.maps.events.set_max_entries(rb_cnt as u32) {
890 capability_warnings.push(format!("Failed to set ringbuf count: {e}"));
891 }
892
893 if let Err(e) = compat::cond_kprobe_enable("gpu_memory_total", &skel.progs.on_gpu_memory_total) {
894 capability_warnings.push(format!("Failed to enable gpu_memory_total kprobe: {e}"));
895 }
896 if let Err(e) = compat::cond_kprobe_enable("hw_pressure_update", &skel.progs.on_hw_pressure_update) {
897 capability_warnings.push(format!("Failed to enable hw_pressure_update kprobe: {e}"));
898 }
899 if let Err(e) = compat::cond_tracepoint_enable("sched:sched_process_wait", &skel.progs.on_sched_wait) {
900 capability_warnings.push(format!("Failed to enable sched_process_wait tracepoint: {e}"));
901 }
902 if let Err(e) = compat::cond_tracepoint_enable("sched:sched_process_hang", &skel.progs.on_sched_hang) {
903 capability_warnings.push(format!("Failed to enable sched_process_hang tracepoint: {e}"));
904 }
905
906 match skel.load() {
908 Ok(mut loaded_skel) => {
909 for (cpu_id, &rb_id) in rb_cpu_mapping.iter().enumerate() {
911 if cpu_id < cpu_cnt_pow2
912 && let Err(e) = loaded_skel.maps.data_rb_cpu_map.update(
913 &(cpu_id as u32).to_ne_bytes(),
914 &rb_id.to_ne_bytes(),
915 libbpf_rs::MapFlags::ANY,
916 ) {
917 capability_warnings.push(format!("Failed to set CPU {} -> ringbuf {}: {}", cpu_id, rb_id, e));
918 }
919 }
920
921 let (skel_links, attach_warnings) = attach_progs(&mut loaded_skel)?;
922 links = skel_links;
923 capability_warnings.extend(attach_warnings);
924
925 if !links.is_empty() || is_root() {
926 if let Err(e) = loaded_skel.progs.scxtop_init.test_run(ProgramInput::default()) {
928 capability_warnings.push(format!("Failed to initialize scxtop BPF program: {e}"));
929 }
930 }
931
932 if !links.is_empty() {
934 let dropped_invalid_ts = Arc::new(std::sync::atomic::AtomicU64::new(0));
936
937 let events_map_fd = loaded_skel.maps.events.as_fd().as_raw_fd();
939 let mut rb_fds = Vec::new();
940
941 for rb_id in 0..rb_cnt {
942 let rb_fd = unsafe {
944 libbpf_sys::bpf_map_create(
945 libbpf_sys::BPF_MAP_TYPE_RINGBUF,
946 std::ptr::null(),
947 0,
948 0,
949 (32 * 1024 * 1024) as u32, std::ptr::null(),
951 )
952 };
953
954 if rb_fd < 0 {
955 capability_warnings.push(format!("Failed to create ringbuffer #{}: {}", rb_id, std::io::Error::last_os_error()));
956 continue;
957 }
958
959 let rb_id_u32 = rb_id as u32;
961 let ret = unsafe {
962 libbpf_sys::bpf_map_update_elem(
963 events_map_fd,
964 &rb_id_u32 as *const u32 as *const std::ffi::c_void,
965 &rb_fd as *const i32 as *const std::ffi::c_void,
966 libbpf_sys::BPF_NOEXIST.into(),
967 )
968 };
969
970 if ret < 0 {
971 capability_warnings.push(format!("Failed to add ringbuffer #{} to hash-of-maps: {}", rb_id, std::io::Error::last_os_error()));
972 continue;
973 }
974
975 rb_fds.push(rb_fd);
976 }
977
978 if !rb_fds.is_empty() {
979 event_rb_data_opt = Some((rb_fds, dropped_invalid_ts, action_tx.clone()));
981 _bpf_enabled = true;
982 }
983 }
984
985 skel_opt = Some(loaded_skel);
986 }
987 Err(e) => {
988 if is_root() {
989 return Err(anyhow!("Failed to load BPF skeleton (running as root): {e}"));
990 } else {
991 capability_warnings.push(format!("Failed to load BPF skeleton: {e}"));
992 capability_warnings.extend(get_capability_warning_message());
993 }
994 }
995 }
996 }
997 Err(e) => {
998 if is_root() {
999 return Err(anyhow!("Failed to open BPF skeleton (running as root): {e}"));
1000 } else {
1001 capability_warnings.push(format!("Failed to open BPF skeleton: {e}"));
1002 capability_warnings.extend(get_capability_warning_message());
1003 }
1004 }
1005 }
1006 } else {
1007 capability_warnings.extend(get_capability_warning_message());
1009 }
1010
1011 if tui_args.experimental_long_tail_tracing {
1013 if let Some(ref mut skel) = skel_opt {
1014 skel.maps.data_data.as_mut().unwrap().trace_duration_ns = config.trace_duration_ns();
1015 skel.maps.data_data.as_mut().unwrap().trace_warmup_ns = config.trace_warmup_ns();
1016
1017 let binary = tui_args
1018 .experimental_long_tail_tracing_binary
1019 .clone()
1020 .unwrap();
1021 let symbol = tui_args
1022 .experimental_long_tail_tracing_symbol
1023 .clone()
1024 .unwrap();
1025
1026 match skel.progs.long_tail_tracker_exit.attach_uprobe_with_opts(
1027 -1, binary.clone(),
1029 0,
1030 UprobeOpts {
1031 retprobe: true,
1032 func_name: Some(symbol.clone()),
1033 ..Default::default()
1034 },
1035 ) {
1036 Ok(link) => links.push(link),
1037 Err(e) => capability_warnings.push(format!("Failed to attach long tail tracker exit: {e}"))
1038 }
1039
1040 match skel.progs.long_tail_tracker_entry.attach_uprobe_with_opts(
1041 -1, binary.clone(),
1043 0,
1044 UprobeOpts {
1045 retprobe: false,
1046 func_name: Some(symbol.clone()),
1047 ..Default::default()
1048 },
1049 ) {
1050 Ok(link) => links.push(link),
1051 Err(e) => capability_warnings.push(format!("Failed to attach long tail tracker entry: {e}"))
1052 }
1053 } else {
1054 capability_warnings.push("Long tail tracing requested but BPF skeleton not available".to_string());
1055 }
1056 }
1057
1058 let mut tui = Tui::new(keymap.clone(), config.tick_rate_ms(), config.frame_rate_ms())?;
1059 let scheduler = read_file_string(SCHED_NAME_PATH).unwrap_or("".to_string());
1060
1061 let mut app = if let Some(skel) = skel_opt {
1063 App::new(
1064 config,
1065 scheduler,
1066 100,
1067 tui_args.process_id,
1068 tui_args.layered,
1069 action_tx.clone(),
1070 skel,
1071 )?
1072 } else {
1073 App::new_without_bpf(
1075 config,
1076 scheduler,
1077 100,
1078 tui_args.process_id,
1079 tui_args.layered,
1080 action_tx.clone(),
1081 )?
1082 };
1083
1084 if !capability_warnings.is_empty() {
1086 app.set_capability_warnings(capability_warnings);
1087 }
1088
1089 tui.enter()?;
1090
1091 let shutdown = app.should_quit.clone();
1093 let mut ringbuffer_handles = Vec::new();
1094 if let Some((rb_fds, dropped_invalid_ts, rb_action_tx)) = event_rb_data_opt {
1095 struct RingBufContext {
1098 dropped_invalid_ts: Arc<std::sync::atomic::AtomicU64>,
1099 action_tx: mpsc::UnboundedSender<Action>,
1100 shutdown: Arc<AtomicBool>,
1101 }
1102
1103 extern "C" fn ring_buffer_sample_callback(
1104 ctx: *mut std::ffi::c_void,
1105 data: *mut std::ffi::c_void,
1106 size: u64,
1107 ) -> std::ffi::c_int {
1108 unsafe {
1109 let ctx = &*(ctx as *const RingBufContext);
1110
1111 if ctx.shutdown.load(std::sync::atomic::Ordering::Relaxed) {
1113 return 0;
1114 }
1115
1116 let mut event = bpf_event::default();
1117 let copy_size = std::cmp::min(size as usize, std::mem::size_of::<bpf_event>());
1118 std::ptr::copy_nonoverlapping(
1119 data as *const u8,
1120 &mut event as *mut bpf_event as *mut u8,
1121 copy_size,
1122 );
1123
1124 if event.ts == 0 {
1126 ctx.dropped_invalid_ts.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1127 return 0;
1128 }
1129
1130 let mut edm = EventDispatchManager::new(None, None);
1131 edm.register_bpf_handler(Box::new(BpfEventActionPublisher::new(ctx.action_tx.clone())));
1132 let _ = edm.on_event(&event);
1133 }
1134 0
1135 }
1136
1137 for rb_fd in rb_fds {
1139 let ctx = Box::new(RingBufContext {
1140 dropped_invalid_ts: dropped_invalid_ts.clone(),
1141 action_tx: rb_action_tx.clone(),
1142 shutdown: shutdown.clone(),
1143 });
1144 let ctx_ptr = Box::into_raw(ctx) as *mut std::ffi::c_void;
1145
1146 let rb_ptr = unsafe {
1147 libbpf_sys::ring_buffer__new(
1148 rb_fd,
1149 Some(ring_buffer_sample_callback),
1150 ctx_ptr,
1151 std::ptr::null(),
1152 )
1153 };
1154
1155 if rb_ptr.is_null() {
1156 unsafe { let _ = Box::from_raw(ctx_ptr as *mut RingBufContext); }
1157 log::warn!("Failed to create ring buffer manager");
1158 continue;
1159 }
1160
1161 let rb = SendRingBuffer(rb_ptr);
1162 let shutdown_clone = shutdown.clone();
1163 let rb_id = ringbuffer_handles.len();
1164 ringbuffer_handles.push(tokio::task::spawn_blocking(move || {
1166 loop {
1167 rb.poll(1);
1169 if shutdown_clone.load(Ordering::Relaxed) {
1170 rb.consume();
1172 rb.free();
1174 log::debug!("ringbuffer #{} polling stopped", rb_id);
1175 break;
1176 }
1177 }
1178 }));
1179 }
1180 }
1181
1182 if tui_args.mangoapp_tracing {
1183 let stop_mangoapp = app.should_quit.clone();
1184 let mangoapp_path = CString::new(tui_args.mangoapp_path.clone()).unwrap();
1185 let poll_intvl_ms = tui_args.mangoapp_poll_intvl_ms;
1186 let tx = action_tx.clone();
1187 tokio::spawn(async move {
1188 poll_mangoapp(
1189 mangoapp_path,
1190 poll_intvl_ms,
1191 tx,
1192 stop_mangoapp,
1193 )
1194 .await
1195 });
1196 }
1197
1198 loop {
1199 tokio::select! {
1200 ev = tui.next() => {
1201 let ev = ev?;
1202 match ev {
1203 Event::Quit => { action_tx.send(Action::Quit)?; },
1204 Event::Tick => action_tx.send(Action::Tick)?,
1205 Event::TickRateChange(tick_rate_ms) => action_tx.send(
1206 Action::TickRateChange(std::time::Duration::from_millis(tick_rate_ms)),
1207 )?,
1208 Event::Render => {
1209 if app.should_quit.load(Ordering::Relaxed) {
1210 break;
1211 }
1212 if app.state() != AppState::Pause {
1213 tui.draw(|f| app.render(f).expect("Failed to render application"))?;
1214 }
1215 }
1216 Event::Key(_) => {
1217 let action = get_action(&app, &keymap, ev);
1218 action_tx.send(action)?;
1219 }
1220 _ => {}
1221 }}
1222
1223 ac = action_rx.recv() => {
1224 let ac = ac.ok_or(anyhow!("actions channel closed"))?;
1225 app.handle_action(&ac)?;
1226 }
1227 }
1228 }
1229 tui.exit()?;
1230
1231 log::debug!("waiting for {} ringbuffer tasks to complete", ringbuffer_handles.len());
1233 for handle in ringbuffer_handles {
1234 if let Err(e) = handle.await {
1235 log::error!("Ringbuffer task panicked: {e}");
1236 }
1237 }
1238
1239 drop(links);
1240
1241 Ok(())
1242 })
1243}
1244
1245fn run_mcp(mcp_args: &scxtop::cli::McpArgs) -> Result<()> {
1246 use scx_utils::Topology;
1247 use scxtop::mcp::{McpServer, McpServerConfig, events::action_to_mcp_event};
1248 use std::sync::Arc;
1249
1250 TermLogger::init(
1252 match mcp_args.verbose {
1253 0 => LevelFilter::Warn,
1254 1 => LevelFilter::Info,
1255 2 => LevelFilter::Debug,
1256 _ => LevelFilter::Trace,
1257 },
1258 SimplelogConfig::default(),
1259 TerminalMode::Stderr,
1260 ColorChoice::Auto,
1261 )?;
1262
1263 let topo = Topology::new().expect("Failed to create topology");
1265 let topo_arc = Arc::new(topo);
1266
1267 let mcp_config = McpServerConfig {
1268 daemon_mode: mcp_args.daemon,
1269 enable_logging: mcp_args.enable_logging,
1270 };
1271
1272 if mcp_args.daemon {
1273 tokio::runtime::Builder::new_multi_thread()
1275 .enable_all()
1276 .worker_threads(4)
1277 .build()
1278 .unwrap()
1279 .block_on(async {
1280 let mut open_object = MaybeUninit::uninit();
1281 let (action_tx, mut action_rx) = mpsc::unbounded_channel();
1282
1283 use scxtop::mcp::create_shared_stats;
1285 let shared_stats = create_shared_stats();
1286 let shared_stats_for_event_handler = shared_stats.clone();
1287
1288 let builder = BpfSkelBuilder::default();
1290 let mut skel = builder.open(&mut open_object)?;
1291
1292 let num_cpus = num_possible_cpus()?;
1294 let rb_cnt = scxtop::topology::calculate_default_ringbuf_count(num_cpus);
1295 let rb_cpu_mapping = scxtop::topology::setup_cpu_to_ringbuf_mapping(rb_cnt, num_cpus)?;
1296
1297 log::info!("Using {} ringbuffers for {} CPUs", rb_cnt, num_cpus);
1298
1299 let cpu_cnt_pow2 = num_cpus.next_power_of_two();
1301 skel.maps.rodata_data.as_mut().unwrap().rb_cpu_map_mask = (cpu_cnt_pow2 - 1) as u64;
1302
1303 skel.maps.data_rb_cpu_map.set_max_entries(cpu_cnt_pow2 as u32)?;
1305
1306 skel.maps.events.set_max_entries(rb_cnt as u32)?;
1308
1309 let mut skel = skel.load()?;
1310
1311 for (cpu_id, &rb_id) in rb_cpu_mapping.iter().enumerate() {
1313 if cpu_id < cpu_cnt_pow2 {
1314 skel.maps.data_rb_cpu_map.update(
1315 &(cpu_id as u32).to_ne_bytes(),
1316 &rb_id.to_ne_bytes(),
1317 libbpf_rs::MapFlags::ANY,
1318 )?;
1319 }
1320 }
1321
1322 use scxtop::mcp::{
1324 WakerWakeeAnalyzer, LatencyTracker, CpuHotspotAnalyzer, MigrationAnalyzer,
1325 ProcessEventHistory, DsqMonitor, EventRateMonitor, WakeupChainTracker, EventBuffer,
1326 SoftirqAnalyzer,
1327 };
1328 use std::sync::Mutex;
1329
1330 let mut waker_wakee = WakerWakeeAnalyzer::new();
1332 waker_wakee.set_topology(topo_arc.clone());
1333 let waker_wakee_arc = Arc::new(Mutex::new(waker_wakee));
1334
1335 let latency_tracker = LatencyTracker::new(1000); let latency_tracker_arc = Arc::new(Mutex::new(latency_tracker));
1338
1339 let cpu_hotspot = CpuHotspotAnalyzer::new(100); let cpu_hotspot_arc = Arc::new(Mutex::new(cpu_hotspot));
1342
1343 let migration_analyzer = MigrationAnalyzer::new(1000); let migration_analyzer_arc = Arc::new(Mutex::new(migration_analyzer));
1346
1347 let process_history = ProcessEventHistory::new(100); let process_history_arc = Arc::new(Mutex::new(process_history));
1350
1351 let dsq_monitor = DsqMonitor::new();
1353 let dsq_monitor_arc = Arc::new(Mutex::new(dsq_monitor));
1354
1355 let rate_monitor = EventRateMonitor::new(1000, 10); let rate_monitor_arc = Arc::new(Mutex::new(rate_monitor));
1358
1359 let wakeup_tracker = WakeupChainTracker::new(10); let wakeup_tracker_arc = Arc::new(Mutex::new(wakeup_tracker));
1362
1363 let event_buffer = EventBuffer::new();
1365 let event_buffer_arc = Arc::new(Mutex::new(event_buffer));
1366
1367 let softirq_analyzer = SoftirqAnalyzer::new(10000); let softirq_analyzer_arc = Arc::new(Mutex::new(softirq_analyzer));
1370
1371 let mut edm = EventDispatchManager::new(None, None);
1373 edm.register_bpf_handler(Box::new(BpfEventActionPublisher::new(action_tx.clone())));
1374
1375 let dropped_invalid_ts = Arc::new(std::sync::atomic::AtomicU64::new(0));
1377
1378 let shutdown = Arc::new(AtomicBool::new(false));
1380
1381 let events_map_fd = skel.maps.events.as_fd().as_raw_fd();
1383 let mut rb_fds = Vec::new();
1384 let mut rb_managers: Vec<SendRingBuffer> = Vec::new();
1385
1386 for rb_id in 0..rb_cnt {
1387 let rb_fd = unsafe {
1389 libbpf_sys::bpf_map_create(
1390 libbpf_sys::BPF_MAP_TYPE_RINGBUF,
1391 std::ptr::null(),
1392 0,
1393 0,
1394 (32 * 1024 * 1024) as u32, std::ptr::null(),
1396 )
1397 };
1398
1399 if rb_fd < 0 {
1400 bail!("Failed to create ringbuffer #{}: {}", rb_id, std::io::Error::last_os_error());
1401 }
1402
1403 let rb_id_u32 = rb_id as u32;
1405 let ret = unsafe {
1406 libbpf_sys::bpf_map_update_elem(
1407 events_map_fd,
1408 &rb_id_u32 as *const u32 as *const std::ffi::c_void,
1409 &rb_fd as *const i32 as *const std::ffi::c_void,
1410 libbpf_sys::BPF_NOEXIST.into(),
1411 )
1412 };
1413
1414 if ret < 0 {
1415 bail!("Failed to add ringbuffer #{} to hash-of-maps: {}", rb_id, std::io::Error::last_os_error());
1416 }
1417
1418 rb_fds.push(rb_fd);
1419 }
1420
1421 struct McpRingBufContext {
1426 dropped_invalid_ts: Arc<std::sync::atomic::AtomicU64>,
1427 shared_stats: Arc<std::sync::RwLock<scxtop::mcp::SharedStats>>,
1428 action_tx: mpsc::UnboundedSender<Action>,
1429 waker_wakee: Arc<std::sync::Mutex<scxtop::mcp::WakerWakeeAnalyzer>>,
1430 cpu_hotspot: Arc<std::sync::Mutex<scxtop::mcp::CpuHotspotAnalyzer>>,
1431 migration_analyzer: Arc<std::sync::Mutex<scxtop::mcp::MigrationAnalyzer>>,
1432 process_history: Arc<std::sync::Mutex<scxtop::mcp::ProcessEventHistory>>,
1433 rate_monitor: Arc<std::sync::Mutex<scxtop::mcp::EventRateMonitor>>,
1434 wakeup_tracker: Arc<std::sync::Mutex<scxtop::mcp::WakeupChainTracker>>,
1435 softirq_analyzer: Arc<std::sync::Mutex<scxtop::mcp::SoftirqAnalyzer>>,
1436 shutdown: Arc<AtomicBool>,
1437 }
1438
1439 extern "C" fn mcp_ring_buffer_callback(
1440 ctx: *mut std::ffi::c_void,
1441 data: *mut std::ffi::c_void,
1442 size: u64,
1443 ) -> std::ffi::c_int {
1444 unsafe {
1445 let ctx = &*(ctx as *const McpRingBufContext);
1446
1447 if ctx.shutdown.load(std::sync::atomic::Ordering::Relaxed) {
1449 return 0;
1450 }
1451
1452 let mut event = bpf_event::default();
1453 let copy_size = std::cmp::min(size as usize, std::mem::size_of::<bpf_event>());
1454 std::ptr::copy_nonoverlapping(
1455 data as *const u8,
1456 &mut event as *mut bpf_event as *mut u8,
1457 copy_size,
1458 );
1459
1460 if event.ts == 0 {
1462 ctx.dropped_invalid_ts.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1463 return 0;
1464 }
1465
1466 if let Ok(mut stats) = ctx.shared_stats.write() {
1468 stats.update_from_event(&event);
1469 }
1470
1471 use scxtop::bpf_intf;
1473 let event_type = event.r#type as u32;
1474
1475 if let Ok(mut analyzer) = ctx.waker_wakee.try_lock() {
1477 match event_type {
1478 bpf_intf::event_type_SCHED_WAKEUP => {
1479 let wakeup = &event.event.wakeup;
1480 analyzer.record_wakeup(
1481 wakeup.pid, wakeup.waker_pid,
1482 &String::from_utf8_lossy(&wakeup.waker_comm),
1483 event.cpu, event.ts,
1484 );
1485 }
1486 bpf_intf::event_type_SCHED_WAKING => {
1487 let waking = &event.event.waking;
1488 analyzer.record_wakeup(
1489 waking.pid, waking.waker_pid,
1490 &String::from_utf8_lossy(&waking.waker_comm),
1491 event.cpu, event.ts,
1492 );
1493 }
1494 bpf_intf::event_type_SCHED_SWITCH => {
1495 let switch = &event.event.sched_switch;
1496 analyzer.record_wakee_run(
1497 switch.next_pid,
1498 &String::from_utf8_lossy(&switch.next_comm),
1499 event.cpu, event.ts,
1500 );
1501 }
1502 _ => {}
1503 }
1504 }
1505
1506 if let Ok(mut analyzer) = ctx.cpu_hotspot.try_lock() {
1508 let json = serde_json::json!({
1509 "cpu": event.cpu, "ts": event.ts, "event_type": event_type
1510 });
1511 analyzer.record_event(&json);
1512 }
1513
1514 if let Ok(mut analyzer) = ctx.migration_analyzer.try_lock()
1516 && event_type == bpf_intf::event_type_SCHED_MIGRATE {
1517 let migrate = &event.event.migrate;
1518 let json = serde_json::json!({
1519 "pid": migrate.pid, "from_cpu": event.cpu,
1520 "to_cpu": migrate.dest_cpu, "ts": event.ts
1521 });
1522 analyzer.record_migration(&json, event.ts);
1523 }
1524
1525 if let Ok(mut history) = ctx.process_history.try_lock() {
1527 let (event_type_str, pid) = match event_type {
1528 bpf_intf::event_type_SCHED_SWITCH => ("sched_switch", event.event.sched_switch.next_pid),
1529 bpf_intf::event_type_SCHED_WAKEUP => ("sched_wakeup", event.event.wakeup.pid),
1530 bpf_intf::event_type_SCHED_WAKING => ("sched_waking", event.event.waking.pid),
1531 bpf_intf::event_type_SCHED_MIGRATE => ("sched_migrate", event.event.migrate.pid),
1532 bpf_intf::event_type_EXIT => ("exit", event.event.exit.pid),
1533 bpf_intf::event_type_EXEC => ("exec", event.event.exec.pid),
1534 _ => ("other", 0),
1535 };
1536 if pid > 0 {
1537 history.record_event(
1538 pid, event_type_str.to_string(), Some(event.cpu),
1539 serde_json::json!({"ts": event.ts}), event.ts,
1540 );
1541 }
1542 }
1543
1544 if let Ok(mut monitor) = ctx.rate_monitor.try_lock() {
1546 let event_type_str = match event_type {
1547 bpf_intf::event_type_SCHED_SWITCH => "sched_switch",
1548 bpf_intf::event_type_SCHED_WAKEUP => "sched_wakeup",
1549 bpf_intf::event_type_SCHED_WAKING => "sched_waking",
1550 bpf_intf::event_type_SCHED_MIGRATE => "sched_migrate",
1551 _ => "other",
1552 };
1553 monitor.record_event(event_type_str.to_string(), event.ts);
1554 }
1555
1556 if let Ok(mut tracker) = ctx.wakeup_tracker.try_lock()
1558 && (event_type == bpf_intf::event_type_SCHED_WAKEUP || event_type == bpf_intf::event_type_SCHED_WAKING) {
1559 let (pid, waker_pid) = if event_type == bpf_intf::event_type_SCHED_WAKEUP {
1560 (event.event.wakeup.pid, event.event.wakeup.waker_pid)
1561 } else {
1562 (event.event.waking.pid, event.event.waking.waker_pid)
1563 };
1564 let json = serde_json::json!({
1565 "pid": pid, "waker_pid": waker_pid, "ts": event.ts, "cpu": event.cpu
1566 });
1567 tracker.record_wakeup(&json, event.ts);
1568 }
1569
1570 if let Ok(mut analyzer) = ctx.softirq_analyzer.try_lock()
1572 && event_type == bpf_intf::event_type_SOFTIRQ {
1573 let softirq = &event.event.softirq;
1574 let json = serde_json::json!({
1575 "type": "softirq", "pid": softirq.pid, "softirq_nr": softirq.softirq_nr,
1576 "entry_ts": softirq.entry_ts, "exit_ts": softirq.exit_ts, "cpu": event.cpu,
1577 });
1578 analyzer.record_event(&json);
1579 }
1580
1581 let mut edm = EventDispatchManager::new(None, None);
1583 edm.register_bpf_handler(Box::new(BpfEventActionPublisher::new(ctx.action_tx.clone())));
1584 let _ = edm.on_event(&event);
1585 }
1586 0
1587 }
1588
1589 for rb_fd in &rb_fds {
1590 let ctx = Box::new(McpRingBufContext {
1591 dropped_invalid_ts: dropped_invalid_ts.clone(),
1592 shared_stats: shared_stats_for_event_handler.clone(),
1593 action_tx: action_tx.clone(),
1594 waker_wakee: waker_wakee_arc.clone(),
1595 cpu_hotspot: cpu_hotspot_arc.clone(),
1596 migration_analyzer: migration_analyzer_arc.clone(),
1597 process_history: process_history_arc.clone(),
1598 rate_monitor: rate_monitor_arc.clone(),
1599 wakeup_tracker: wakeup_tracker_arc.clone(),
1600 softirq_analyzer: softirq_analyzer_arc.clone(),
1601 shutdown: shutdown.clone(),
1602 });
1603 let ctx_ptr = Box::into_raw(ctx) as *mut std::ffi::c_void;
1604
1605 let rb_ptr = unsafe {
1606 libbpf_sys::ring_buffer__new(
1607 *rb_fd,
1608 Some(mcp_ring_buffer_callback),
1609 ctx_ptr,
1610 std::ptr::null(),
1611 )
1612 };
1613
1614 if rb_ptr.is_null() {
1615 unsafe { let _ = Box::from_raw(ctx_ptr as *mut McpRingBufContext); }
1616 bail!("Failed to create ring buffer manager");
1617 }
1618
1619 rb_managers.push(SendRingBuffer(rb_ptr));
1620 }
1621
1622 let (initial_links, _warnings) = attach_progs(&mut skel)?;
1624
1625 if !initial_links.is_empty() || is_root() {
1627 skel.progs
1628 .scxtop_init
1629 .test_run(ProgramInput::default())
1630 .ok();
1631 }
1632
1633 use scxtop::mcp::BpfPerfEventAttacher;
1637 let perf_program_addr = &skel.progs.perf_sample_handler as *const _ as usize;
1639
1640 let bpf_attacher = BpfPerfEventAttacher::new(move |perf_fd| {
1641 unsafe {
1644 let prog =
1646 &*(perf_program_addr as *const libbpf_rs::ProgramImpl<libbpf_rs::Mut>);
1647 prog.attach_perf_event(perf_fd)
1648 .map(|link| Box::new(link) as Box<dyn std::any::Any + Send>)
1649 .map_err(|e| anyhow::anyhow!("Failed to attach perf event: {}", e))
1650 }
1651 });
1652 let bpf_attacher_arc = Arc::new(bpf_attacher);
1653
1654 use scxtop::mcp::{AttachCallback, EventControl, StatsControlCommand};
1656 let mut event_control_instance = EventControl::new();
1657
1658 let skel_ptr = &mut skel as *mut _ as usize;
1661 let attach_callback: AttachCallback = Box::new(move |program_names: &[&str]| {
1662 unsafe {
1663 let skel_ref = &mut *(skel_ptr as *mut BpfSkel);
1664 attach_progs_selective(skel_ref, program_names).map(|(links, _)| links)
1665 }
1666 });
1667
1668 event_control_instance.set_bpf_links(initial_links, attach_callback);
1671 event_control_instance.disable_event_tracking()?;
1672 info!("BPF programs detached by default - use control_event_tracking to enable");
1673
1674 let (stats_tx, stats_rx) = mpsc::unbounded_channel::<StatsControlCommand>();
1676 event_control_instance.set_stats_control_channel(stats_tx);
1677
1678 let event_control = Arc::new(event_control_instance);
1680
1681 let config = Config::default_config();
1683 let scheduler =
1684 read_file_string(SCHED_NAME_PATH).unwrap_or_else(|_| "".to_string());
1685 let mut app = App::new(
1686 config,
1687 scheduler,
1688 100,
1689 mcp_args.process_id,
1690 mcp_args.layered,
1691 action_tx.clone(),
1692 skel,
1693 )?;
1694
1695 use scxtop::mcp::AnalyzerControl;
1697
1698 let mut analyzer_control = AnalyzerControl::new();
1699 analyzer_control.set_event_control(event_control.clone());
1700
1701 analyzer_control.set_event_buffer(event_buffer_arc.clone());
1703 analyzer_control.set_latency_tracker(latency_tracker_arc.clone());
1704 analyzer_control.set_cpu_hotspot_analyzer(cpu_hotspot_arc.clone());
1705 analyzer_control.set_migration_analyzer(migration_analyzer_arc.clone());
1706 analyzer_control.set_process_history(process_history_arc.clone());
1707 analyzer_control.set_dsq_monitor(dsq_monitor_arc.clone());
1708 analyzer_control.set_rate_monitor(rate_monitor_arc.clone());
1709 analyzer_control.set_wakeup_tracker(wakeup_tracker_arc.clone());
1710 analyzer_control.set_waker_wakee_analyzer(waker_wakee_arc.clone());
1711 analyzer_control.set_softirq_analyzer(softirq_analyzer_arc.clone());
1712
1713 let analyzer_control = Arc::new(Mutex::new(analyzer_control));
1715
1716 use std::collections::HashMap;
1718 let trace_cache = Arc::new(Mutex::new(HashMap::new()));
1719
1720 let mut server = McpServer::new(mcp_config)
1722 .with_topology(topo_arc)
1723 .setup_scheduler_resource()
1724 .setup_profiling_resources()
1725 .with_bpf_perf_attacher(bpf_attacher_arc)
1726 .with_shared_stats(shared_stats.clone())
1727 .with_stats_client(None)
1728 .with_event_control(event_control.clone())
1729 .with_analyzer_control(analyzer_control.clone())
1730 .with_trace_cache(trace_cache)
1731 .setup_stats_resources();
1732
1733 let _event_stream_rx = server.enable_event_streaming();
1735 let resources = server.get_resources_handle();
1736
1737 let bpf_stats = server.get_bpf_stats_collector();
1739
1740 let perf_profiler = server.get_perf_profiler();
1742
1743 let shutdown_poll = shutdown.clone();
1745
1746 let mut ringbuffer_handles = Vec::new();
1747 for (rb_id, rb) in rb_managers.into_iter().enumerate() {
1748 let stop_poll_clone = shutdown_poll.clone();
1749 ringbuffer_handles.push(tokio::spawn(async move {
1750 loop {
1751 rb.poll(1);
1753 if stop_poll_clone.load(Ordering::Relaxed) {
1754 rb.consume();
1756 rb.free();
1758 debug!("ringbuffer #{} polling stopped", rb_id);
1759 break;
1760 }
1761 }
1762 }));
1763 }
1764
1765 if let Some(collector) = bpf_stats {
1768 let shutdown_stats = shutdown.clone();
1769 let mut stats_rx_task = stats_rx;
1770 tokio::spawn(async move {
1771 let mut running = false;
1772 let mut interval_ms = 100u64;
1773 let mut interval = tokio::time::interval(Duration::from_millis(interval_ms));
1774
1775 loop {
1776 tokio::select! {
1777 Some(cmd) = stats_rx_task.recv() => {
1779 match cmd {
1780 StatsControlCommand::Start(new_interval_ms) => {
1781 running = true;
1782 interval_ms = new_interval_ms;
1783 interval = tokio::time::interval(Duration::from_millis(interval_ms));
1784 info!("Stats collection started with {}ms interval", interval_ms);
1785 }
1786 StatsControlCommand::Stop => {
1787 running = false;
1788 info!("Stats collection stopped");
1789 }
1790 }
1791 }
1792
1793 _ = interval.tick(), if running => {
1795 if shutdown_stats.load(Ordering::Relaxed) {
1796 break;
1797 }
1798 let _ = collector.collect_sample();
1799 }
1800
1801 _ = tokio::time::sleep(Duration::from_millis(100)), if !running => {
1803 if shutdown_stats.load(Ordering::Relaxed) {
1804 break;
1805 }
1806 }
1807 }
1808 }
1809 });
1810 }
1811
1812 info!("MCP daemon started, processing BPF events");
1813
1814 let mut mcp_server_task = Box::pin(server.run_async());
1816 let mcp_result;
1817 loop {
1818 tokio::select! {
1819 result = &mut mcp_server_task => {
1821 info!("MCP server exited");
1822 shutdown.store(true, Ordering::Relaxed);
1823 mcp_result = result;
1824 break;
1825 }
1826
1827 Some(action) = action_rx.recv() => {
1829 if matches!(action, Action::Quit) {
1831 info!("Received quit action");
1832 shutdown.store(true, Ordering::Relaxed);
1833 mcp_result = Ok(());
1834 break;
1835 }
1836
1837 if let Some(ref profiler) = perf_profiler
1839 && let Action::PerfSample(ref perf_sample) = action {
1840 use scxtop::mcp::RawSample;
1841 profiler.add_sample(RawSample {
1842 address: perf_sample.instruction_pointer,
1843 pid: perf_sample.pid,
1844 cpu_id: perf_sample.cpu_id,
1845 is_kernel: perf_sample.is_kernel,
1846 kernel_stack: perf_sample.kernel_stack.clone(),
1847 user_stack: perf_sample.user_stack.clone(),
1848 layer_id: if perf_sample.layer_id >= 0 {
1849 Some(perf_sample.layer_id)
1850 } else {
1851 None
1852 },
1853 });
1854 }
1855
1856 let _ = app.handle_action(&action);
1858
1859 if let Some(event) = action_to_mcp_event(&action) {
1861 let _ = resources.push_event(event);
1862 }
1863 }
1864 }
1865 }
1866
1867 debug!("waiting for {} ringbuffer tasks to complete", ringbuffer_handles.len());
1869 for handle in ringbuffer_handles {
1870 if let Err(e) = handle.await {
1871 log::error!("Ringbuffer task panicked: {e}");
1872 }
1873 }
1874
1875 mcp_result
1877 })
1878 } else {
1879 use std::collections::HashMap;
1881 use std::sync::Mutex;
1882 let trace_cache = Arc::new(Mutex::new(HashMap::new()));
1883 let mut server = McpServer::new(mcp_config)
1884 .with_topology(topo_arc)
1885 .setup_scheduler_resource()
1886 .setup_profiling_resources()
1887 .with_stats_client(None)
1888 .with_trace_cache(trace_cache)
1889 .setup_stats_resources();
1890 server.run_blocking()
1891 }
1892}
1893
1894fn main() -> Result<()> {
1895 let args = Cli::parse();
1896
1897 match &args.command.unwrap_or(Commands::Tui(args.tui)) {
1898 Commands::Tui(tui_args) => {
1899 run_tui(tui_args)?;
1900 }
1901 Commands::Trace(trace_args) => {
1902 run_trace(trace_args)?;
1903 }
1904 Commands::Mcp(mcp_args) => {
1905 run_mcp(mcp_args)?;
1906 }
1907 Commands::GenerateCompletions { shell, output } => {
1908 generate_completions(Cli::command(), *shell, output.clone())
1909 .unwrap_or_else(|_| panic!("Failed to generate completions for {shell}"));
1910 }
1911 }
1912 Ok(())
1913}