1use 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 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}