rd_util/
lib.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2use anyhow::{anyhow, bail, Context, Result};
3use chrono::{DateTime, Local};
4use crossbeam::channel::Sender;
5use glob::glob;
6use log::{error, info, warn};
7use scan_fmt::scan_fmt;
8use serde::{Deserialize, Serialize};
9use simplelog as sl;
10use std::cell::RefCell;
11use std::collections::HashMap;
12use std::env;
13use std::ffi::{CString, OsStr, OsString};
14use std::fmt::Write as FmtWrite;
15use std::fs;
16use std::io::prelude::*;
17use std::io::BufReader;
18use std::mem::size_of;
19use std::os::linux::fs::MetadataExt as LinuxME;
20use std::os::unix::ffi::OsStrExt;
21use std::os::unix::fs::MetadataExt as UnixME;
22use std::os::unix::fs::PermissionsExt;
23use std::path::{Path, PathBuf};
24use std::process::{self, Command};
25use std::sync::{atomic, Condvar, Mutex};
26use std::thread_local;
27use std::time::{Duration, UNIX_EPOCH};
28
29pub mod anon_area;
30pub mod iocost;
31pub mod journal_tailer;
32pub mod json_file;
33pub mod storage_info;
34pub mod systemd;
35
36pub use iocost::{IoCostModelParams, IoCostQoSParams, IoCostSysSave};
37pub use journal_tailer::*;
38pub use json_file::{
39    JsonArgs, JsonArgsHelper, JsonConfigFile, JsonLoad, JsonRawFile, JsonReportFile, JsonSave,
40};
41pub use storage_info::*;
42pub use systemd::TransientService;
43
44pub const TO_MSEC: f64 = 1000.0;
45pub const TO_PCT: f64 = 100.0;
46pub const MSEC: f64 = 1.0 / 1000.0;
47
48pub const READ: usize = 0;
49pub const WRITE: usize = 1;
50
51lazy_static::lazy_static! {
52    static ref GIT_VERSION: &'static str = {
53        match option_env!("VERGEN_GIT_SEMVER_LIGHTWEIGHT") {
54            Some(v) => split_git_version(v).1,
55            None => ""
56        }
57    };
58    static ref BUILD_TAG: String = {
59        let mut tag = env!("VERGEN_RUSTC_HOST_TRIPLE").to_string();
60        if cfg!(debug_assertions) {
61            write!(tag, "/debug").unwrap();
62        }
63        tag
64    };
65
66    pub static ref TOTAL_SYSTEM_MEMORY: usize = {
67        let mut sys = sysinfo::System::new();
68        sys.refresh_memory();
69        sys.total_memory() as usize
70    };
71    pub static ref TOTAL_SYSTEM_SWAP: usize = {
72        let mut sys = sysinfo::System::new();
73        sys.refresh_memory();
74        sys.total_swap() as usize
75    };
76    pub static ref NR_SYSTEM_CPUS: usize = ::num_cpus::get();
77    static ref TOTAL_MEMORY: atomic::AtomicUsize = atomic::AtomicUsize::new(0);
78    static ref TOTAL_SWAP: atomic::AtomicUsize = atomic::AtomicUsize::new(0);
79    static ref NR_CPUS: atomic::AtomicUsize = atomic::AtomicUsize::new(0);
80    pub static ref PAGE_SIZE: usize = ::page_size::get();
81    pub static ref ROTATIONAL_SWAP: bool = storage_info::is_swap_rotational();
82    pub static ref IS_FB_PROD: bool = {
83        match glob("/sys/fs/cgroup/**/fbagentd.service")
84            .unwrap()
85            .filter_map(|x| x.ok())
86            .next()
87        {
88            Some(_) => {
89                warn!("FB PROD detected, default parameters will be adjusted");
90                true
91            }
92            None => false,
93        }
94    };
95}
96
97pub fn full_version(semver: &str) -> String {
98    let mut ver = semver.to_string();
99    if GIT_VERSION.len() > 0 {
100        write!(ver, "-{}", &*GIT_VERSION).unwrap();
101    }
102    if BUILD_TAG.len() > 0 {
103        write!(ver, " {}", &*BUILD_TAG).unwrap();
104    }
105    ver
106}
107
108fn split_git_version(ver: &str) -> (&str, &str) {
109    let comps = ver.split('-').collect::<Vec<&str>>();
110    let mut git_len = 0;
111    let mut discard_len = 0;
112    let mut idx = comps.len() - 1;
113
114    if idx > 0 && comps[idx] == "dirty" {
115        git_len += comps[idx].len() + 1;
116        idx -= 1;
117    }
118    if idx > 0 && &comps[idx][0..1] == "g" && u64::from_str_radix(&comps[idx][1..], 16).is_ok() {
119        git_len += comps[idx].len() + 1;
120        idx -= 1;
121        if idx > 0 && comps[idx].parse::<u32>().is_ok() {
122            discard_len = comps[idx].len() + 1;
123        }
124    }
125
126    if git_len == 0 {
127        (ver, "")
128    } else {
129        (
130            &ver[0..ver.len() - discard_len - git_len],
131            &ver[ver.len() - git_len + 1..],
132        )
133    }
134}
135
136pub fn parse_version(ver: &str) -> (&str, &str, &str) {
137    let (rest, tag) = ver.split_once(' ').unwrap_or((ver, ""));
138    let (sem, git) = split_git_version(rest);
139    (sem, git, tag)
140}
141
142pub fn parse_semver(sem: &str) -> (u32, u32, u32) {
143    let mut vers: [u32; 3] = [0, 0, 0];
144    let mut idx = 0;
145
146    for part in sem.split('.') {
147        match part.parse::<u32>() {
148            Ok(v) => vers[idx] = v,
149            _ => {}
150        }
151        idx += 1;
152        if idx == 3 {
153            break;
154        }
155    }
156    return (vers[0], vers[1], vers[2]);
157}
158
159fn verify_bin(target: &str) -> bool {
160    match find_bin(target, exe_dir().ok()) {
161        Some(_) => return true,
162        None => {
163            error!(
164                "Failed to find {:?}, install with `cargo install {}`",
165                target, target
166            );
167            return false;
168        }
169    };
170}
171
172pub fn verify_agent_and_hashd(_ver: &str) -> bool {
173    let mut res = true;
174    res &= verify_bin("rd-hashd");
175    res &= verify_bin("rd-agent");
176    res
177}
178
179pub fn total_memory() -> usize {
180    match TOTAL_MEMORY.load(atomic::Ordering::Relaxed) {
181        0 => *TOTAL_SYSTEM_MEMORY,
182        v => v,
183    }
184}
185
186pub fn total_swap() -> usize {
187    match TOTAL_SWAP.load(atomic::Ordering::Relaxed) {
188        0 => *TOTAL_SYSTEM_SWAP,
189        v => v,
190    }
191}
192
193pub fn nr_cpus() -> usize {
194    match NR_CPUS.load(atomic::Ordering::Relaxed) {
195        0 => *NR_SYSTEM_CPUS,
196        v => v,
197    }
198}
199
200pub const SWAPPINESS_PATH: &str = "/proc/sys/vm/swappiness";
201
202pub fn read_swappiness() -> Result<u32> {
203    Ok(read_one_line(SWAPPINESS_PATH)
204        .context("Reading swappiness")?
205        .trim()
206        .parse::<u32>()
207        .context("Parsing swappiness")?)
208}
209
210pub const ZSWAP_ENABLED_PATH: &str = "/sys/module/zswap/parameters/enabled";
211
212pub fn read_zswap_enabled() -> Result<bool> {
213    if !Path::new(ZSWAP_ENABLED_PATH).exists() {
214        return Ok(false);
215    }
216    Ok(
217        if let Some(ch) = read_one_line(ZSWAP_ENABLED_PATH)
218            .context("Reading zswap enabled")?
219            .trim()
220            .chars()
221            .next()
222        {
223            ch == 'y' || ch == 'Y' || ch == '1'
224        } else {
225            bail!("{:?} read empty", ZSWAP_ENABLED_PATH);
226        },
227    )
228}
229
230pub fn override_system_configuration(
231    total_memory: Option<usize>,
232    total_swap: Option<usize>,
233    nr_cpus: Option<usize>,
234) {
235    let total_memory = total_memory.unwrap_or(0);
236    let total_swap = total_swap.unwrap_or(0);
237    let nr_cpus = nr_cpus.unwrap_or(0);
238
239    TOTAL_MEMORY.store(total_memory, atomic::Ordering::Relaxed);
240    TOTAL_SWAP.store(total_swap, atomic::Ordering::Relaxed);
241    NR_CPUS.store(nr_cpus, atomic::Ordering::Relaxed);
242
243    let mut buf = String::new();
244    if total_memory > 0 {
245        write!(
246            buf,
247            " memory={}->{}",
248            format_size(*TOTAL_SYSTEM_MEMORY),
249            format_size(total_memory)
250        )
251        .unwrap();
252    }
253    if total_swap > 0 {
254        write!(
255            buf,
256            " swap={}->{}",
257            format_size(*TOTAL_SYSTEM_SWAP),
258            format_size(total_swap)
259        )
260        .unwrap();
261    }
262    if nr_cpus > 0 {
263        write!(buf, " cpus={}->{}", *NR_SYSTEM_CPUS, nr_cpus).unwrap();
264    }
265    if buf.len() > 0 {
266        info!("System configuration overrides:{}", &buf);
267    }
268}
269
270pub fn to_gb<T>(size: T) -> f64
271where
272    T: num::ToPrimitive,
273{
274    let size_f64 = size.to_f64().unwrap();
275    size_f64 / (1 << 30) as f64
276}
277
278pub fn to_mb<T>(size: T) -> f64
279where
280    T: num::ToPrimitive,
281{
282    let size_f64 = size.to_f64().unwrap();
283    size_f64 / (1 << 20) as f64
284}
285
286pub fn to_kb<T>(size: T) -> f64
287where
288    T: num::ToPrimitive,
289{
290    let size_f64 = size.to_f64().unwrap();
291    size_f64 / (1 << 10) as f64
292}
293
294pub fn scale_ratio<T>(ratio: f64, (left, mid, right): (T, T, T)) -> T
295where
296    T: PartialOrd + num::FromPrimitive + num::ToPrimitive,
297{
298    let (left_f64, mid_f64, right_f64) = (
299        left.to_f64().unwrap(),
300        mid.to_f64().unwrap(),
301        right.to_f64().unwrap(),
302    );
303
304    let v = if ratio < 0.5 {
305        left_f64 + (mid_f64 - left_f64) * ratio / 0.5
306    } else {
307        mid_f64 + (right_f64 - mid_f64) * (ratio - 0.5) / 0.5
308    };
309
310    num::clamp(T::from_f64(v).unwrap(), left, right)
311}
312
313pub fn custom_underline(content: &str, line_char: &str) -> String {
314    let nr_spaces = content.chars().take_while(|c| *c == ' ').count();
315    let len = content.chars().count() - nr_spaces;
316    format!(
317        "{}\n{}{}\n",
318        content,
319        " ".repeat(nr_spaces),
320        line_char.repeat(len)
321    )
322}
323
324pub fn underline(content: &str) -> String {
325    custom_underline(content, "-")
326}
327
328pub fn double_underline(content: &str) -> String {
329    custom_underline(content, "=")
330}
331
332fn format_size_internal<T>(size: T, zero: &str) -> String
333where
334    T: num::ToPrimitive,
335{
336    let format_size_helper = |size: u64, shift: u32, suffix: &str| -> Option<String> {
337        let unit: u64 = 1 << shift;
338
339        if (size as f64 / unit as f64) < 99.95 {
340            Some(format!(
341                "{:.1}{}",
342                (size as f64 / unit as f64).max(0.1),
343                suffix
344            ))
345        } else if (size as f64 / unit as f64) < 1024.0 {
346            Some(format!("{:.0}{}", size as f64 / unit as f64, suffix))
347        } else {
348            None
349        }
350    };
351
352    let size = size.to_u64().unwrap();
353
354    if size == 0 {
355        zero.to_string()
356    } else if size < 9999 {
357        format!("{}", size)
358    } else {
359        format_size_helper(size, 10, "K")
360            .or_else(|| format_size_helper(size, 20, "M"))
361            .or_else(|| format_size_helper(size, 30, "G"))
362            .or_else(|| format_size_helper(size, 40, "P"))
363            .or_else(|| format_size_helper(size, 50, "E"))
364            .unwrap_or_else(|| "INF".into())
365    }
366}
367
368pub fn format_size<T>(size: T) -> String
369where
370    T: num::ToPrimitive,
371{
372    format_size_internal(size, "0")
373}
374
375pub fn format_size_dashed<T>(size: T) -> String
376where
377    T: num::ToPrimitive,
378{
379    format_size_internal(size, "-")
380}
381
382fn format_count_internal<T>(count: T, zero: &str) -> String
383where
384    T: num::ToPrimitive,
385{
386    let format_count_helper = |count: u64, zeroes: u32, suffix: &str| -> Option<String> {
387        let unit: u64 = 10_u64.pow(zeroes);
388
389        if (count as f64 / unit as f64) < 99.95 {
390            Some(format!(
391                "{:.1}{}",
392                (count as f64 / unit as f64).max(0.1),
393                suffix
394            ))
395        } else if (count as f64 / unit as f64) < 1000.0 {
396            Some(format!("{:.0}{}", count as f64 / unit as f64, suffix))
397        } else {
398            None
399        }
400    };
401
402    let count = count.to_u64().unwrap();
403
404    if count == 0 {
405        zero.to_string()
406    } else if count < 1000 {
407        format!("{}", count)
408    } else {
409        format_count_helper(count, 3, "k")
410            .or_else(|| format_count_helper(count, 6, "m"))
411            .or_else(|| format_count_helper(count, 9, "g"))
412            .or_else(|| format_count_helper(count, 12, "p"))
413            .or_else(|| format_count_helper(count, 15, "e"))
414            .unwrap_or_else(|| "INF".into())
415    }
416}
417
418pub fn format_count<T>(count: T) -> String
419where
420    T: num::ToPrimitive,
421{
422    format_count_internal(count, "0")
423}
424
425pub fn format_count_dashed<T>(count: T) -> String
426where
427    T: num::ToPrimitive,
428{
429    format_count_internal(count, "-")
430}
431
432fn format_duration_internal(dur: f64, zero: &str) -> String {
433    let format_nsecs_helper = |nsecs: u64, unit: u64, max: u64, suffix: &str| -> Option<String> {
434        if nsecs == 0 {
435            Some(zero.to_string())
436        } else if (nsecs as f64 / unit as f64) < 99.95 {
437            Some(format!(
438                "{:.1}{}",
439                (nsecs as f64 / unit as f64).max(0.1),
440                suffix
441            ))
442        } else if (nsecs as f64 / unit as f64) < max as f64 {
443            Some(format!("{:.0}{}", nsecs as f64 / unit as f64, suffix))
444        } else {
445            None
446        }
447    };
448
449    let nsecs = (dur * 1_000_000_000.0).round() as u64;
450
451    format_nsecs_helper(nsecs, 10_u64.pow(0), 1000, "n")
452        .or_else(|| format_nsecs_helper(nsecs, 10_u64.pow(3), 1000, "u"))
453        .or_else(|| format_nsecs_helper(nsecs, 10_u64.pow(6), 1000, "m"))
454        .or_else(|| format_nsecs_helper(nsecs, 10_u64.pow(9), 60, "s"))
455        .or_else(|| format_nsecs_helper(nsecs, 10_u64.pow(9) * 60, 60, "M"))
456        .or_else(|| format_nsecs_helper(nsecs, 10_u64.pow(9) * 60 * 60, 24, "H"))
457        .or_else(|| format_nsecs_helper(nsecs, 10_u64.pow(9) * 60 * 60 * 24, 365, "D"))
458        .or_else(|| format_nsecs_helper(nsecs, 10_u64.pow(9) * 60 * 60 * 24 * 365, 1000, "Y"))
459        .unwrap_or_else(|| "INF".into())
460}
461
462pub fn format_duration(dur: f64) -> String {
463    format_duration_internal(dur, "0")
464}
465
466pub fn format_duration_dashed(dur: f64) -> String {
467    format_duration_internal(dur, "-")
468}
469
470fn format4_pct_internal(ratio: f64, zero: &str) -> String {
471    let pct = ratio * TO_PCT;
472    if pct < 0.0 {
473        "NEG".into()
474    } else if pct == 0.0 {
475        zero.to_string()
476    } else if pct < 99.95 {
477        format!("{:.01}", pct)
478    } else if pct < 9999.5 {
479        format!("{:.0}", pct)
480    } else if pct / 1000.0 < 99.5 {
481        format!("{:.0}k", pct / 1000.0)
482    } else {
483        "INF".into()
484    }
485}
486
487pub fn format4_pct(ratio: f64) -> String {
488    format4_pct_internal(ratio, "0")
489}
490
491pub fn format4_pct_dashed(ratio: f64) -> String {
492    format4_pct_internal(ratio, "-")
493}
494
495fn format_pct_internal(ratio: f64, zero: &str) -> String {
496    let pct = ratio * TO_PCT;
497    if pct < 0.0 {
498        "NEG".into()
499    } else if pct == 0.0 {
500        zero.to_string()
501    } else if pct < 99.995 {
502        format!("{:.02}", pct)
503    } else if pct < 999.95 {
504        format!("{:.01}", pct)
505    } else if pct < 99999.5 {
506        format!("{:.0}", pct)
507    } else if pct / 1000.0 < 99.995 {
508        format!("{:.1}k", pct / 1000.0)
509    } else if pct / 1000.0 < 9999.5 {
510        format!("{:.0}k", pct / 1000.0)
511    } else {
512        "INF".into()
513    }
514}
515
516pub fn format_pct(ratio: f64) -> String {
517    format_pct_internal(ratio, "0")
518}
519
520pub fn format_pct_dashed(ratio: f64) -> String {
521    format_pct_internal(ratio, "-")
522}
523
524pub fn format_percentile(pct: &str) -> String {
525    match pct.parse::<f64>() {
526        Ok(pctf) => {
527            if pctf == 0.0 {
528                "min".to_string()
529            } else if pctf == 100.0 {
530                "max".to_string()
531            } else {
532                format!("p{}", pct)
533            }
534        }
535        _ => pct.to_string(),
536    }
537}
538
539pub fn parse_duration(input: &str) -> Result<f64> {
540    lazy_static::lazy_static! {
541        static ref UNITS: HashMap<char, f64> = [
542            ('n', 0.000_000_001),
543            ('u', 0.000_001),
544            ('m', 0.001),
545            ('s', 1.0),
546            ('M', 60.0),
547            ('H', 3600.0),
548            ('D', 3600.0 * 24.0),
549            ('Y', 3600.0 * 24.0 * 365.0),
550        ]
551            .iter()
552            .cloned()
553            .collect();
554    }
555
556    let mut num = String::new();
557    let mut sum = 0.0;
558    for ch in input.chars() {
559        match ch {
560            '_' => continue,
561            ch if UNITS.contains_key(&ch) => {
562                sum += num.trim().parse::<f64>()? * UNITS[&ch];
563                num.clear();
564            }
565            ch => num.push(ch),
566        }
567    }
568    if num.trim().len() > 0 {
569        sum += num.trim().parse::<f64>()?;
570    }
571    Ok(sum)
572}
573
574pub fn parse_size(input: &str) -> Result<u64> {
575    lazy_static::lazy_static! {
576        static ref UNITS: HashMap<char, u32> = [
577            ('B', 0),
578            ('K', 10),
579            ('M', 20),
580            ('G', 30),
581            ('T', 40),
582            ('P', 50),
583            ('E', 60),
584        ].iter().cloned().collect();
585    }
586
587    let parse_num = |num: &str, shift: u32| -> Result<u64> {
588        Ok(if num.contains(".") {
589            (num.parse::<f64>()? * (2u64.pow(shift) as f64)).round() as u64
590        } else {
591            num.parse::<u64>()? * (1 << shift)
592        })
593    };
594
595    let mut num = String::new();
596    let mut sum = 0;
597    for ch in input.chars() {
598        let ch = ch.to_uppercase().to_string().chars().next().unwrap();
599        match ch {
600            '_' => continue,
601            ch if UNITS.contains_key(&ch) => {
602                sum += parse_num(num.trim(), UNITS[&ch])?;
603                num.clear();
604            }
605            ch => num.push(ch),
606        }
607    }
608    if num.trim().len() > 0 {
609        sum += parse_num(num.trim(), 0)?;
610    }
611    Ok(sum)
612}
613
614pub fn parse_frac(input: &str) -> Result<f64> {
615    let mut input = input.trim();
616    let mut mult = 1.0;
617    if input.ends_with("%") {
618        input = &input[0..input.len() - 1];
619        mult = 0.01;
620    }
621    let v = input
622        .parse::<f64>()
623        .with_context(|| format!("failed to parse fractional \"{}\"", input))?
624        * mult;
625    if v < 0.0 {
626        bail!("fractional {} is negative", v);
627    }
628    Ok(v)
629}
630
631fn is_executable<P: AsRef<Path>>(path_in: P) -> bool {
632    let path = path_in.as_ref();
633    match path.metadata() {
634        Ok(md) => md.is_file() && md.mode() & 0o111 != 0,
635        Err(_) => false,
636    }
637}
638
639pub fn exe_dir() -> Result<PathBuf> {
640    let mut path = env::current_exe()?;
641    path.pop();
642    Ok(path)
643}
644
645pub fn find_bin<N: AsRef<OsStr>, P: AsRef<OsStr>>(
646    name_in: N,
647    prepend_in: Option<P>,
648) -> Option<PathBuf> {
649    let name = name_in.as_ref();
650    let mut search = OsString::new();
651    if let Some(prepend) = prepend_in.as_ref() {
652        search.push(prepend);
653        search.push(":");
654    }
655    if let Some(dirs) = env::var_os("PATH") {
656        search.push(dirs);
657    }
658    for dir in env::split_paths(&search) {
659        let mut path = dir.to_owned();
660        path.push(name);
661        if let Ok(path) = path.canonicalize() {
662            if is_executable(&path) {
663                return Some(path);
664            }
665        }
666    }
667    None
668}
669
670pub fn chgrp<P: AsRef<Path>>(path_in: P, gid: u32) -> Result<bool> {
671    let path = path_in.as_ref();
672    let md = fs::metadata(path)?;
673    if md.st_gid() != gid {
674        let cpath = CString::new(path.as_os_str().as_bytes())?;
675        if unsafe { libc::chown(cpath.as_ptr(), md.st_uid(), gid) } < 0 {
676            bail!("Failed to chgrp {:?} to {} ({:?})", path, gid, unsafe {
677                *libc::__errno_location()
678            });
679        }
680        Ok(true)
681    } else {
682        Ok(false)
683    }
684}
685
686pub fn set_sgid<P: AsRef<Path>>(path_in: P) -> Result<bool> {
687    let path = path_in.as_ref();
688    let md = fs::metadata(path)?;
689    let mut perm = md.permissions();
690    if perm.mode() & 0o2000 == 0 {
691        perm.set_mode(perm.mode() | 0o2000);
692        fs::set_permissions(path, perm)?;
693        Ok(true)
694    } else {
695        Ok(false)
696    }
697}
698
699pub fn read_one_line<P: AsRef<Path>>(path: P) -> Result<String> {
700    let f = fs::OpenOptions::new().read(true).open(path)?;
701    let r = BufReader::new(f);
702    Ok(r.lines().next().ok_or(anyhow!("File empty"))??)
703}
704
705pub fn write_one_line<P: AsRef<Path>>(path: P, line: &str) -> Result<()> {
706    let mut f = fs::OpenOptions::new().write(true).open(path)?;
707    Ok(f.write_all(line.as_ref())?)
708}
709
710pub fn unix_now() -> u64 {
711    UNIX_EPOCH.elapsed().unwrap().as_secs()
712}
713
714pub fn unix_now_f64() -> f64 {
715    UNIX_EPOCH.elapsed().unwrap().as_secs_f64()
716}
717
718pub fn format_unix_time(time: u64) -> String {
719    DateTime::<Local>::from(UNIX_EPOCH + Duration::from_secs(time))
720        .format("%x %T")
721        .to_string()
722}
723
724pub fn format_period(per: (u64, u64)) -> String {
725    format!(
726        "{} - {} ({}-{})",
727        format_unix_time(per.0),
728        format_unix_time(per.1),
729        per.0,
730        per.1
731    )
732}
733
734pub fn init_logging(verbosity: u32, logfile: String) {
735    if std::env::var("RUST_LOG").is_ok() {
736        env_logger::init();
737    } else {
738        let sl_level = match verbosity {
739            0 | 1 => sl::LevelFilter::Info,
740            2 => sl::LevelFilter::Debug,
741            _ => sl::LevelFilter::Trace,
742        };
743        let mut lcfg = sl::ConfigBuilder::new();
744        lcfg.set_time_level(sl::LevelFilter::Off)
745            .set_location_level(sl::LevelFilter::Off)
746            .set_target_level(sl::LevelFilter::Off)
747            .set_thread_level(sl::LevelFilter::Off);
748
749        if logfile.is_empty() {
750            if !console::user_attended_stderr()
751                || sl::TermLogger::init(
752                    sl_level,
753                    lcfg.build(),
754                    sl::TerminalMode::Stderr,
755                    sl::ColorChoice::Auto,
756                )
757                .is_err()
758            {
759                sl::WriteLogger::init(sl_level, lcfg.build(), std::io::stderr()).unwrap();
760            }
761        } else {
762            let termlogger: Box<dyn sl::SharedLogger>;
763            if console::user_attended_stderr() {
764                termlogger = sl::TermLogger::new(
765                    sl_level,
766                    lcfg.build(),
767                    sl::TerminalMode::Stderr,
768                    sl::ColorChoice::Auto,
769                );
770            } else {
771                termlogger = sl::WriteLogger::new(sl_level, lcfg.build(), std::io::stderr());
772            }
773
774            sl::CombinedLogger::init(vec![
775                termlogger,
776                sl::WriteLogger::new(
777                    sl::LevelFilter::Debug,
778                    lcfg.build(),
779                    std::fs::File::create(logfile).unwrap(),
780                ),
781            ])
782            .unwrap();
783        }
784    }
785}
786
787pub fn child_reader_thread(name: String, stdout: process::ChildStdout, tx: Sender<String>) {
788    let reader = BufReader::new(stdout);
789    for line in reader.lines() {
790        match line {
791            Ok(line) => {
792                if let Err(e) = tx.send(line) {
793                    info!("{}: Reader thread terminating ({:?})", &name, &e);
794                    break;
795                }
796            }
797            Err(e) => {
798                warn!("{}: Failed to read from journalctl ({:?})", &name, &e);
799                break;
800            }
801        }
802    }
803}
804
805pub fn run_command(cmd: &mut Command, emsg: &str) -> Result<()> {
806    let cmd_str = format!("{:?}", &cmd);
807
808    match cmd.status() {
809        Ok(rc) if rc.success() => Ok(()),
810        Ok(rc) => bail!("{:?} ({:?}): {}", &cmd_str, &rc, emsg),
811        Err(e) => bail!("{:?} ({:?}): {}", &cmd_str, &e, emsg),
812    }
813}
814
815pub fn fill_area_with_random<T, R: rand::Rng + ?Sized>(area: &mut [T], comp: f64, rng: &mut R) {
816    let area = unsafe {
817        std::slice::from_raw_parts_mut(
818            std::mem::transmute::<*mut T, *mut u64>(area.as_mut_ptr()),
819            area.len() * size_of::<T>() / size_of::<u64>(),
820        )
821    };
822
823    const BLOCK_SIZE: usize = 512;
824    const WORDS_PER_BLOCK: usize = BLOCK_SIZE / size_of::<u64>();
825    let rands_per_block = (((WORDS_PER_BLOCK as f64) * (1.0 - comp)) as usize).min(WORDS_PER_BLOCK);
826    let last_first = area[0];
827
828    for i in 0..area.len() {
829        area[i] = if i % WORDS_PER_BLOCK < rands_per_block {
830            rng.gen()
831        } else {
832            0
833        };
834    }
835
836    // guarantee that the first word doesn't stay the same
837    if area[0] == last_first {
838        area[0] += 1;
839    }
840}
841
842pub fn read_cgroup_flat_keyed_file(path: &str) -> Result<HashMap<String, u64>> {
843    let f = fs::OpenOptions::new().read(true).open(path)?;
844    let r = BufReader::new(f);
845    let mut map = HashMap::new();
846
847    for line in r.lines().filter_map(Result::ok) {
848        if let Ok((key, val)) = scan_fmt!(&line, "{} {d}", String, u64) {
849            map.insert(key, val);
850        }
851    }
852    Ok(map)
853}
854
855pub fn read_cgroup_nested_keyed_file(
856    path: &str,
857) -> Result<HashMap<String, HashMap<String, String>>> {
858    let f = fs::OpenOptions::new().read(true).open(path)?;
859    let r = BufReader::new(f);
860    let mut top_map = HashMap::new();
861
862    for line in r.lines().filter_map(Result::ok) {
863        let mut split = line.split_whitespace();
864        let top_key = split.next().unwrap();
865
866        let mut map = HashMap::new();
867        for tok in split {
868            if let Ok((key, val)) = scan_fmt!(tok, "{}={}", String, String) {
869                map.insert(key, val);
870            }
871        }
872        top_map.insert(top_key.into(), map);
873    }
874    Ok(top_map)
875}
876
877struct GlobalProgState {
878    exiting: bool,
879    kick_seq: u64,
880}
881
882lazy_static::lazy_static! {
883    static ref PROG_STATE: Mutex<GlobalProgState> = Mutex::new(GlobalProgState {
884        exiting: false,
885        kick_seq: 1
886    });
887    static ref PROG_WAITQ: Condvar = Condvar::new();
888}
889
890thread_local! {
891    static LOCAL_KICK_SEQ: RefCell<u64> = RefCell::new(0);
892}
893
894pub fn setup_prog_state() {
895    ctrlc::set_handler(move || {
896        info!("SIGINT/TERM received, exiting...");
897        set_prog_exiting();
898    })
899    .expect("Error setting term handler");
900}
901
902pub fn set_prog_exiting() {
903    PROG_STATE.lock().unwrap().exiting = true;
904    PROG_WAITQ.notify_all();
905}
906
907pub fn prog_exiting() -> bool {
908    PROG_STATE.lock().unwrap().exiting
909}
910
911pub fn prog_kick() {
912    PROG_STATE.lock().unwrap().kick_seq += 1;
913    PROG_WAITQ.notify_all();
914}
915
916#[derive(Debug, Clone, Copy, PartialEq, Eq)]
917pub enum ProgState {
918    Running,
919    Exiting,
920    Kicked,
921}
922
923pub fn wait_prog_state(dur: Duration) -> ProgState {
924    let mut first = true;
925    let mut state = PROG_STATE.lock().unwrap();
926    loop {
927        if state.exiting {
928            return ProgState::Exiting;
929        }
930        if LOCAL_KICK_SEQ.with(|seq| {
931            if *seq.borrow() < state.kick_seq {
932                *seq.borrow_mut() = state.kick_seq;
933                true
934            } else {
935                false
936            }
937        }) {
938            return ProgState::Kicked;
939        }
940
941        if first {
942            state = PROG_WAITQ.wait_timeout(state, dur).unwrap().0;
943            first = false;
944        } else {
945            return ProgState::Running;
946        }
947    }
948}
949
950#[derive(Debug, Deserialize, Serialize)]
951pub struct LambdaRequest {
952    pub data: String,
953    pub email: Option<String>,
954    pub github: Option<String>,
955}
956
957#[derive(Debug, Deserialize, Serialize)]
958#[serde(rename_all = "camelCase")]
959pub struct LambdaResponse {
960    pub issue: Option<String>,
961    pub error_type: Option<String>,
962    pub error_message: Option<String>,
963}
964
965#[cfg(test)]
966mod tests {
967    #[test]
968    fn test_format_duration() {
969        for pair in &[
970            (0.000003932, "3.9u"),
971            (0.00448, "4.5m"),
972            (0.3, "300m"),
973            (2042.0, "34.0M"),
974            (3456000.0, "40.0D"),
975            (60480000.0, "1.9Y"),
976        ] {
977            let result = super::format_duration(pair.0);
978            assert_eq!(&result, pair.1);
979            println!("{} -> {} ({})", pair.0, &result, pair.1);
980        }
981    }
982
983    #[test]
984    fn test_parse_duration() {
985        for pair in &[
986            (0.0000039, "3.9u"),
987            (0.0044, "4.4m"),
988            (0.3, "300m"),
989            (2040.0, "34.0M"),
990            (3456000.0, "40.0D"),
991            (59918400.0, "1.9Y"),
992            (59918401.1, "1.9Y_1s_100m"),
993            (59918401.1, "1.9Y1.1s"),
994            (59918401.102, "1.9Y  1.1s  2000  u"),
995            (1.27, "1.27"),
996            (1.37, "100m1.27"),
997        ] {
998            let result = super::parse_duration(pair.1).unwrap();
999            assert_eq!(pair.0, result);
1000            println!("{} -> {} ({})", pair.1, result, pair.0);
1001        }
1002    }
1003
1004    #[test]
1005    fn test_parse_size() {
1006        for pair in &[
1007            (4404019, "4.2m"),
1008            (2164785152, "2G_16.5M"),
1009            (1659790359820, "1.5t  9.8  G   248281"),
1010        ] {
1011            let result = super::parse_size(pair.1).unwrap();
1012            assert_eq!(pair.0, result);
1013            println!("{} -> {} ({})", pair.1, result, pair.0);
1014        }
1015    }
1016}