summary history branches tags files
commit:2581b9e0a7af8da593a0663136c686f6e57ea613
author:Trevor Bentley
committer:Trevor Bentley
date:Mon Nov 13 15:41:51 2017 +0100
parents:
Reading idle status from w, xssstate, xprintidle, and uptime
diff --git a/.gitignore b/.gitignore
line changes: +4/-0
index 0000000..18f44ef
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/target/
+**/*.rs.bk
+*~
+#*#

diff --git a/Cargo.lock b/Cargo.lock
line changes: +88/-0
index 0000000..5a0c2a9
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,88 @@
+[root]
+name = "circadian"
+version = "0.1.0"
+dependencies = [
+ "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "lazy_static"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libc"
+version = "0.2.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "memchr"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "thread_local"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unreachable"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "utf8-ranges"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "void"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699"
+"checksum lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c9e5e58fa1a4c3b915a561a78a22ee0cac6ab97dca2504428bc1cb074375f8d5"
+"checksum libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "5ba3df4dcb460b9dfbd070d41c94c19209620c191b0340b929ce748a2bcd42d2"
+"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a"
+"checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b"
+"checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db"
+"checksum thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14"
+"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
+"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
+"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"

diff --git a/Cargo.toml b/Cargo.toml
line changes: +7/-0
index 0000000..7ade524
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "circadian"
+version = "0.1.0"
+authors = ["mrmekon"]
+
+[dependencies]
+regex = "0.2"

diff --git a/src/main.rs b/src/main.rs
line changes: +159/-0
index 0000000..8d9150a
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,159 @@
+extern crate regex;
+use regex::Regex;
+
+use std::error::Error;
+use std::process::Stdio;
+use std::process::Command;
+
+pub struct CircadianError(String);
+impl std::fmt::Debug for CircadianError {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+impl From<std::io::Error> for CircadianError {
+    fn from(error: std::io::Error) -> Self {
+        CircadianError(error.description().to_owned())
+    }
+}
+impl From<regex::Error> for CircadianError {
+    fn from(error: regex::Error) -> Self {
+        CircadianError(error.description().to_owned())
+    }
+}
+
+type IdleResult = Result<u32, CircadianError>;
+type ThreshResult = Result<bool, CircadianError>;
+
+#[allow(dead_code)]
+enum CpuHistory {
+    Min1,
+    Min5,
+    Min15
+}
+
+/// Parse idle time strings from 'w' command into seconds
+fn parse_w_time(time_str: &str) -> Result<u32, CircadianError> {
+    let mut secs: u32 = 0;
+    let mut mins: u32 = 0;
+    let mut hours:u32 = 0;
+    let re_sec = Regex::new(r"^\d+.\d+s$")?;
+    let re_min = Regex::new(r"^\d+:\d+$")?;
+    let re_hour = Regex::new(r"^\d+:\d+m$")?;
+    if re_sec.is_match(time_str) {
+        let time_str: &str = time_str.trim_matches('s');
+        let parts: Vec<u32> = time_str.split(".")
+            .map(|s| str::parse::<u32>(s).unwrap_or(0))
+            .collect();
+        secs = *parts.get(0).unwrap_or(&0);
+    }
+    else if re_min.is_match(time_str) {
+        let parts: Vec<u32> = time_str.split(":")
+            .map(|s| str::parse::<u32>(s).unwrap_or(0))
+            .collect();
+        mins = *parts.get(0).unwrap_or(&0);
+        secs = *parts.get(1).unwrap_or(&0);
+    }
+    else if re_hour.is_match(time_str) {
+        let time_str: &str = time_str.trim_matches('m');
+        let parts: Vec<u32> = time_str.split(":")
+            .map(|s| str::parse::<u32>(s).unwrap_or(0))
+            .collect();
+        hours = *parts.get(0).unwrap_or(&0);
+        mins = *parts.get(1).unwrap_or(&0);
+    }
+    else {
+        return Err(CircadianError("Invalid idle format".to_string()));
+    }
+    Ok((hours*60*60) + (mins*60) + secs)
+}
+
+// Call 'w' command and return minimum idle time
+fn idle_w() -> IdleResult {
+    let w_output = Command::new("w")
+        .arg("-hus")
+        .stdout(Stdio::piped()).spawn()?;
+    let w_stdout = w_output.stdout
+        .ok_or(CircadianError("w command has no output".to_string()))?;
+    let awk_output = Command::new("awk")
+        .arg("{print $4}")
+        .stdin(w_stdout)
+        .output()?;
+    let idle_times: Vec<u32> = String::from_utf8(awk_output.stdout)
+        .unwrap_or(String::new())
+        .split("\n")
+        .filter(|t| t.len() > 0)
+        .map(|t| parse_w_time(t))
+        .filter_map(|t| t.ok())
+        .collect();
+    Ok(idle_times.iter().cloned().fold(std::u32::MAX, std::cmp::min))
+}
+
+// Call 'xssstate' command and return idle time
+fn idle_xssstate() -> IdleResult {
+    let output = Command::new("xssstate")
+        .env("DISPLAY", ":0.0")
+        .arg("-i")
+        .output()?;
+    let mut idle_str = String::from_utf8(output.stdout)
+        .unwrap_or(String::new());
+    idle_str.pop();
+    Ok(idle_str.parse::<u32>().unwrap_or(0)/1000)
+}
+
+// Call 'xprintidle' command and return idle time
+fn idle_xprintidle() -> IdleResult {
+    let output = Command::new("xprintidle")
+        .env("DISPLAY", ":0.0")
+        .output()?;
+    let mut idle_str = String::from_utf8(output.stdout)
+        .unwrap_or(String::new());
+    idle_str.pop();
+    Ok(idle_str.parse::<u32>().unwrap_or(0)/1000)
+}
+
+// Compare whether 'uptime' 5-min CPU usage compares
+// to the given thresh with the given cmp function.
+//
+// ex: thresh_cpu(CpuHistory::Min1, 0.1, std::cmp::PartialOrd::lt) returns true
+//     if the 5-min CPU usage is less than 0.1 for the past minute
+//
+fn thresh_cpu<C>(history: CpuHistory, thresh: f64, cmp: C) -> ThreshResult
+    where C: Fn(&f64, &f64) -> bool {
+    let output = Command::new("uptime")
+        .output()?;
+    let uptime_str = String::from_utf8(output.stdout)
+        .unwrap_or(String::new());
+    let columns: Vec<&str> = uptime_str.split(" ").collect();
+    let cpu_usages: Vec<f64> = columns.iter()
+        .rev().take(3).map(|x| *x).collect::<Vec<&str>>().iter()
+        .rev()
+        .map(|x| *x)
+        .filter(|x| x.len() > 0)
+        .map(|x| str::parse::<f64>(&x[0..x.len()-1]).unwrap_or(std::f64::MAX))
+        .collect::<Vec<f64>>();
+    let idle: Vec<bool> = cpu_usages.iter()
+        .map(|x| cmp(x, &thresh))
+        .collect();
+    // idle is bools of [1min, 5min, 15min] CPU usage
+    let idx = match history {
+        CpuHistory::Min1 => 0,
+        CpuHistory::Min5 => 1,
+        CpuHistory::Min15 => 2,
+    };
+    Ok(*idle.get(idx).unwrap_or(&false))
+}
+
+fn main() {
+    println!("Hello, world!");
+    println!("Sec: {:?}", parse_w_time("10.45s"));
+    println!("Sec: {:?}", parse_w_time("1:11"));
+    println!("Sec: {:?}", parse_w_time("0:10m"));
+    loop {
+        println!("w min: {:?}", idle_w());
+        println!("xssstate min: {:?}", idle_xssstate());
+        println!("xprintidle min: {:?}", idle_xprintidle());
+        println!("cpu: {:?}", thresh_cpu(CpuHistory::Min5, 0.1, std::cmp::PartialOrd::lt));
+        std::thread::sleep(std::time::Duration::from_millis(2000));
+    }
+}