Skip to main content

scxtop/
main.rs

1// Copyright (c) Meta Platforms, Inc. and affiliates.
2//
3// This software may be used and distributed according to the terms of the
4// GNU General Public License version 2.
5
6use 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
65// Wrapper to make ring buffer pointer Send-safe for tokio spawn
66// SAFETY: We ensure the pointer remains valid for the task lifetime
67struct 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            // Check if we should handle this character as input for filtering
101            if let Some(action) = handle_input_entry(app, c.to_string()) {
102                action
103            } else {
104                // Check for state-specific key bindings before falling back to global keymap
105                match (app.state(), c) {
106                    // In BPF program detail view, 'p' toggles perf sampling
107                    (AppState::BpfProgramDetail, 'p') => Action::ToggleBpfPerfSampling,
108                    // Fall back to global keymap for all other cases
109                    _ => 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
136/// Attaches BPF programs to the skel, handling non-root scenarios gracefully
137fn attach_progs(skel: &mut BpfSkel) -> Result<(Vec<Link>, Vec<String>)> {
138    attach_progs_selective(skel, &[])
139}
140
141/// Attaches specified BPF programs to the skel
142/// If program_names is empty, attaches all programs
143fn 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    // Check capabilities before attempting to attach
151    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    // Helper function to check if a program should be attached
163    let should_attach = |name: &str| -> bool { attach_all || program_names.contains(&name) };
164
165    // Helper macro to safely attach programs and collect warnings
166    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                            // If running as root and still failing, it's a real error
176                            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    // Try to attach core scheduler probes
191    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    // 6.13 compatibility probes
204    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        // v2 API variants (6.19+) - schedulers call these directly via compat macros
212        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    // Optional probes
237    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 no links were successfully attached and we're not root, provide helpful guidance
243    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    // Trace function always requires root privileges
252    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    // Calculate how many ringbuffers we'll need to ensure enough worker threads
279    let num_cpus = num_possible_cpus()?;
280    let rb_cnt = scxtop::topology::calculate_default_ringbuf_count(num_cpus);
281
282    // Ensure we have at least rb_cnt + 4 worker threads
283    // (+4 for trace generation, stats, and other async tasks)
284    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            // Set up the BPF skel and publisher
300            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            // Set up multiple ringbuffers for scalability
313            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            // Set up CPU-to-ringbuffer mapping in BPF
320            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            // Set max entries for the CPU-to-ringbuf map array
324            skel.maps
325                .data_rb_cpu_map
326                .set_max_entries(cpu_cnt_pow2 as u32)?;
327
328            // Set max entries for events hash-of-maps
329            skel.maps.events.set_max_entries(rb_cnt as u32)?;
330
331            // Load the BPF skeleton (no graceful handling for trace mode - requires root)
332            let mut skel = skel.load()?;
333
334            // Populate the CPU-to-ringbuffer mapping after loading
335            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            // Attach programs (no graceful handling for trace mode - requires root)
348            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            // 6.13 compatibility
363            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                // v2 API variants (6.19+)
383                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            // Counter for events dropped due to invalid timestamps (userspace filtering)
427            let dropped_invalid_ts = Arc::new(std::sync::atomic::AtomicU64::new(0));
428
429            // Create shutdown flag early so it can be used in ringbuffer callbacks
430            let shutdown = Arc::new(AtomicBool::new(false));
431
432            // Create multiple ringbuffers and add them to the hash-of-maps
433            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                // Create individual ringbuffer (size must be power of 2)
439                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, // 32MB per ringbuffer (must be power of 2)
446                        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                // Add ringbuffer to hash-of-maps
459                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            // Set up ring buffer managers using raw libbpf C API
481            // We use the C API because we're creating ringbuffers dynamically
482            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                    // Stop processing if shutdown requested
497                    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                    // Drop events with invalid timestamps
510                    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            // Set up the background threads to poll all ringbuffers
553            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            // Spawn a separate blocking task for each ringbuffer
560            // Use spawn_blocking because rb.poll() is a blocking C FFI call
561            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                        // Poll with 1ms timeout (blocking call)
568                        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                            // Consume remaining events
576                            let consumed = rb.consume();
577                            info!("ringbuffer #{} consumed {} events", rb_id, consumed);
578                            // Free the ring buffer
579                            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            // Embed topology metadata in traces for cross-machine analysis
636            {
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                        // Check shutdown flag to stop early if requested
664                        _ = 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                                // After processing, check shutdown to avoid
680                                // draining the entire buffered channel through
681                                // select! one event at a time.
682                                if shutdown_trace.load(Ordering::Relaxed) {
683                                    break;
684                                }
685                            } else {
686                                break;
687                            }
688                        }
689                    }
690                }
691                // Drain remaining events in a tight loop without select!
692                // overhead. This is much faster for large buffered channels.
693                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            // Proper shutdown sequence to avoid hanging:
710            // 1) Stop new BPF events by detaching programs
711            // 2) Set shutdown flag to stop polling tasks
712            // 3) Wait for all ringbuffer tasks to consume remaining events and exit
713            // 4) Wait for stats task to exit
714            // 5) Drop action_tx to close the channel (all producers are done)
715            // 6) Wait for trace generation to complete
716            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            // Wait for all ringbuffer polling tasks to finish consuming
730            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            // Wait for producer tasks (stats) to complete
742            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            // Now safe to drop action_tx - all producers are done
758            info!("shutdown: dropping action_tx");
759            drop(action_tx);
760            info!("shutdown: action_tx dropped, waiting for trace generation");
761
762            // Wait for trace generation to complete
763            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    // Calculate how many ringbuffers we'll need to ensure enough worker threads
802    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    // Ensure we have at least rb_cnt + 4 worker threads
807    // (+4 for UI rendering, event handling, and other async tasks)
808    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            // Declare open_object at the very beginning so it lives for the entire async block
817            let mut open_object = MaybeUninit::uninit();
818
819            let (action_tx, mut action_rx) = mpsc::unbounded_channel();
820
821            // Check capabilities early to determine if we can run with BPF functionality
822            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>, // rb_fds
828                Arc<std::sync::atomic::AtomicU64>, // dropped_invalid_ts
829                mpsc::UnboundedSender<Action>, // action_tx for ringbuffer contexts
830            )> = None;
831            let mut skel_opt = None;
832
833            if has_bpf_cap {
834                // Try to initialize BPF components
835                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                // Try to open the BPF skeleton with graceful error handling
844                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                        // Set up multiple ringbuffers for scalability
873                        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                        // Set up CPU-to-ringbuffer mapping in BPF
880                        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                        // Set max entries for the CPU-to-ringbuf map array
884                        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                        // Set max entries for events hash-of-maps
889                        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                        // Try to load the BPF skeleton
907                        match skel.load() {
908                            Ok(mut loaded_skel) => {
909                                // Populate the CPU-to-ringbuffer mapping after loading
910                                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                                    // Only run scxtop_init if we have some BPF functionality
927                                    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                                // Set up event ring buffer if we have any attached programs
933                                if !links.is_empty() {
934                                    // Counter for events dropped due to invalid timestamps (userspace filtering)
935                                    let dropped_invalid_ts = Arc::new(std::sync::atomic::AtomicU64::new(0));
936
937                                    // Create multiple ringbuffers and add them to the hash-of-maps
938                                    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                                        // Create individual ringbuffer (size must be power of 2)
943                                        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, // 32MB per ringbuffer (must be power of 2)
950                                                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                                        // Add ringbuffer to hash-of-maps
960                                        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                                        // Save data for later ringbuffer manager creation (after app is created)
980                                        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                // No BPF capabilities detected
1008                capability_warnings.extend(get_capability_warning_message());
1009            }
1010
1011            // Handle experimental long tail tracing if enabled and we have a skeleton
1012            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, /* pid, -1 == all */
1028                        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, /* pid, -1 == all */
1042                        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            // Create app with or without BPF skeleton
1062            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                // Create app without BPF functionality
1074                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            // Pass warnings to the app if any exist
1085            if !capability_warnings.is_empty() {
1086                app.set_capability_warnings(capability_warnings);
1087            }
1088
1089            tui.enter()?;
1090
1091            // Start BPF event polling only if we have ringbuffer data
1092            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                // Set up ring buffer managers using raw libbpf C API
1096                // Now that app is created, we can use app.should_quit for the callbacks
1097                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                        // Stop processing if shutdown requested
1112                        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                        // Drop events with invalid timestamps
1125                        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                // Spawn a separate task for each ringbuffer
1138                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                    // Use spawn_blocking because rb.poll() is a blocking C FFI call
1165                    ringbuffer_handles.push(tokio::task::spawn_blocking(move || {
1166                        loop {
1167                            // Poll with 1ms timeout (blocking call)
1168                            rb.poll(1);
1169                            if shutdown_clone.load(Ordering::Relaxed) {
1170                                // Consume remaining events
1171                                rb.consume();
1172                                // Free the ring buffer
1173                                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            // Wait for all ringbuffer tasks to finish consuming remaining events
1232            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    // Set up logging to stderr (important: not stdout, which is used for MCP protocol)
1251    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    // Initialize topology
1264    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        // Daemon mode: Full BPF event processing
1274        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                // Create shared stats for MCP server
1284                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                // Set up BPF
1289                let builder = BpfSkelBuilder::default();
1290                let mut skel = builder.open(&mut open_object)?;
1291
1292                // Set up multiple ringbuffers for scalability
1293                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                // Set up CPU-to-ringbuffer mapping in BPF
1300                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                // Set max entries for the CPU-to-ringbuf map array
1304                skel.maps.data_rb_cpu_map.set_max_entries(cpu_cnt_pow2 as u32)?;
1305
1306                // Set max entries for events hash-of-maps
1307                skel.maps.events.set_max_entries(rb_cnt as u32)?;
1308
1309                let mut skel = skel.load()?;
1310
1311                // Populate the CPU-to-ringbuffer mapping after loading
1312                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                // Create ALL analyzers BEFORE setting up event handlers
1323                use scxtop::mcp::{
1324                    WakerWakeeAnalyzer, LatencyTracker, CpuHotspotAnalyzer, MigrationAnalyzer,
1325                    ProcessEventHistory, DsqMonitor, EventRateMonitor, WakeupChainTracker, EventBuffer,
1326                    SoftirqAnalyzer,
1327                };
1328                use std::sync::Mutex;
1329
1330                // 1. Waker/Wakee Analyzer
1331                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                // 2. Latency Tracker
1336                let latency_tracker = LatencyTracker::new(1000); // 1 second window
1337                let latency_tracker_arc = Arc::new(Mutex::new(latency_tracker));
1338
1339                // 3. CPU Hotspot Analyzer
1340                let cpu_hotspot = CpuHotspotAnalyzer::new(100); // 100ms window
1341                let cpu_hotspot_arc = Arc::new(Mutex::new(cpu_hotspot));
1342
1343                // 4. Migration Analyzer
1344                let migration_analyzer = MigrationAnalyzer::new(1000); // 1 second window
1345                let migration_analyzer_arc = Arc::new(Mutex::new(migration_analyzer));
1346
1347                // 5. Process Event History
1348                let process_history = ProcessEventHistory::new(100); // 100 events per process
1349                let process_history_arc = Arc::new(Mutex::new(process_history));
1350
1351                // 6. DSQ Monitor
1352                let dsq_monitor = DsqMonitor::new();
1353                let dsq_monitor_arc = Arc::new(Mutex::new(dsq_monitor));
1354
1355                // 7. Event Rate Monitor
1356                let rate_monitor = EventRateMonitor::new(1000, 10); // 1s window, 10 baselines
1357                let rate_monitor_arc = Arc::new(Mutex::new(rate_monitor));
1358
1359                // 8. Wakeup Chain Tracker
1360                let wakeup_tracker = WakeupChainTracker::new(10); // max 10 chain length
1361                let wakeup_tracker_arc = Arc::new(Mutex::new(wakeup_tracker));
1362
1363                // 9. Event Buffer
1364                let event_buffer = EventBuffer::new();
1365                let event_buffer_arc = Arc::new(Mutex::new(event_buffer));
1366
1367                // 10. Softirq Analyzer
1368                let softirq_analyzer = SoftirqAnalyzer::new(10000); // 10 second window
1369                let softirq_analyzer_arc = Arc::new(Mutex::new(softirq_analyzer));
1370
1371                // Set up event dispatch manager
1372                let mut edm = EventDispatchManager::new(None, None);
1373                edm.register_bpf_handler(Box::new(BpfEventActionPublisher::new(action_tx.clone())));
1374
1375                // Counter for events dropped due to invalid timestamps (userspace filtering)
1376                let dropped_invalid_ts = Arc::new(std::sync::atomic::AtomicU64::new(0));
1377
1378                // Create shutdown flag early so it can be used in ringbuffer callbacks
1379                let shutdown = Arc::new(AtomicBool::new(false));
1380
1381                // Create multiple ringbuffers and add them to the hash-of-maps
1382                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                    // Create individual ringbuffer (size must be power of 2)
1388                    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, // 32MB per ringbuffer (must be power of 2)
1395                            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                    // Add ringbuffer to hash-of-maps
1404                    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                // Set up ring buffer managers using raw libbpf C API
1422                // We use the C API because we're creating ringbuffers dynamically
1423
1424                // Context struct holding all the data needed by the callback
1425                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                        // Stop processing if shutdown requested
1448                        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                        // Drop events with invalid timestamps
1461                        if event.ts == 0 {
1462                            ctx.dropped_invalid_ts.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1463                            return 0;
1464                        }
1465
1466                        // Update shared stats from BPF event
1467                        if let Ok(mut stats) = ctx.shared_stats.write() {
1468                            stats.update_from_event(&event);
1469                        }
1470
1471                        // Feed events to all analyzers
1472                        use scxtop::bpf_intf;
1473                        let event_type = event.r#type as u32;
1474
1475                        // 1. Waker/Wakee Analyzer
1476                        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                        // 2. CPU Hotspot Analyzer
1507                        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                        // 3. Migration Analyzer
1515                        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                        // 4. Process Event History
1526                        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                        // 6. Event Rate Monitor
1545                        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                        // 7. Wakeup Chain Tracker
1557                        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                        // 8. Softirq Analyzer
1571                        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                        // Dispatch to action channel
1582                        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                // Attach BPF programs initially
1623                let (initial_links, _warnings) = attach_progs(&mut skel)?;
1624
1625                // Initialize BPF program
1626                if !initial_links.is_empty() || is_root() {
1627                    skel.progs
1628                        .scxtop_init
1629                        .test_run(ProgramInput::default())
1630                        .ok();
1631                }
1632
1633                // Create BPF perf event attacher BEFORE passing skeleton to App
1634                // This gives us a handle to attach perf events for profiling
1635                // We create a closure that captures a raw pointer to the program
1636                use scxtop::mcp::BpfPerfEventAttacher;
1637                // Get raw pointer to the perf_sample_handler program as usize to make it Send
1638                let perf_program_addr = &skel.progs.perf_sample_handler as *const _ as usize;
1639
1640                let bpf_attacher = BpfPerfEventAttacher::new(move |perf_fd| {
1641                    // SAFETY: The skeleton is kept alive in the App and not dropped
1642                    // until the MCP server is done, so this pointer remains valid
1643                    unsafe {
1644                        // Cast back to ProgramImpl with Mut parameter
1645                        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                // Create event control for dynamic BPF program attachment/detachment
1655                use scxtop::mcp::{AttachCallback, EventControl, StatsControlCommand};
1656                let mut event_control_instance = EventControl::new();
1657
1658                // Create attach callback using skeleton pointer (similar to perf attacher)
1659                // SAFETY: Skeleton is kept alive in App until daemon shutdown
1660                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                // Give EventControl the initial links and callback, then immediately detach
1669                // to start with minimal overhead
1670                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                // Create stats control channel
1675                let (stats_tx, stats_rx) = mpsc::unbounded_channel::<StatsControlCommand>();
1676                event_control_instance.set_stats_control_channel(stats_tx);
1677
1678                // Wrap in Arc after configuration
1679                let event_control = Arc::new(event_control_instance);
1680
1681                // Create App (but don't use it in spawned tasks due to Send constraints)
1682                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                // Create analyzer control and register ALL analyzers
1696                use scxtop::mcp::AnalyzerControl;
1697
1698                let mut analyzer_control = AnalyzerControl::new();
1699                analyzer_control.set_event_control(event_control.clone());
1700
1701                // Register all analyzers
1702                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                // Wrap analyzer control in Arc<Mutex<>>
1714                let analyzer_control = Arc::new(Mutex::new(analyzer_control));
1715
1716                // Create trace cache for perfetto analysis
1717                use std::collections::HashMap;
1718                let trace_cache = Arc::new(Mutex::new(HashMap::new()));
1719
1720                // Create MCP server
1721                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                // Enable event streaming
1734                let _event_stream_rx = server.enable_event_streaming();
1735                let resources = server.get_resources_handle();
1736
1737                // Get BPF stats collector for periodic sampling
1738                let bpf_stats = server.get_bpf_stats_collector();
1739
1740                // Get perf profiler for stack trace collection
1741                let perf_profiler = server.get_perf_profiler();
1742
1743                // Start BPF polling tasks - spawn a separate task for each ringbuffer
1744                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                            // Poll with 1ms timeout
1752                            rb.poll(1);
1753                            if stop_poll_clone.load(Ordering::Relaxed) {
1754                                // Consume remaining events
1755                                rb.consume();
1756                                // Free the ring buffer
1757                                rb.free();
1758                                debug!("ringbuffer #{} polling stopped", rb_id);
1759                                break;
1760                            }
1761                        }
1762                    }));
1763                }
1764
1765                // Start controllable BPF stats collection task
1766                // Task responds to start/stop commands via channel, starts in stopped state
1767                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                                // Handle control commands
1778                                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                                // Collect stats if running
1794                                _ = interval.tick(), if running => {
1795                                    if shutdown_stats.load(Ordering::Relaxed) {
1796                                        break;
1797                                    }
1798                                    let _ = collector.collect_sample();
1799                                }
1800
1801                                // Check shutdown even when not running
1802                                _ = 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                // Main loop: handle both MCP server and action processing
1815                let mut mcp_server_task = Box::pin(server.run_async());
1816                let mcp_result;
1817                loop {
1818                    tokio::select! {
1819                        // Handle MCP server
1820                        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                        // Handle actions from BPF
1828                        Some(action) = action_rx.recv() => {
1829                            // Check for shutdown
1830                            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                            // Feed perf samples to profiler if it's collecting
1838                            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                            // Update app state
1857                            let _ = app.handle_action(&action);
1858
1859                            // Convert action to MCP event and push to stream
1860                            if let Some(event) = action_to_mcp_event(&action) {
1861                                let _ = resources.push_event(event);
1862                            }
1863                        }
1864                    }
1865                }
1866
1867                // Wait for all ringbuffer tasks to finish consuming remaining events
1868                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                // Links are managed by EventControl and will be dropped on shutdown
1876                mcp_result
1877            })
1878    } else {
1879        // One-shot mode: No BPF, just serve static data
1880        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}