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::bpf_skel::types::bpf_event;
8use scxtop::cli::{generate_completions, Cli, Commands, TraceArgs, TuiArgs};
9use scxtop::config::Config;
10use scxtop::edm::{ActionHandler, BpfEventActionPublisher, BpfEventHandler, EventDispatchManager};
11use scxtop::layered_util;
12use scxtop::mangoapp::poll_mangoapp;
13use scxtop::search;
14use scxtop::tracer::Tracer;
15use scxtop::util::{
16    check_bpf_capability, get_capability_warning_message, get_clock_value, is_root,
17    read_file_string,
18};
19use scxtop::Action;
20use scxtop::App;
21use scxtop::CpuStatTracker;
22use scxtop::Event;
23use scxtop::Key;
24use scxtop::KeyMap;
25use scxtop::MemStatSnapshot;
26use scxtop::PerfettoTraceManager;
27use scxtop::SystemStatAction;
28use scxtop::Tui;
29use scxtop::SCHED_NAME_PATH;
30use scxtop::{available_kprobe_events, UpdateColVisibilityAction};
31use scxtop::{bpf_skel::*, AppState};
32
33use anyhow::anyhow;
34use anyhow::Result;
35use clap::{CommandFactory, Parser};
36use futures::future::join_all;
37use libbpf_rs::skel::OpenSkel;
38use libbpf_rs::skel::SkelBuilder;
39use libbpf_rs::Link;
40use libbpf_rs::ProgramInput;
41use libbpf_rs::RingBufferBuilder;
42use libbpf_rs::UprobeOpts;
43use log::debug;
44use log::info;
45use ratatui::crossterm::event::{KeyCode::Char, KeyEvent};
46use simplelog::{
47    ColorChoice, Config as SimplelogConfig, LevelFilter, TermLogger, TerminalMode, WriteLogger,
48};
49use std::ffi::CString;
50use std::fs::File;
51use std::mem::MaybeUninit;
52use std::str::FromStr;
53use std::sync::atomic::AtomicBool;
54use std::sync::atomic::Ordering;
55use std::sync::Arc;
56use std::time::Duration;
57use sysinfo::System;
58use tokio::sync::mpsc;
59
60fn get_action(app: &App, keymap: &KeyMap, event: Event) -> Action {
61    match event {
62        Event::Error => Action::None,
63        Event::Tick => Action::Tick,
64        Event::TickRateChange(tick_rate_ms) => {
65            Action::TickRateChange(std::time::Duration::from_millis(tick_rate_ms))
66        }
67        Event::Key(key) => handle_key_event(app, keymap, key),
68        Event::Paste(paste) => handle_input_entry(app, paste).unwrap_or(Action::None),
69        _ => Action::None,
70    }
71}
72
73fn handle_key_event(app: &App, keymap: &KeyMap, key: KeyEvent) -> Action {
74    match key.code {
75        Char(c) => {
76            // Check if we should handle this character as input for filtering
77            if let Some(action) = handle_input_entry(app, c.to_string()) {
78                action
79            } else {
80                keymap.action(&Key::Char(c))
81            }
82        }
83        _ => keymap.action(&Key::Code(key.code)),
84    }
85}
86
87fn handle_input_entry(app: &App, s: String) -> Option<Action> {
88    match app.state() {
89        AppState::PerfEvent | AppState::KprobeEvent => Some(Action::InputEntry(s)),
90        AppState::Default
91        | AppState::Llc
92        | AppState::Node
93        | AppState::Process
94        | AppState::Memory
95        | AppState::PerfTop
96            if app.filtering() =>
97        {
98            Some(Action::InputEntry(s))
99        }
100        _ => None,
101    }
102}
103
104/// Attaches BPF programs to the skel, handling non-root scenarios gracefully
105fn attach_progs(skel: &mut BpfSkel) -> Result<(Vec<Link>, Vec<String>)> {
106    let mut links = Vec::new();
107    let mut warnings = Vec::new();
108
109    // Check capabilities before attempting to attach
110    let has_bpf_cap = check_bpf_capability();
111
112    if !has_bpf_cap {
113        warnings
114            .push("BPF programs cannot be attached - scheduler monitoring disabled".to_string());
115        warnings.push("Try running as root or configure BPF permissions".to_string());
116        return Ok((links, warnings));
117    }
118
119    // Helper macro to safely attach programs and collect warnings
120    macro_rules! safe_attach {
121        ($prog:expr, $name:literal) => {
122            match $prog.attach() {
123                Ok(link) => {
124                    links.push(link);
125                }
126                Err(e) => {
127                    if is_root() {
128                        // If running as root and still failing, it's a real error
129                        return Err(anyhow!(
130                            "Failed to attach {} (running as root): {}",
131                            $name,
132                            e
133                        ));
134                    } else {
135                        warnings.push(format!("Failed to attach {}: {}", $name, e));
136                    }
137                }
138            }
139        };
140    }
141
142    // Try to attach core scheduler probes
143    safe_attach!(skel.progs.on_sched_cpu_perf, "sched_cpu_perf");
144    safe_attach!(skel.progs.scx_sched_reg, "scx_sched_reg");
145    safe_attach!(skel.progs.scx_sched_unreg, "scx_sched_unreg");
146    safe_attach!(skel.progs.on_sched_switch, "sched_switch");
147    safe_attach!(skel.progs.on_sched_wakeup, "sched_wakeup");
148    safe_attach!(skel.progs.on_sched_wakeup_new, "sched_wakeup_new");
149    safe_attach!(skel.progs.on_sched_waking, "sched_waking");
150    safe_attach!(skel.progs.on_sched_migrate_task, "sched_migrate_task");
151    safe_attach!(skel.progs.on_sched_fork, "sched_fork");
152    safe_attach!(skel.progs.on_sched_exec, "sched_exec");
153    safe_attach!(skel.progs.on_sched_exit, "sched_exit");
154
155    // 6.13 compatibility probes
156    if compat::ksym_exists("scx_bpf_dsq_insert_vtime")? {
157        safe_attach!(skel.progs.scx_insert_vtime, "scx_insert_vtime");
158        safe_attach!(skel.progs.scx_insert, "scx_insert");
159        safe_attach!(skel.progs.scx_dsq_move, "scx_dsq_move");
160        safe_attach!(skel.progs.scx_dsq_move_set_vtime, "scx_dsq_move_set_vtime");
161        safe_attach!(skel.progs.scx_dsq_move_set_slice, "scx_dsq_move_set_slice");
162    } else {
163        safe_attach!(skel.progs.scx_dispatch, "scx_dispatch");
164        safe_attach!(skel.progs.scx_dispatch_vtime, "scx_dispatch_vtime");
165        safe_attach!(
166            skel.progs.scx_dispatch_from_dsq_set_vtime,
167            "scx_dispatch_from_dsq_set_vtime"
168        );
169        safe_attach!(
170            skel.progs.scx_dispatch_from_dsq_set_slice,
171            "scx_dispatch_from_dsq_set_slice"
172        );
173        safe_attach!(skel.progs.scx_dispatch_from_dsq, "scx_dispatch_from_dsq");
174    }
175
176    // Optional probes
177    safe_attach!(skel.progs.on_cpuhp_enter, "cpuhp_enter");
178    safe_attach!(skel.progs.on_cpuhp_exit, "cpuhp_exit");
179
180    // If no links were successfully attached and we're not root, provide helpful guidance
181    if links.is_empty() && !is_root() {
182        warnings.extend(get_capability_warning_message());
183    }
184
185    Ok((links, warnings))
186}
187
188fn run_trace(trace_args: &TraceArgs) -> Result<()> {
189    // Trace function always requires root privileges
190    if !is_root() {
191        return Err(anyhow!(
192            "Trace functionality requires root privileges. Please run as root"
193        ));
194    }
195
196    TermLogger::init(
197        match trace_args.verbose {
198            0 => simplelog::LevelFilter::Info,
199            1 => simplelog::LevelFilter::Debug,
200            _ => simplelog::LevelFilter::Trace,
201        },
202        SimplelogConfig::default(),
203        TerminalMode::Mixed,
204        ColorChoice::Auto,
205    )?;
206
207    let mut kprobe_events = available_kprobe_events()?;
208    kprobe_events.sort();
209    search::sorted_contains_all(&kprobe_events, &trace_args.kprobes)
210        .then_some(())
211        .ok_or_else(|| anyhow!("Invalid kprobe events"))?;
212
213    let config = Config::default_config();
214    let worker_threads = config.worker_threads() as usize;
215    tokio::runtime::Builder::new_multi_thread()
216        .enable_all()
217        .worker_threads(if worker_threads > 2 {
218            worker_threads
219        } else {
220            4
221        })
222        .build()
223        .unwrap()
224        .block_on(async {
225            let (action_tx, mut action_rx) = mpsc::unbounded_channel();
226
227            // Set up the BPF skel and publisher
228            let mut open_object = MaybeUninit::uninit();
229            let mut builder = BpfSkelBuilder::default();
230            if trace_args.verbose > 2 {
231                builder.obj_builder.debug(true);
232            }
233
234            let skel = builder.open(&mut open_object)?;
235            compat::cond_kprobe_enable("gpu_memory_total", &skel.progs.on_gpu_memory_total)?;
236            compat::cond_kprobe_enable("hw_pressure_update", &skel.progs.on_hw_pressure_update)?;
237            compat::cond_tracepoint_enable("sched:sched_process_wait", &skel.progs.on_sched_wait)?;
238            compat::cond_tracepoint_enable("sched:sched_process_hang", &skel.progs.on_sched_hang)?;
239
240            // Load the BPF skeleton (no graceful handling for trace mode - requires root)
241            let mut skel = skel.load()?;
242            skel.maps.data_data.as_mut().unwrap().enable_bpf_events = false;
243
244            // Attach programs (no graceful handling for trace mode - requires root)
245            let mut links = vec![
246                skel.progs.on_sched_cpu_perf.attach()?,
247                skel.progs.scx_sched_reg.attach()?,
248                skel.progs.scx_sched_unreg.attach()?,
249                skel.progs.on_sched_switch.attach()?,
250                skel.progs.on_sched_wakeup.attach()?,
251                skel.progs.on_sched_wakeup_new.attach()?,
252                skel.progs.on_sched_waking.attach()?,
253                skel.progs.on_sched_migrate_task.attach()?,
254                skel.progs.on_sched_fork.attach()?,
255                skel.progs.on_sched_exec.attach()?,
256                skel.progs.on_sched_exit.attach()?,
257            ];
258
259            // 6.13 compatibility
260            if compat::ksym_exists("scx_bpf_dsq_insert_vtime")? {
261                if let Ok(link) = skel.progs.scx_insert_vtime.attach() {
262                    links.push(link);
263                }
264                if let Ok(link) = skel.progs.scx_insert.attach() {
265                    links.push(link);
266                }
267                if let Ok(link) = skel.progs.scx_dsq_move.attach() {
268                    links.push(link);
269                }
270                if let Ok(link) = skel.progs.scx_dsq_move_set_vtime.attach() {
271                    links.push(link);
272                }
273                if let Ok(link) = skel.progs.scx_dsq_move_set_slice.attach() {
274                    links.push(link);
275                }
276            } else {
277                if let Ok(link) = skel.progs.scx_dispatch.attach() {
278                    links.push(link);
279                }
280                if let Ok(link) = skel.progs.scx_dispatch_vtime.attach() {
281                    links.push(link);
282                }
283                if let Ok(link) = skel.progs.scx_dispatch_from_dsq_set_vtime.attach() {
284                    links.push(link);
285                }
286                if let Ok(link) = skel.progs.scx_dispatch_from_dsq_set_slice.attach() {
287                    links.push(link);
288                }
289                if let Ok(link) = skel.progs.scx_dispatch_from_dsq.attach() {
290                    links.push(link);
291                }
292            }
293            if let Ok(link) = skel.progs.on_cpuhp_enter.attach() {
294                links.push(link);
295            }
296            if let Ok(link) = skel.progs.on_cpuhp_exit.attach() {
297                links.push(link);
298            }
299
300            let bpf_publisher = BpfEventActionPublisher::new(action_tx.clone());
301
302            // Set up the event buffer
303            let mut event_rbb = RingBufferBuilder::new();
304            let mut edm = EventDispatchManager::new(None, None);
305            edm.register_bpf_handler(Box::new(bpf_publisher));
306            let event_handler = move |data: &[u8]| {
307                let mut event = bpf_event::default();
308                plain::copy_from_bytes(&mut event, data).expect("Event data buffer was too short");
309                let _ = edm.on_event(&event);
310                0
311            };
312            event_rbb.add(&skel.maps.events, event_handler)?;
313            let event_rb = event_rbb.build()?;
314
315            // Set up the background threads
316            let shutdown = Arc::new(AtomicBool::new(false));
317            let stop_poll = shutdown.clone();
318            let stop_stats = shutdown.clone();
319
320            let mut handles = Vec::new();
321            handles.push(tokio::spawn(async move {
322                loop {
323                    let _ = event_rb.poll(Duration::from_millis(1));
324                    if stop_poll.load(Ordering::Relaxed) {
325                        // Flush the ring buffer to ensure all events are processed
326                        let _ = event_rb.consume();
327                        debug!("polling stopped");
328                        break;
329                    }
330                }
331            }));
332
333            if trace_args.system_stats {
334                let mut cpu_stat_tracker = CpuStatTracker::default();
335                let mut mem_stats = MemStatSnapshot::default();
336                let mut system = System::new_all();
337                let action_tx_clone = action_tx.clone();
338
339                handles.push(tokio::spawn(async move {
340                    loop {
341                        if stop_stats.load(Ordering::Relaxed) {
342                            break;
343                        }
344                        let ts = get_clock_value(libc::CLOCK_BOOTTIME);
345
346                        cpu_stat_tracker
347                            .update(&mut system)
348                            .expect("Failed to update cpu stats");
349
350                        mem_stats.update().expect("Failed to update mem stats");
351
352                        let sys_stat_action = Action::SystemStat(SystemStatAction {
353                            ts,
354                            cpu_data_prev: cpu_stat_tracker.prev.clone(),
355                            cpu_data_current: cpu_stat_tracker.current.clone(),
356                            mem_info: mem_stats.clone(),
357                        });
358                        action_tx_clone
359                            .send(sys_stat_action)
360                            .expect("Failed to send CpuStat action");
361
362                        tokio::time::sleep(Duration::from_millis(100)).await;
363                    }
364                }));
365            }
366
367            let trace_file_prefix = config.trace_file_prefix().to_string();
368            let trace_file = trace_args.output_file.clone();
369            let mut trace_manager = PerfettoTraceManager::new(trace_file_prefix, None);
370
371            info!("starting trace for {}ms", trace_args.trace_ms);
372            trace_manager.start()?;
373            let mut tracer = Tracer::new(skel);
374            tracer.trace(&trace_args.kprobes)?;
375
376            handles.push(tokio::spawn(async move {
377                let mut count = 0;
378                loop {
379                    let action = action_rx.recv().await;
380                    if let Some(a) = action {
381                        count += 1;
382                        trace_manager
383                            .on_action(&a)
384                            .expect("Action should have been resolved");
385                    } else {
386                        trace_manager.stop(trace_file, None).unwrap();
387                        info!("trace file compiled, collected {count} events");
388                        break;
389                    }
390                }
391            }));
392            tokio::time::sleep(Duration::from_millis(trace_args.trace_ms)).await;
393
394            // 1) set the shutdown variable to stop background tokio threads
395            // 2) next, drop the links to detach the attached BPF programs
396            // 3) drop the action_tx to ensure action_rx closes
397            // 4) wait for the completion of the trace file generation to complete
398            shutdown.store(true, Ordering::Relaxed);
399            tracer.clear_links()?;
400            drop(links);
401            drop(action_tx);
402            info!("generating trace");
403            let results = join_all(handles).await;
404            for result in results {
405                if let Err(e) = result {
406                    eprintln!("Task panicked: {e}");
407                }
408            }
409
410            let stats = tracer.stats()?;
411            info!("{stats:?}");
412
413            Ok(())
414        })
415}
416
417fn run_tui(tui_args: &TuiArgs) -> Result<()> {
418    if let Ok(log_path) = std::env::var("RUST_LOG_PATH") {
419        let log_level = match std::env::var("RUST_LOG") {
420            Ok(v) => LevelFilter::from_str(&v)?,
421            Err(_) => LevelFilter::Info,
422        };
423
424        WriteLogger::init(
425            log_level,
426            simplelog::Config::default(),
427            File::create(log_path)?,
428        )?;
429
430        log_panics::Config::new()
431            .backtrace_mode(log_panics::BacktraceMode::Resolved)
432            .install_panic_hook();
433    };
434
435    let config = Config::merge([
436        Config::from(tui_args.clone()),
437        Config::load_or_default().expect("Failed to load config or load default config"),
438    ]);
439    let keymap = config.active_keymap.clone();
440
441    tokio::runtime::Builder::new_multi_thread()
442        .enable_all()
443        .worker_threads(config.worker_threads() as usize)
444        .build()
445        .unwrap()
446        .block_on(async {
447            // Declare open_object at the very beginning so it lives for the entire async block
448            let mut open_object = MaybeUninit::uninit();
449
450            let (action_tx, mut action_rx) = mpsc::unbounded_channel();
451
452            // Check capabilities early to determine if we can run with BPF functionality
453            let has_bpf_cap = check_bpf_capability();
454            let mut capability_warnings = Vec::new();
455            let mut _bpf_enabled = false;
456            let mut links = Vec::new();
457            let mut event_rb_opt = None;
458            let mut skel_opt = None;
459
460            if has_bpf_cap {
461                // Try to initialize BPF components
462                let mut builder = BpfSkelBuilder::default();
463                if config.debug() {
464                    builder.obj_builder.debug(true);
465                }
466                let bpf_publisher = BpfEventActionPublisher::new(action_tx.clone());
467                let mut edm = EventDispatchManager::new(None, None);
468                edm.register_bpf_handler(Box::new(bpf_publisher));
469
470                // Try to open the BPF skeleton with graceful error handling
471                match builder.open(&mut open_object) {
472                    Ok(mut skel) => {
473                        skel.maps.rodata_data.as_mut().unwrap().long_tail_tracing_min_latency_ns =
474                            tui_args.experimental_long_tail_tracing_min_latency_ns;
475
476                        let _map_handle = if tui_args.layered {
477                            skel.maps.rodata_data.as_mut().unwrap().layered = true;
478                            action_tx.send(Action::UpdateColVisibility(UpdateColVisibilityAction {
479                                table: "Process".to_string(),
480                                col: "Layer ID".to_string(),
481                                visible: true,
482                            }))?;
483                            action_tx.send(Action::UpdateColVisibility(UpdateColVisibilityAction {
484                                table: "Thread".to_string(),
485                                col: "Layer ID".to_string(),
486                                visible: true,
487                            }))?;
488                            match layered_util::attach_to_existing_map("task_ctxs", &mut skel.maps.task_ctxs) {
489                                Ok(handle) => Some(handle),
490                                Err(e) => {
491                                    capability_warnings.push(format!("Failed to attach to layered map: {e}"));
492                                    None
493                                }
494                            }
495                        } else {
496                            None
497                        };
498
499                        if let Err(e) = compat::cond_kprobe_enable("gpu_memory_total", &skel.progs.on_gpu_memory_total) {
500                            capability_warnings.push(format!("Failed to enable gpu_memory_total kprobe: {e}"));
501                        }
502                        if let Err(e) = compat::cond_kprobe_enable("hw_pressure_update", &skel.progs.on_hw_pressure_update) {
503                            capability_warnings.push(format!("Failed to enable hw_pressure_update kprobe: {e}"));
504                        }
505                        if let Err(e) = compat::cond_tracepoint_enable("sched:sched_process_wait", &skel.progs.on_sched_wait) {
506                            capability_warnings.push(format!("Failed to enable sched_process_wait tracepoint: {e}"));
507                        }
508                        if let Err(e) = compat::cond_tracepoint_enable("sched:sched_process_hang", &skel.progs.on_sched_hang) {
509                            capability_warnings.push(format!("Failed to enable sched_process_hang tracepoint: {e}"));
510                        }
511
512                        // Try to load the BPF skeleton
513                        match skel.load() {
514                            Ok(mut loaded_skel) => {
515                                let (skel_links, attach_warnings) = attach_progs(&mut loaded_skel)?;
516                                links = skel_links;
517                                capability_warnings.extend(attach_warnings);
518
519                                if !links.is_empty() || is_root() {
520                                    // Only run scxtop_init if we have some BPF functionality
521                                    if let Err(e) = loaded_skel.progs.scxtop_init.test_run(ProgramInput::default()) {
522                                        capability_warnings.push(format!("Failed to initialize scxtop BPF program: {e}"));
523                                    }
524                                }
525
526                                // Set up event ring buffer if we have any attached programs
527                                if !links.is_empty() {
528                                    let mut event_rbb = RingBufferBuilder::new();
529                                    let event_handler = move |data: &[u8]| {
530                                        let mut event = bpf_event::default();
531                                        plain::copy_from_bytes(&mut event, data).expect("Event data buffer was too short");
532                                        let _ = edm.on_event(&event);
533                                        0
534                                    };
535                                    if let Err(e) = event_rbb.add(&loaded_skel.maps.events, event_handler) {
536                                        capability_warnings.push(format!("Failed to add event handler: {e}"));
537                                    } else {
538                                        match event_rbb.build() {
539                                            Ok(event_rb) => {
540                                                event_rb_opt = Some(event_rb);
541                                                _bpf_enabled = true;
542                                            }
543                                            Err(e) => {
544                                                capability_warnings.push(format!("Failed to build event ring buffer: {e}"));
545                                            }
546                                        }
547                                    }
548                                }
549
550                                skel_opt = Some(loaded_skel);
551                            }
552                            Err(e) => {
553                                if is_root() {
554                                    return Err(anyhow!("Failed to load BPF skeleton (running as root): {e}"));
555                                } else {
556                                    capability_warnings.push(format!("Failed to load BPF skeleton: {e}"));
557                                    capability_warnings.extend(get_capability_warning_message());
558                                }
559                            }
560                        }
561                    }
562                    Err(e) => {
563                        if is_root() {
564                            return Err(anyhow!("Failed to open BPF skeleton (running as root): {e}"));
565                        } else {
566                            capability_warnings.push(format!("Failed to open BPF skeleton: {e}"));
567                            capability_warnings.extend(get_capability_warning_message());
568                        }
569                    }
570                }
571            } else {
572                // No BPF capabilities detected
573                capability_warnings.extend(get_capability_warning_message());
574            }
575
576            // Handle experimental long tail tracing if enabled and we have a skeleton
577            if tui_args.experimental_long_tail_tracing {
578                if let Some(ref mut skel) = skel_opt {
579                    skel.maps.data_data.as_mut().unwrap().trace_duration_ns = config.trace_duration_ns();
580                    skel.maps.data_data.as_mut().unwrap().trace_warmup_ns = config.trace_warmup_ns();
581
582                    let binary = tui_args
583                        .experimental_long_tail_tracing_binary
584                        .clone()
585                        .unwrap();
586                    let symbol = tui_args
587                        .experimental_long_tail_tracing_symbol
588                        .clone()
589                        .unwrap();
590
591                    match skel.progs.long_tail_tracker_exit.attach_uprobe_with_opts(
592                        -1, /* pid, -1 == all */
593                        binary.clone(),
594                        0,
595                        UprobeOpts {
596                            retprobe: true,
597                            func_name: Some(symbol.clone()),
598                            ..Default::default()
599                        },
600                    ) {
601                        Ok(link) => links.push(link),
602                        Err(e) => capability_warnings.push(format!("Failed to attach long tail tracker exit: {e}"))
603                    }
604
605                    match skel.progs.long_tail_tracker_entry.attach_uprobe_with_opts(
606                        -1, /* pid, -1 == all */
607                        binary.clone(),
608                        0,
609                        UprobeOpts {
610                            retprobe: false,
611                            func_name: Some(symbol.clone()),
612                            ..Default::default()
613                        },
614                    ) {
615                        Ok(link) => links.push(link),
616                        Err(e) => capability_warnings.push(format!("Failed to attach long tail tracker entry: {e}"))
617                    }
618                } else {
619                    capability_warnings.push("Long tail tracing requested but BPF skeleton not available".to_string());
620                }
621            }
622
623            let mut tui = Tui::new(keymap.clone(), config.tick_rate_ms(), config.frame_rate_ms())?;
624            let scheduler = read_file_string(SCHED_NAME_PATH).unwrap_or("".to_string());
625
626            // Create app with or without BPF skeleton
627            let mut app = if let Some(skel) = skel_opt {
628                App::new(
629                    config,
630                    scheduler,
631                    100,
632                    tui_args.process_id,
633                    tui_args.layered,
634                    action_tx.clone(),
635                    skel,
636                )?
637            } else {
638                // Create app without BPF functionality
639                App::new_without_bpf(
640                    config,
641                    scheduler,
642                    100,
643                    tui_args.process_id,
644                    tui_args.layered,
645                    action_tx.clone(),
646                )?
647            };
648
649            // Pass warnings to the app if any exist
650            if !capability_warnings.is_empty() {
651                app.set_capability_warnings(capability_warnings);
652            }
653
654            tui.enter()?;
655
656            // Start BPF event polling only if we have an event ring buffer
657            let shutdown = app.should_quit.clone();
658            if let Some(event_rb) = event_rb_opt {
659                tokio::spawn(async move {
660                    loop {
661                        let _ = event_rb.poll(Duration::from_millis(1));
662                        if shutdown.load(Ordering::Relaxed) {
663                            break;
664                        }
665                    }
666                });
667            }
668
669            if tui_args.mangoapp_tracing {
670                let stop_mangoapp = app.should_quit.clone();
671                let mangoapp_path = CString::new(tui_args.mangoapp_path.clone()).unwrap();
672                let poll_intvl_ms = tui_args.mangoapp_poll_intvl_ms;
673                let tx = action_tx.clone();
674                tokio::spawn(async move {
675                    poll_mangoapp(
676                        mangoapp_path,
677                        poll_intvl_ms,
678                        tx,
679                        stop_mangoapp,
680                    )
681                    .await
682                });
683            }
684
685            loop {
686                tokio::select! {
687                    ev = tui.next() => {
688                        let ev = ev?;
689                        match ev {
690                            Event::Quit => { action_tx.send(Action::Quit)?; },
691                            Event::Tick => action_tx.send(Action::Tick)?,
692                            Event::TickRateChange(tick_rate_ms) => action_tx.send(
693                                Action::TickRateChange(std::time::Duration::from_millis(tick_rate_ms)),
694                            )?,
695                            Event::Render => {
696                                if app.should_quit.load(Ordering::Relaxed) {
697                                    break;
698                                }
699                                if app.state() != AppState::Pause {
700                                    tui.draw(|f| app.render(f).expect("Failed to render application"))?;
701                                }
702                            }
703                            Event::Key(_) => {
704                                let action = get_action(&app, &keymap, ev);
705                                action_tx.send(action)?;
706                            }
707                            _ => {}
708                    }}
709
710                    ac = action_rx.recv() => {
711                        let ac = ac.ok_or(anyhow!("actions channel closed"))?;
712                        app.handle_action(&ac)?;
713                    }
714                }
715            }
716            tui.exit()?;
717            drop(links);
718
719            Ok(())
720        })
721}
722
723fn main() -> Result<()> {
724    let args = Cli::parse();
725
726    match &args.command.unwrap_or(Commands::Tui(args.tui)) {
727        Commands::Tui(tui_args) => {
728            run_tui(tui_args)?;
729        }
730        Commands::Trace(trace_args) => {
731            run_trace(trace_args)?;
732        }
733        Commands::GenerateCompletions { shell, output } => {
734            generate_completions(Cli::command(), *shell, output.clone())
735                .unwrap_or_else(|_| panic!("Failed to generate completions for {shell}"));
736        }
737    }
738    Ok(())
739}