Skip to content

Instantly share code, notes, and snippets.

@opensourcegeek
Created November 14, 2016 19:43
Show Gist options
  • Save opensourcegeek/db541448dce5879cd3e53e4bcd6e6e92 to your computer and use it in GitHub Desktop.
Save opensourcegeek/db541448dce5879cd3e53e4bcd6e6e92 to your computer and use it in GitHub Desktop.
Rust code to look for bacons and sausages
#[macro_use]
extern crate nickel;
extern crate rustc_serialize;
extern crate scan_dir;
extern crate csv;
use std::ops::Deref;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::{thread, time};
use std::collections::VecDeque;
use std::sync::Arc;
use std::sync::Mutex;
use std::io::prelude::*;
use std::fs::File;
use std::fs;
use std::process;
use std::env;
use scan_dir::ScanDir;
use std::str::FromStr;
use nickel::{Nickel, HttpRouter};
use rustc_serialize::json::{Json, ToJson};
const ONE_SEC_IN_MILLIS:u64 = 1000;
const MAX_LEN_Q:u64 = 60;
#[derive(Debug)]
struct CircularQ {
items: VecDeque<u32>
}
impl CircularQ {
fn new() -> CircularQ {
CircularQ {
items: VecDeque::<u32>::new()
}
}
fn push_item(&mut self, item: u32) -> () {
if self.items.len() >= MAX_LEN_Q as usize {
self.items.pop_front();
}
self.items.push_back(item);
}
fn get_items(&self) -> &VecDeque<u32> {
&self.items
}
}
impl ToJson for CircularQ {
fn to_json(&self) -> Json {
let mut is = Vec::new();
for i in &self.items {
is.push(i.to_json());
}
Json::Array(is)
}
}
struct Stats {
// Bacon/Serial name
name: String,
// Map<String, u32> - String is sausage number and we save u32 which is value
// sausage number starts with 1, where 1 is the total utilization in CircularQ.
// 2 is sausage 1, 3 is sausage 2 and so on. You can scan from sausage 1
// to sausage 9 at the moment.
utilization: BTreeMap<String, CircularQ>
}
type StatsTree = BTreeMap<String, Stats>;
impl Stats {
fn new() -> Stats {
Stats {
name: "".to_string(),
utilization: BTreeMap::<String, CircularQ>::new()
}
}
fn set_name(&mut self, name: String) -> () {
self.name = name;
}
fn set_utilization(&mut self, key: String, val: u32) -> () {
if self.utilization.contains_key(&key) {
if let Some(q) = self.utilization.get_mut(&key) {
q.push_item(val);
}
} else {
let mut q = CircularQ::new();
q.push_item(val);
self.utilization.insert(key, q);
}
}
fn get_average(&self, key: String) -> f32 {
match self.utilization.get(&key) {
Some(q) => {
let mut sum = 0;
for i in q.get_items() {
sum = sum + i;
}
let len = q.get_items().len() as u32;
(sum / len) as f32
}
None => {
// The key doesn't exist?? Would we ever hit this branch??
0.0
}
}
}
fn get_100_percent_usage(&self, key: String) -> f32 {
match self.utilization.get(&key) {
Some(q) => {
let mut times_100 = 0;
let percent_100: u32 = 100;
for i in q.get_items() {
if i == &percent_100 {
times_100 = times_100 + 1;
}
}
let len = q.get_items().len() as u32;
((times_100 / len) * 100) as f32
}
None => {
// The key doesn't exist?? Would we ever hit this branch??
0.0
}
}
}
}
impl ToJson for Stats {
fn to_json(&self) -> Json {
let mut map = BTreeMap::new();
map.insert("bacon".to_string(), self.name.to_json());
let sausage_indices: Vec<_> = self.utilization.keys().cloned().collect();
for sausage_index in sausage_indices {
let average = self.get_average(sausage_index.to_string());
let average_100 = self.get_100_percent_usage(sausage_index.to_string());
let avg_key = format!("{}-{}", "average-utilization".to_string(), sausage_index);
let hundred_percent_key = format!("{}-{}", "100-utilization".to_string(), sausage_index);
map.insert(avg_key, average.to_json());
map.insert(hundred_percent_key, average_100.to_json());
}
Json::Object(map)
}
}
fn get_bacon_name_and_metric_index(file_name: String) -> (String, String) {
let v: Vec<&str> = file_name.split(".log").collect();
let v2: Vec<&str> = v[0].split("/").collect();
let v3: Vec<&str> = v2[v2.len() -1].split("bacon_utilization_").collect();
let v4: Vec<&str> = v3[v3.len() -1].split("_").collect();
println!("{} --- {}", v4[0], v4[1]);
(v4[0].to_string(), v4[1].to_string())
}
fn start_producer_consumer(args: Vec<String>) -> () {
let ids = Arc::new(Mutex::new(BTreeMap::<String, Stats>::new()));
let m = ids.clone();
let mutex = ids.clone();
let csv_mutex = ids.clone();
let handle_two = thread::spawn(move || {
let mut counter = 0;
loop {
counter = counter + 1;
{
let directory = args[1].as_str();
// Scan for only log files
let all_log_files: Vec<_> = ScanDir::files().walk(directory, |iter| {
iter.filter(|&(_, ref name)| name.ends_with(".log"))
.map(|(ref entry, _)| entry.path())
.collect()
}).unwrap();
for log_file_path in all_log_files {
println!("Name: {:?}", log_file_path);
let mut f = File::open(log_file_path.clone()).expect("Unable to open file");
let mut s = String::new();
f.read_to_string(&mut s);
println!("{}", s);
let (bacon_name, metric_index) = get_bacon_name_and_metric_index(
log_file_path.to_str().unwrap().to_string());
let values:Vec<&str> = s.split(",").collect();
let val = values[1];
println!("Val for {} sausage (index) {} is {}", bacon_name, metric_index, val);
// Split value in string and also set the value
let mut ptr = mutex.lock().unwrap();
// This scope helps mutex unlock when it leaves this scope - without it
// this lock never gets released!
if !ptr.contains_key(&bacon_name) {
let mut stats = Stats::new();
stats.set_name(bacon_name.clone());
stats.set_utilization(metric_index, u32::from_str(val).unwrap());
ptr.insert(bacon_name.clone(), stats);
} else {
let mut stats = ptr.get_mut(&bacon_name).unwrap();
stats.set_utilization(metric_index, u32::from_str(val).unwrap());
}
}
}
thread::sleep(time::Duration::from_millis(ONE_SEC_IN_MILLIS));
}
});
let handle_one = thread::spawn(move || {
let mut server = Nickel::new();
server.get("/utilization.json", middleware! { |req|
println!("Trying to get lock");
let ptr = m.lock().unwrap();
// loop stats ptr and build btreemap/vector and do a to_json?
//ptr.to_json()
ptr.to_json()
});
server.get("/utilization.csv", middleware! { |req|
println!("Trying to get lock");
let ptr = csv_mutex.lock().unwrap();
// loop stats ptr and build btreemap/vector and do a to_json?
//ptr.to_json()
let mut records = Vec::new();
for (serial, stats) in ptr.iter() {
let mut record = Vec::new();
record.push(stats.name.clone());
for (sausage_index, q) in stats.utilization.iter() {
let average = stats.get_average(sausage_index.to_string());
let average_100 = stats.get_100_percent_usage(sausage_index.to_string());
record.push(average.to_string().clone());
record.push(average_100.to_string().clone());
}
records.push(record);
}
let mut writer = csv::Writer::from_memory();
for record in records {
writer.encode(record);
}
writer.into_string()
});
server.get("/boo", middleware! { |req|
let mut map = BTreeMap::new();
map.insert("boo".to_string(), "boo-boo".to_json());
Json::Object(map)
});
server.listen("127.0.0.1:16080").unwrap();
});
handle_two.join().unwrap();
handle_one.join().unwrap();
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
println!("Not enough arguments - missing path for log file directory");
process::exit(1);
}
start_producer_consumer(args);
}
@leshow
Copy link

leshow commented Nov 25, 2016

line 111, you can lose the for loop and use a fold or a sum. Since VecDeque implements iterator.

also, those get_average branches where if the Option is zero you return 0.0; You might want to have that function return a Result, since it looks like if it is None, then you're in an error case from your commentary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment