below_common/
util.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::cell::RefCell;
16use std::cell::RefMut;
17use std::io;
18use std::io::Read;
19use std::time::Duration;
20use std::time::SystemTime;
21use std::time::UNIX_EPOCH;
22
23/// This file contains various helpers
24use chrono::prelude::*;
25
26const BELOW_RC: &str = "/.config/below/belowrc";
27
28/// Execute an expression every n times. For example
29/// `every_n!(1 + 2, println!("I'm mod 3")` will print on the 1st,
30/// 4th, and so on calls.
31#[macro_export]
32macro_rules! every_n {
33    ($n:expr, $ex:expr) => {{
34        static COUNT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
35        let p = COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
36        if p % ($n) == 0 {
37            $ex
38        }
39    }};
40}
41pub use every_n;
42
43/// Convert `timestamp` from `SystemTime` to seconds since epoch
44pub fn get_unix_timestamp(timestamp: SystemTime) -> u64 {
45    timestamp
46        .duration_since(SystemTime::UNIX_EPOCH)
47        .expect("SystemTime before UNIX EPOCH!")
48        .as_secs()
49}
50
51/// Convert `timestamp` from seconds since epoch to `SystemTime`
52pub fn get_system_time(timestamp: u64) -> SystemTime {
53    UNIX_EPOCH + Duration::from_secs(timestamp)
54}
55
56fn convert(val: f64, base: f64, units: &[&'static str]) -> String {
57    if val < 1_f64 {
58        return format!("{:.1} {}", val, units[0]);
59    }
60    let exponent = std::cmp::min(
61        (val.ln() / base.ln()).floor() as i32,
62        (units.len() - 1) as i32,
63    );
64    let pretty_val = format!("{:.1}", val / base.powi(exponent))
65        .parse::<f64>()
66        .unwrap()
67        * 1_f64;
68    let unit = units[exponent as usize];
69    format!("{} {}", pretty_val, unit)
70}
71
72/// Convert `val` bytes into a human friendly string
73pub fn convert_bytes(val: f64) -> String {
74    const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
75    convert(val, 1024_f64, UNITS)
76}
77
78/// Convert `val` Hz into a human friendly string
79pub fn convert_freq(val: u64) -> String {
80    const UNITS: &[&str] = &["Hz", "kHz", "MHz", "GHz", "THz", "PHz", "EHz", "ZHz", "YHz"];
81    let val_f64 = val as f64;
82    convert(val_f64, 1000_f64, UNITS)
83}
84
85/// Convert `val` microseconds into a human friendly string. Largest unit used is seconds.
86pub fn convert_duration(val: u64) -> String {
87    const UNITS: &[&str] = &["us", "ms", "s"];
88    convert(val as f64, 1000_f64, UNITS)
89}
90
91pub fn get_prefix(collapsed: bool) -> &'static str {
92    if collapsed { "└+ " } else { "└─ " }
93}
94
95/// Fold a long string to a fixed size string and keep the front and back
96///
97/// The effective length of the string will be `width - 3` since this fn will use
98/// 3 '.'s to replace the omitted content. If the width is less than 3, it will
99/// return the original string. This function will also take a stop_filter closure
100/// as an indicator of where to split the string. Please note that, this function
101/// will take the minimal value of (width - 3)/2 and first index that hit the stop_filter
102/// as the front half cutting point.
103///
104/// # Arguments
105///
106/// * `val` -- The string that needs to be folded.
107/// * `width` -- The final target string length.
108/// * `start_idx` -- From which index should we start apply the stop_filter
109/// * `stop_filter` -- The first half will be cut at the first index that returns false
110///    after apply the filter if the index is less than (width - 3)/2
111pub fn fold_string<F>(val: &str, width: usize, start_idx: usize, stop_filter: F) -> String
112where
113    F: FnMut(char) -> bool,
114{
115    let str_len = val.len();
116    if start_idx >= str_len || val[start_idx..].len() <= width || width <= 3 {
117        return val.into();
118    }
119
120    let first_symbo_pos = val[start_idx..].find(stop_filter).unwrap_or(str_len) + 1;
121    let mid_str_len = (width - 3) >> 1;
122    let front_len = std::cmp::min(first_symbo_pos, mid_str_len);
123    let front_string = val[..front_len].to_string();
124    let back_string = val[str_len - width + front_len + 3..].to_string();
125    format!("{}...{}", front_string, back_string)
126}
127
128/// Convert system timestamp to human readable datetime.
129pub fn timestamp_to_datetime(timestamp: &i64) -> String {
130    let naive = NaiveDateTime::from_timestamp_opt(*timestamp, 0).unwrap();
131    let datetime = naive.and_utc();
132    datetime
133        .with_timezone(&Local)
134        .format("%Y-%m-%d %H:%M:%S")
135        .to_string()
136}
137
138/// Convert system time to human readable datetime.
139pub fn systemtime_to_datetime(system_time: SystemTime) -> String {
140    timestamp_to_datetime(&(get_unix_timestamp(system_time) as i64))
141}
142
143pub fn is_cpu_significant(v: f64) -> Option<cursive::theme::BaseColor> {
144    if v > 100.0 {
145        Some(cursive::theme::BaseColor::Red)
146    } else {
147        None
148    }
149}
150
151/// Get the belowrc filename.
152pub fn get_belowrc_filename() -> String {
153    format!(
154        "{}{}",
155        std::env::var("HOME").expect("Fail to obtain HOME env var"),
156        BELOW_RC
157    )
158}
159
160/// The dump section key for belowrc
161pub fn get_belowrc_dump_section_key() -> &'static str {
162    "dump"
163}
164
165/// The cmd section key for belowrc
166pub fn get_belowrc_cmd_section_key() -> &'static str {
167    "cmd"
168}
169
170/// The view section key for belowrc
171pub fn get_belowrc_view_section_key() -> &'static str {
172    "view"
173}
174
175pub fn read_kern_file_to_internal_buffer<R: Read>(
176    buffer: &RefCell<Vec<u8>>,
177    mut reader: R,
178) -> io::Result<RefMut<'_, str>> {
179    const BUFFER_CHUNK_SIZE: usize = 1 << 16;
180
181    let mut buffer = buffer.borrow_mut();
182    let mut total_read = 0;
183
184    loop {
185        let buf_len = buffer.len();
186        if buf_len < total_read + BUFFER_CHUNK_SIZE {
187            buffer.resize(buf_len + BUFFER_CHUNK_SIZE, 0);
188        }
189
190        match reader.read(&mut buffer[total_read..]) {
191            Ok(0) => break,
192            Ok(n) => {
193                total_read += n;
194                // n < BUFFER_CHUNK_SIZE does not indicate EOF because kernel
195                // may partially fill the buffer to avoid breaking the line.
196            }
197            Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
198            Err(e) => return Err(e),
199        }
200    }
201
202    RefMut::filter_map(buffer, |vec| {
203        std::str::from_utf8_mut(&mut vec[..total_read]).ok()
204    })
205    .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-8 data"))
206}
207
208#[cfg(test)]
209mod test {
210    use super::*;
211
212    #[test]
213    fn test_every_n() {
214        let mut v1 = Vec::new();
215        let mut v2 = Vec::new();
216        for i in 0..10 {
217            every_n!(2, v1.push(i));
218            every_n!(1 + 2, v2.push(i));
219        }
220        assert_eq!(v1, vec![0, 2, 4, 6, 8]);
221        assert_eq!(v2, vec![0, 3, 6, 9]);
222    }
223
224    #[test]
225    fn test_convert_bytes() {
226        // TODO(T118356932): This should really be 0 B
227        assert_eq!(convert_bytes(0.0), "0.0 B".to_owned());
228        assert_eq!(convert_bytes(1_024.0), "1 KB".to_owned());
229        assert_eq!(convert_bytes(1_023.0), "1023 B".to_owned());
230        assert_eq!(convert_bytes(1_076.0), "1.1 KB".to_owned());
231        assert_eq!(convert_bytes(10_239.0), "10 KB".to_owned());
232        assert_eq!(convert_bytes(1024_f64.powi(2)), "1 MB".to_owned());
233        // TODO(T118356932): This should really be 1 MB
234        assert_eq!(convert_bytes(1024_f64.powi(2) - 1.0), "1024 KB".to_owned());
235        // TODO(T118356932): This should really be 1 GB
236        assert_eq!(convert_bytes(1024_f64.powi(3) - 1.0), "1024 MB".to_owned());
237        assert_eq!(convert_bytes(1024_f64.powi(3)), "1 GB".to_owned());
238        assert_eq!(convert_bytes(1024_f64.powi(4)), "1 TB".to_owned());
239    }
240
241    #[test]
242    fn test_convert_freq() {
243        // TODO(T118356932): This should really be 0 Hz
244        assert_eq!(convert_freq(0), "0.0 Hz".to_owned());
245        assert_eq!(convert_freq(1_000), "1 kHz".to_owned());
246        assert_eq!(convert_freq(999), "999 Hz".to_owned());
247        assert_eq!(convert_freq(1_050), "1.1 kHz".to_owned());
248        assert_eq!(convert_freq(9_999), "10 kHz".to_owned());
249        assert_eq!(convert_freq(1_000_000), "1 MHz".to_owned());
250        // TODO(T118356932): This should really be 1 GHz
251        assert_eq!(convert_freq(999_950_000), "1000 MHz".to_owned());
252        assert_eq!(convert_freq(1_000_000_000), "1 GHz".to_owned());
253        assert_eq!(convert_freq(1_000_000_000_000), "1 THz".to_owned());
254    }
255}