#[cfg(unix)]
extern crate libc;
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate lazy_static;
extern crate byteorder;
extern crate chrono;
extern crate hex;
extern crate libflate;
#[cfg(unix)]
mod platform_specific_items {
pub use libc::pid_t;
pub use libc::sysconf;
pub use libc::{_SC_CLK_TCK, _SC_PAGESIZE};
}
#[cfg(windows)]
mod platform_specific_items {
pub type pid_t = i32;
pub fn sysconf(_: i32) -> i64 {
panic!()
}
pub const _SC_CLK_TCK: i32 = 2;
pub const _SC_PAGESIZE: i32 = 30;
}
use platform_specific_items::*;
use std::collections::HashMap;
use std::ffi::CStr;
use std::fmt;
use std::fs::File;
use std::io::{Read, Write};
use std::mem;
use std::path::Path;
use std::str::FromStr;
use chrono::{DateTime, Local};
const PROC_CONFIG_GZ: &str = "/proc/config.gz";
const BOOT_CONFIG: &str = "/boot/config";
trait IntoOption<T> {
fn into_option(t: Self) -> Option<T>;
}
impl<T> IntoOption<T> for Option<T> {
fn into_option(t: Option<T>) -> Option<T> {
t
}
}
impl<T, R> IntoOption<T> for Result<T, R> {
fn into_option(t: Result<T, R>) -> Option<T> {
t.ok()
}
}
#[macro_use]
macro_rules! expect {
($e:expr) => {
::IntoOption::into_option($e).unwrap_or_else(|| {
panic!(
"Failed to unwrap {}. Please report this as a procfs bug.",
stringify!($e)
)
})
};
($e:expr, $msg:expr) => {
::IntoOption::into_option($e).unwrap_or_else(|| {
panic!(
"Failed to unwrap {} ({}). Please report this as a procfs bug.",
stringify!($e),
$msg
)
})
};
}
#[macro_use]
macro_rules! from_str {
($t:tt, $e:expr) => {{
let e = $e;
$t::from_str_radix(e, 10).unwrap_or_else(|_| {
panic!(
"Failed to parse {} ({:?}) as a {}. Please report this as a procfs bug.",
stringify!($e),
e,
stringify!($t),
)
})
}};
($t:tt, $e:expr, $radix:expr) => {{
let e = $e;
$t::from_str_radix(e, $radix).unwrap_or_else(|_| {
panic!(
"Failed to parse {} ({:?}) as a {}. Please report this as a procfs bug.",
stringify!($e),
e,
stringify!($t)
)
})
}};
($t:tt, $e:expr, $radix:expr, pid:$pid:expr) => {{
let e = $e;
$t::from_str_radix(e, $radix).unwrap_or_else(|_| {
panic!(
"Failed to parse {} ({:?}) as a {} (pid {}). Please report this as a procfs bug.",
stringify!($e),
e,
stringify!($t),
$pid
)
})
}};
}
pub(crate) fn read_file<P: AsRef<Path>>(path: P) -> ProcResult<String> {
let mut f = File::open(path)?;
let mut buf = String::new();
f.read_to_string(&mut buf)?;
Ok(buf)
}
pub(crate) fn write_file<P: AsRef<Path>, T: AsRef<[u8]>>(path: P, buf: T) -> ProcResult<()> {
let mut f = File::open(path)?;
f.write_all(buf.as_ref())?;
Ok(())
}
pub(crate) fn read_value<P: AsRef<Path>, T: FromStr<Err = E>, E: fmt::Debug>(
path: P,
) -> ProcResult<T> {
read_file(path).map(|buf| buf.trim().parse().unwrap())
}
pub(crate) fn write_value<P: AsRef<Path>, T: fmt::Display>(path: P, value: T) -> ProcResult<()> {
write_file(path, value.to_string().as_bytes())
}
mod process;
pub use process::*;
mod meminfo;
pub use meminfo::*;
mod net;
pub use net::*;
mod cpuinfo;
pub use cpuinfo::*;
mod cgroups;
pub use cgroups::*;
pub mod sys;
pub use sys::kernel::Version as KernelVersion;
lazy_static! {
static ref BOOTTIME: DateTime<Local> = {
boot_time().unwrap()
};
static ref TICKS_PER_SECOND: i64 = {
ticks_per_second().unwrap()
};
static ref KERNEL: KernelVersion = {
KernelVersion::current().unwrap()
};
static ref PAGESIZE: i64 = {
page_size().unwrap()
};
}
fn convert_to_bytes(num: u64, unit: &str) -> u64 {
match unit {
"B" => num,
"KiB" | "kiB" => num * 1024,
"kB" | "KB" => num * 1000,
"MiB" | "miB" => num * 1024 * 1024,
"MB" | "mB" => num * 1000 * 1000,
"GiB" | "giB" => num * 1024 * 1024 * 1024,
"GB" | "gB" => num * 1000 * 1000 * 1000,
unknown => panic!("Unknown unit type {}", unknown),
}
}
fn convert_to_kibibytes(num: u64, unit: &str) -> u64 {
match unit {
"B" => num,
"KiB" | "kiB" | "kB" | "KB" => num * 1024,
"MiB" | "miB" | "MB" | "mB" => num * 1024 * 1024,
"GiB" | "giB" | "GB" | "gB" => num * 1024 * 1024 * 1024,
unknown => panic!("Unknown unit type {}", unknown),
}
}
trait FromStrRadix: Sized {
fn from_str_radix(t: &str, radix: u32) -> Result<Self, std::num::ParseIntError>;
}
impl FromStrRadix for u64 {
fn from_str_radix(s: &str, radix: u32) -> Result<u64, std::num::ParseIntError> {
u64::from_str_radix(s, radix)
}
}
impl FromStrRadix for i32 {
fn from_str_radix(s: &str, radix: u32) -> Result<i32, std::num::ParseIntError> {
i32::from_str_radix(s, radix)
}
}
fn split_into_num<T: FromStrRadix>(s: &str, sep: char, radix: u32) -> (T, T) {
let mut s = s.split(sep);
let a = match FromStrRadix::from_str_radix(s.next().unwrap(), radix) {
Ok(v) => v,
_ => panic!(),
};
let b = match FromStrRadix::from_str_radix(s.next().unwrap(), radix) {
Ok(v) => v,
_ => panic!(),
};
(a, b)
}
pub type ProcResult<T> = Result<T, ProcError>;
#[derive(Debug)]
pub enum ProcError {
PermissionDenied,
NotFound,
Io(std::io::Error),
Other(String),
}
impl From<std::io::Error> for ProcError {
fn from(io: std::io::Error) -> Self {
use std::io::ErrorKind;
match io.kind() {
ErrorKind::PermissionDenied => ProcError::PermissionDenied,
ErrorKind::NotFound => ProcError::NotFound,
_other => ProcError::Io(io),
}
}
}
#[derive(Debug)]
pub struct LoadAverage {
pub one: f32,
pub five: f32,
pub fifteen: f32,
pub cur: u32,
pub max: u32,
pub latest_pid: u32,
}
impl LoadAverage {
pub fn new() -> ProcResult<LoadAverage> {
use std::fs::File;
let mut f = File::open("/proc/loadavg")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
let mut s = s.split_whitespace();
let one = f32::from_str(s.next().unwrap()).unwrap();
let five = f32::from_str(s.next().unwrap()).unwrap();
let fifteen = f32::from_str(s.next().unwrap()).unwrap();
let curmax = s.next().unwrap();
let latest_pid = u32::from_str(s.next().unwrap()).unwrap();
let mut s = curmax.split('/');
let cur = u32::from_str(s.next().unwrap()).unwrap();
let max = u32::from_str(s.next().unwrap()).unwrap();
Ok(LoadAverage {
one,
five,
fifteen,
cur,
max,
latest_pid,
})
}
}
pub fn ticks_per_second() -> std::io::Result<i64> {
if cfg!(unix) {
match unsafe { sysconf(_SC_CLK_TCK) } {
-1 => Err(std::io::Error::last_os_error()),
x => Ok(x),
}
} else {
panic!("Not supported on non-unix platforms")
}
}
pub fn boot_time() -> ProcResult<DateTime<Local>> {
let now = Local::now();
let mut f = File::open("/proc/uptime")?;
let mut buf = String::new();
f.read_to_string(&mut buf)?;
let uptime_seconds = f32::from_str(buf.split_whitespace().next().unwrap()).unwrap();
Ok(now - chrono::Duration::milliseconds((uptime_seconds * 1000.0) as i64))
}
pub fn page_size() -> std::io::Result<i64> {
if cfg!(unix) {
match unsafe { sysconf(_SC_PAGESIZE) } {
-1 => Err(std::io::Error::last_os_error()),
x => Ok(x),
}
} else {
panic!("Not supported on non-unix platforms")
}
}
#[derive(Debug, PartialEq)]
pub enum ConfigSetting {
Yes,
Module,
Value(String),
}
pub fn kernel_config() -> ProcResult<HashMap<String, ConfigSetting>> {
use libflate::gzip::Decoder;
use std::io::{BufRead, BufReader};
let reader: Box<BufRead> = if Path::new(PROC_CONFIG_GZ).exists() {
let file = File::open(PROC_CONFIG_GZ)?;
let decoder = Decoder::new(file)?;
Box::new(BufReader::new(decoder))
} else {
let mut kernel: libc::utsname = unsafe { mem::zeroed() };
if unsafe { libc::uname(&mut kernel) != 0 } {
return Err(ProcError::Other(format!("Failed to call uname()")));
}
let filename = format!(
"{}-{}",
BOOT_CONFIG,
unsafe { CStr::from_ptr(kernel.release.as_ptr() as *const i8) }.to_string_lossy()
);
if Path::new(&filename).exists() {
let file = File::open(filename)?;
Box::new(BufReader::new(file))
} else {
let file = File::open(BOOT_CONFIG)?;
Box::new(BufReader::new(file))
}
};
let mut map = HashMap::new();
for line in reader.lines() {
let line = line?;
if line.starts_with('#') {
continue;
}
if line.contains('=') {
let mut s = line.splitn(2, '=');
let name = expect!(s.next()).to_owned();
let value = match expect!(s.next()) {
"y" => ConfigSetting::Yes,
"m" => ConfigSetting::Module,
s => ConfigSetting::Value(s.to_owned()),
};
map.insert(name, value);
}
}
Ok(map)
}
pub fn meminfo() -> ProcResult<Meminfo> {
Meminfo::new()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kernel_const() {
println!("{:?}", *KERNEL);
}
#[test]
fn test_kernel_from_str() {
let k = KernelVersion::from_str("1.2.3").unwrap();
assert_eq!(k.major, 1);
assert_eq!(k.minor, 2);
assert_eq!(k.patch, 3);
let k = KernelVersion::from_str("4.9.16-gentoo").unwrap();
assert_eq!(k.major, 4);
assert_eq!(k.minor, 9);
assert_eq!(k.patch, 16);
}
#[test]
fn test_kernel_cmp() {
let a = KernelVersion::from_str("1.2.3").unwrap();
let b = KernelVersion::from_str("1.2.3").unwrap();
let c = KernelVersion::from_str("1.2.4").unwrap();
let d = KernelVersion::from_str("1.5.4").unwrap();
let e = KernelVersion::from_str("2.5.4").unwrap();
assert_eq!(a, b);
assert!(a < c);
assert!(a < d);
assert!(a < e);
assert!(e > d);
assert!(e > c);
assert!(e > b);
}
#[test]
fn test_loadavg() {
let load = LoadAverage::new().unwrap();
println!("{:?}", load);
}
#[test]
fn test_from_str() {
assert_eq!(from_str!(u8, "12"), 12);
assert_eq!(from_str!(u8, "A", 16), 10);
}
#[test]
#[should_panic]
fn test_from_str_panic() {
let s = "four";
from_str!(u8, s);
}
#[test]
fn test_kernel_config() {
match std::env::var("TRAVIS") {
Ok(ref s) if s == "true" => return,
_ => {}
}
let config = kernel_config().unwrap();
println!("{:#?}", config);
}
}