scx_utils/
user_exit_info.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.
5use crate::bindings;
6use crate::compat;
7use anyhow::Result;
8use anyhow::bail;
9use std::ffi::CStr;
10use std::os::raw::c_char;
11use std::sync::Mutex;
12
13pub struct UeiDumpPtr {
14    pub ptr: *const c_char,
15}
16unsafe impl Send for UeiDumpPtr {}
17
18pub static UEI_DUMP_PTR_MUTEX: Mutex<UeiDumpPtr> = Mutex::new(UeiDumpPtr {
19    ptr: std::ptr::null(),
20});
21
22lazy_static::lazy_static! {
23    pub static ref SCX_ECODE_RSN_HOTPLUG: u64 =
24    compat::read_enum("scx_exit_code", "SCX_ECODE_RSN_HOTPLUG").unwrap_or(0);
25}
26
27lazy_static::lazy_static! {
28    pub static ref SCX_ECODE_ACT_RESTART: u64 =
29    compat::read_enum("scx_exit_code", "SCX_ECODE_ACT_RESTART").unwrap_or(0);
30}
31
32pub enum ScxExitKind {
33    None = bindings::scx_exit_kind_SCX_EXIT_NONE as isize,
34    Done = bindings::scx_exit_kind_SCX_EXIT_DONE as isize,
35    Unreg = bindings::scx_exit_kind_SCX_EXIT_UNREG as isize,
36    UnregBPF = bindings::scx_exit_kind_SCX_EXIT_UNREG_BPF as isize,
37    UnregKern = bindings::scx_exit_kind_SCX_EXIT_UNREG_KERN as isize,
38    SysRq = bindings::scx_exit_kind_SCX_EXIT_SYSRQ as isize,
39    Error = bindings::scx_exit_kind_SCX_EXIT_ERROR as isize,
40    ErrorBPF = bindings::scx_exit_kind_SCX_EXIT_ERROR_BPF as isize,
41    ErrorStall = bindings::scx_exit_kind_SCX_EXIT_ERROR_STALL as isize,
42}
43
44pub enum ScxConsts {
45    ExitDumpDflLen = bindings::scx_consts_SCX_EXIT_DUMP_DFL_LEN as isize,
46}
47
48/// Takes a reference to C struct user_exit_info and reads it into
49/// UserExitInfo. See UserExitInfo.
50#[macro_export]
51macro_rules! uei_read {
52    ($skel: expr, $uei:ident) => {{
53        scx_utils::paste! {
54            let bpf_uei = $skel.maps.data_data.$uei;
55            let bpf_dump = scx_utils::UEI_DUMP_PTR_MUTEX.lock().unwrap().ptr;
56            let exit_code_ptr = match scx_utils::compat::struct_has_field("scx_exit_info", "exit_code") {
57                Ok(true) => &bpf_uei.exit_code as *const _,
58                _ => std::ptr::null(),
59            };
60
61            scx_utils::UserExitInfo::new(
62                &bpf_uei.kind as *const _,
63                exit_code_ptr,
64                bpf_uei.reason.as_ptr() as *const _,
65                bpf_uei.msg.as_ptr() as *const _,
66                bpf_dump,
67            )
68        }
69    }};
70}
71
72/// Resize debug dump area according to ops.exit_dump_len. If this macro is
73/// not called, debug dump area is not allocated and debug dump won't be
74/// printed out.
75#[macro_export]
76macro_rules! uei_set_size {
77    ($skel: expr, $ops: ident, $uei:ident) => {{
78        scx_utils::paste! {
79            let len = match $skel.struct_ops.$ops().exit_dump_len {
80                0 => scx_utils::ScxConsts::ExitDumpDflLen as u32,
81                v => v,
82            };
83            $skel.maps.rodata_data.[<$uei _dump_len>] = len;
84            $skel.maps.[<data_ $uei _dump>].set_value_size(len).unwrap();
85
86            let mut ptr = scx_utils::UEI_DUMP_PTR_MUTEX.lock().unwrap();
87            *ptr = scx_utils::UeiDumpPtr { ptr:
88                       $skel
89                       .maps
90                       .[<data_ $uei _dump>]
91                       .initial_value()
92                       .unwrap()
93                       .as_ptr() as *const _,
94            };
95        }
96    }};
97}
98
99/// Takes a reference to C struct user_exit_info and test whether the BPF
100/// scheduler has exited. See UserExitInfo.
101#[macro_export]
102macro_rules! uei_exited {
103    ($skel: expr, $uei:ident) => {{
104        let bpf_uei = $skel.maps.data_data.uei;
105        (unsafe { std::ptr::read_volatile(&bpf_uei.kind as *const _) } != 0)
106    }};
107}
108
109/// Takes a reference to C struct user_exit_info, reads, invokes
110/// UserExitInfo::report() on and then returns Ok(uei). See UserExitInfo.
111#[macro_export]
112macro_rules! uei_report {
113    ($skel: expr, $uei:ident) => {{
114        let uei = scx_utils::uei_read!($skel, $uei);
115        uei.report().and_then(|_| Ok(uei))
116    }};
117}
118
119/// Rust counterpart of C struct user_exit_info.
120#[derive(Debug, Default)]
121pub struct UserExitInfo {
122    /// The C enum scx_exit_kind value. Test against ScxExitKind. None-zero
123    /// value indicates that the BPF scheduler has exited.
124    kind: i32,
125    exit_code: i64,
126    reason: Option<String>,
127    msg: Option<String>,
128    dump: Option<String>,
129}
130
131impl UserExitInfo {
132    /// Create UserExitInfo from C struct user_exit_info. Each scheduler
133    /// implementation creates its own Rust binding for the C struct
134    /// user_exit_info, so we can't take the type directly. Instead, this
135    /// method takes each member field. Use the macro uei_read!() on the C
136    /// type which then calls this method with the individual fields.
137    pub fn new(
138        kind_ptr: *const i32,
139        exit_code_ptr: *const i64,
140        reason_ptr: *const c_char,
141        msg_ptr: *const c_char,
142        dump_ptr: *const c_char,
143    ) -> Self {
144        let kind = unsafe { std::ptr::read_volatile(kind_ptr) };
145        let exit_code = if exit_code_ptr.is_null() {
146            0
147        } else {
148            unsafe { std::ptr::read_volatile(exit_code_ptr) }
149        };
150
151        let (reason, msg) = (
152            Some(
153                unsafe { CStr::from_ptr(reason_ptr) }
154                    .to_str()
155                    .expect("Failed to convert reason to string")
156                    .to_string(),
157            )
158            .filter(|s| !s.is_empty()),
159            Some(
160                unsafe { CStr::from_ptr(msg_ptr) }
161                    .to_str()
162                    .expect("Failed to convert msg to string")
163                    .to_string(),
164            )
165            .filter(|s| !s.is_empty()),
166        );
167
168        let dump = if dump_ptr.is_null() {
169            None
170        } else {
171            Some(
172                unsafe { CStr::from_ptr(dump_ptr) }
173                    .to_str()
174                    .expect("Failed to convert msg to string")
175                    .to_string(),
176            )
177            .filter(|s| !s.is_empty())
178        };
179
180        Self {
181            kind,
182            exit_code,
183            reason,
184            msg,
185            dump,
186        }
187    }
188
189    /// Print out the exit message to stderr if the exit was normal. After
190    /// an error exit, it throws an error containing the exit message
191    /// instead. If debug dump exists, it's always printed to stderr.
192    pub fn report(&self) -> Result<()> {
193        if self.kind == 0 {
194            return Ok(());
195        }
196
197        if let Some(dump) = &self.dump {
198            eprintln!("\nDEBUG DUMP");
199            eprintln!(
200                "================================================================================\n"
201            );
202            eprintln!("{}", dump);
203            eprintln!(
204                "================================================================================\n"
205            );
206        }
207
208        let why = match (&self.reason, &self.msg) {
209            (Some(reason), None) => format!("EXIT: {}", reason),
210            (Some(reason), Some(msg)) => format!("EXIT: {} ({})", reason, msg),
211            _ => "<UNKNOWN>".into(),
212        };
213
214        if self.kind <= ScxExitKind::UnregKern as i32 {
215            eprintln!("{}", why);
216            Ok(())
217        } else {
218            bail!("{}", why)
219        }
220    }
221
222    /// Return the exit code that the scheduler gracefully exited with. This
223    /// only applies when the BPF scheduler exits with scx_bpf_exit(), i.e. kind
224    /// ScxExitKind::UnregBPF.
225    pub fn exit_code(&self) -> Option<i64> {
226        if self.kind == ScxExitKind::UnregBPF as i32 || self.kind == ScxExitKind::UnregKern as i32 {
227            Some(self.exit_code)
228        } else {
229            None
230        }
231    }
232
233    /// Test whether the BPF scheduler requested restart.
234    pub fn should_restart(&self) -> bool {
235        match self.exit_code() {
236            Some(ecode) => (ecode & *SCX_ECODE_ACT_RESTART as i64) != 0,
237            _ => false,
238        }
239    }
240}