scx_utils/
misc.rs

1use anyhow::Result;
2use anyhow::{anyhow, bail};
3use libc;
4use log::{info, warn};
5use scx_stats::prelude::*;
6use serde::Deserialize;
7use std::path::Path;
8use std::thread::sleep;
9use std::time::Duration;
10
11pub fn monitor_stats<T>(
12    stats_args: &[(String, String)],
13    intv: Duration,
14    mut should_exit: impl FnMut() -> bool,
15    mut output: impl FnMut(T) -> Result<()>,
16) -> Result<()>
17where
18    T: for<'a> Deserialize<'a>,
19{
20    let mut retry_cnt: u32 = 0;
21
22    const RETRYABLE_ERRORS: [std::io::ErrorKind; 2] = [
23        std::io::ErrorKind::NotFound,
24        std::io::ErrorKind::ConnectionRefused,
25    ];
26
27    while !should_exit() {
28        let mut client = match StatsClient::new().connect() {
29            Ok(v) => v,
30            Err(e) => match e.downcast_ref::<std::io::Error>() {
31                Some(ioe) if RETRYABLE_ERRORS.contains(&ioe.kind()) => {
32                    if retry_cnt == 1 {
33                        info!("Stats server not available, retrying...");
34                    }
35                    retry_cnt += 1;
36                    sleep(Duration::from_secs(1));
37                    continue;
38                }
39                _ => Err(e)?,
40            },
41        };
42        retry_cnt = 0;
43
44        while !should_exit() {
45            let stats = match client.request::<T>("stats", stats_args.to_owned()) {
46                Ok(v) => v,
47                Err(e) => match e.downcast_ref::<std::io::Error>() {
48                    Some(ioe) => {
49                        info!("Connection to stats_server failed ({})", ioe);
50                        sleep(Duration::from_secs(1));
51                        break;
52                    }
53                    None => {
54                        warn!("error on handling stats_server result {}", e);
55                        sleep(Duration::from_secs(1));
56                        break;
57                    }
58                },
59            };
60            output(stats)?;
61            sleep(intv);
62        }
63    }
64
65    Ok(())
66}
67
68pub fn set_rlimit_infinity() {
69    unsafe {
70        // Call setrlimit to set the locked-in-memory limit to unlimited.
71        let new_rlimit = libc::rlimit {
72            rlim_cur: libc::RLIM_INFINITY,
73            rlim_max: libc::RLIM_INFINITY,
74        };
75        let res = libc::setrlimit(libc::RLIMIT_MEMLOCK, &new_rlimit);
76        if res != 0 {
77            panic!("setrlimit failed with error code: {}", res);
78        }
79    };
80}
81
82/// Read a file and parse its content into the specified type.
83///
84/// Trims null and whitespace before parsing.
85///
86/// # Errors
87/// Returns an error if reading or parsing fails.
88pub fn read_from_file<T>(path: &Path) -> Result<T>
89where
90    T: std::str::FromStr,
91    T::Err: std::error::Error + Send + Sync + 'static,
92{
93    let val = match std::fs::read_to_string(path) {
94        Ok(val) => val,
95        Err(_) => {
96            bail!("Failed to open or read file {:?}", path);
97        }
98    };
99    let val = val.trim_end_matches('\0');
100
101    match val.trim().parse::<T>() {
102        Ok(parsed) => Ok(parsed),
103        Err(_) => {
104            bail!("Failed to parse content '{}' from {:?}", val.trim(), path);
105        }
106    }
107}
108
109pub fn read_file_usize_vec(path: &Path, separator: char) -> Result<Vec<usize>> {
110    let val = std::fs::read_to_string(path)?;
111    let val = val.trim_end_matches('\0');
112
113    val.split(separator)
114        .map(|s| {
115            s.trim()
116                .parse::<usize>()
117                .map_err(|_| anyhow!("Failed to parse '{}' as usize", s))
118        })
119        .collect::<Result<Vec<usize>>>()
120}
121
122pub fn read_file_byte(path: &Path) -> Result<usize> {
123    let val = std::fs::read_to_string(path)?;
124    let val = val.trim_end_matches('\0');
125    let val = val.trim();
126
127    // E.g., 10K, 10M, 10G, 10
128    if let Some(sval) = val.strip_suffix("K") {
129        let byte = sval.parse::<usize>()?;
130        return Ok(byte * 1024);
131    }
132    if let Some(sval) = val.strip_suffix("M") {
133        let byte = sval.parse::<usize>()?;
134        return Ok(byte * 1024 * 1024);
135    }
136    if let Some(sval) = val.strip_suffix("G") {
137        let byte = sval.parse::<usize>()?;
138        return Ok(byte * 1024 * 1024 * 1024);
139    }
140
141    let byte = val.parse::<usize>()?;
142    Ok(byte)
143}
144
145/* Load is reported as weight * duty cycle
146 *
147 * In the Linux kernel, EEDVF uses default weight = 1 s.t.
148 * load for a nice-0 thread runnable for time slice = 1
149 *
150 * To conform with cgroup weights convention, sched-ext uses
151 * the convention of default weight = 100 with the formula
152 * 100 * nice ^ 1.5. This means load for a nice-0 thread
153 * runnable for time slice = 100.
154 *
155 * To ensure we report load metrics consistently with the Linux
156 * kernel, we divide load by 100.0 prior to reporting metrics.
157 * This is also more intuitive for users since 1 CPU roughly
158 * means 1 unit of load.
159 *
160 * We only do this prior to reporting as its easier to work with
161 * weight as integers in BPF / userspace than floating point.
162 */
163pub fn normalize_load_metric(metric: f64) -> f64 {
164    metric / 100.0
165}