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 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
100    match val.trim().parse::<T>() {
101        Ok(parsed) => Ok(parsed),
102        Err(_) => {
103            bail!("Failed to parse content '{}' from {:?}", val.trim(), path);
104        }
105    }
106}
107
108pub fn read_file_usize_vec(path: &Path, separator: char) -> Result<Vec<usize>> {
109    let val = std::fs::read_to_string(path)?;
110
111    val.split(separator)
112        .map(|s| {
113            s.trim()
114                .parse::<usize>()
115                .map_err(|_| anyhow!("Failed to parse '{}' as usize", s))
116        })
117        .collect::<Result<Vec<usize>>>()
118}
119
120pub fn read_file_byte(path: &Path) -> Result<usize> {
121    let val = std::fs::read_to_string(path)?;
122    let val = val.trim();
123
124    // E.g., 10K, 10M, 10G, 10
125    if let Some(sval) = val.strip_suffix("K") {
126        let byte = sval.parse::<usize>()?;
127        return Ok(byte * 1024);
128    }
129    if let Some(sval) = val.strip_suffix("M") {
130        let byte = sval.parse::<usize>()?;
131        return Ok(byte * 1024 * 1024);
132    }
133    if let Some(sval) = val.strip_suffix("G") {
134        let byte = sval.parse::<usize>()?;
135        return Ok(byte * 1024 * 1024 * 1024);
136    }
137
138    let byte = val.parse::<usize>()?;
139    Ok(byte)
140}
141
142/* Load is reported as weight * duty cycle
143 *
144 * In the Linux kernel, EEDVF uses default weight = 1 s.t.
145 * load for a nice-0 thread runnable for time slice = 1
146 *
147 * To conform with cgroup weights convention, sched-ext uses
148 * the convention of default weight = 100 with the formula
149 * 100 * nice ^ 1.5. This means load for a nice-0 thread
150 * runnable for time slice = 100.
151 *
152 * To ensure we report load metrics consistently with the Linux
153 * kernel, we divide load by 100.0 prior to reporting metrics.
154 * This is also more intuitive for users since 1 CPU roughly
155 * means 1 unit of load.
156 *
157 * We only do this prior to reporting as its easier to work with
158 * weight as integers in BPF / userspace than floating point.
159 */
160pub fn normalize_load_metric(metric: f64) -> f64 {
161    metric / 100.0
162}