1use anyhow::Result;
2use anyhow::{anyhow, Context};
3use log::{info, warn};
4use nix::sys::resource::{getrlimit, setrlimit, Resource, RLIM_INFINITY};
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) => {
48 if let Some(ioe) = e.downcast_ref::<std::io::Error>() {
49 info!("Connection to stats_server failed ({ioe})");
50 } else {
51 warn!("Error handling stats_server result: {e}");
52 }
53 sleep(Duration::from_secs(1));
54 break;
55 }
56 };
57 output(stats)?;
58 sleep(intv);
59 }
60 }
61
62 Ok(())
63}
64
65pub fn try_set_rlimit_infinity() {
66 if setrlimit(Resource::RLIMIT_MEMLOCK, RLIM_INFINITY, RLIM_INFINITY).is_err() {
69 if let Ok((soft, hard)) = getrlimit(Resource::RLIMIT_MEMLOCK) {
72 warn!("Current MEMLOCK limit: soft={soft}, hard={hard}");
73 } else {
74 warn!("Cannot change or query MEMLOCK limit");
75 }
76 }
77}
78
79pub fn read_from_file<T>(path: &Path) -> Result<T>
86where
87 T: std::str::FromStr,
88 T::Err: std::error::Error + Send + Sync + 'static,
89{
90 let val = std::fs::read_to_string(path)
91 .with_context(|| format!("Failed to open or read file: {}", path.display()))?;
92
93 let val = val.trim_end_matches('\0').trim();
94
95 val.parse::<T>()
96 .with_context(|| format!("Failed to parse content '{}' from {}", val, path.display()))
97}
98
99pub fn read_file_usize_vec(path: &Path, separator: char) -> Result<Vec<usize>> {
100 let val = std::fs::read_to_string(path)?;
101 let val = val.trim_end_matches('\0');
102
103 val.split(separator)
104 .map(|s| {
105 s.trim()
106 .parse::<usize>()
107 .map_err(|_| anyhow!("Failed to parse '{}' as usize", s))
108 })
109 .collect::<Result<Vec<usize>>>()
110}
111
112pub fn read_file_byte(path: &Path) -> Result<usize> {
113 let val = std::fs::read_to_string(path)?;
114 let val = val.trim_end_matches('\0');
115 let val = val.trim();
116
117 if let Some(sval) = val.strip_suffix("K") {
119 let byte = sval.parse::<usize>()?;
120 return Ok(byte * 1024);
121 }
122 if let Some(sval) = val.strip_suffix("M") {
123 let byte = sval.parse::<usize>()?;
124 return Ok(byte * 1024 * 1024);
125 }
126 if let Some(sval) = val.strip_suffix("G") {
127 let byte = sval.parse::<usize>()?;
128 return Ok(byte * 1024 * 1024 * 1024);
129 }
130
131 let byte = val.parse::<usize>()?;
132 Ok(byte)
133}
134
135pub fn normalize_load_metric(metric: f64) -> f64 {
154 metric / 100.0
155}
156
157pub fn find_best_split_size(total_items: usize, min_size: usize, max_size: usize) -> usize {
172 if total_items <= min_size {
173 return total_items;
174 }
175
176 let mut optimal_size = min_size;
177
178 for size in min_size..=max_size {
179 if total_items % size == 0 {
180 optimal_size = size;
181 break;
182 }
183 if total_items % size < total_items % optimal_size {
185 optimal_size = size;
186 }
187 }
188
189 optimal_size
190}