use crate::exporters::*;
use crate::sensors::{Sensor, Topology};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::fs::File;
use std::path::PathBuf;
use std::thread;
use std::time::{Duration, Instant};
pub struct JSONExporter {
topology: Topology,
reports: Vec<Report>,
}
impl Exporter for JSONExporter {
fn run(&mut self, parameters: ArgMatches) {
self.runner(parameters);
}
fn get_options() -> HashMap<String, ExporterOption> {
let mut options = HashMap::new();
options.insert(
String::from("timeout"),
ExporterOption {
default_value: Some(String::from("10")),
long: String::from("timeout"),
short: String::from("t"),
required: false,
takes_value: true,
help: String::from("Maximum time spent measuring, in seconds."),
},
);
options.insert(
String::from("step_duration"),
ExporterOption {
default_value: Some(String::from("2")),
long: String::from("step"),
short: String::from("s"),
required: false,
takes_value: true,
help: String::from("Set measurement step duration in second."),
},
);
options.insert(
String::from("step_duration_nano"),
ExporterOption {
default_value: Some(String::from("0")),
long: String::from("step_nano"),
short: String::from("n"),
required: false,
takes_value: true,
help: String::from("Set measurement step duration in nano second."),
},
);
options.insert(
String::from("file_path"),
ExporterOption {
default_value: Some(String::from("")),
long: String::from("file"),
short: String::from("f"),
required: false,
takes_value: true,
help: String::from("Destination file for the report."),
},
);
options
}
}
#[derive(Serialize, Deserialize)]
struct Domain {
name: String,
consumption: f32,
}
#[derive(Serialize, Deserialize)]
struct Socket {
id: u16,
consumption: f32,
domains: Vec<Domain>,
}
#[derive(Serialize, Deserialize)]
struct Consumer {
exe: PathBuf,
pid: i32,
consumption: f32,
}
#[derive(Serialize, Deserialize)]
struct Report {
host: f32,
consumers: Vec<Consumer>,
sockets: Vec<Socket>,
}
impl JSONExporter {
pub fn new(mut sensor: Box<dyn Sensor>) -> JSONExporter {
let some_topology = *sensor.get_topology();
JSONExporter {
topology: some_topology.unwrap(),
reports: Vec::new(),
}
}
pub fn runner(&mut self, parameters: ArgMatches) {
let timeout = parameters.value_of("timeout").unwrap();
if timeout.is_empty() {
self.iterate(¶meters);
} else {
let now = Instant::now();
let timeout_secs: u64 = timeout.parse().unwrap();
let step_duration: u64 = parameters
.value_of("step_duration")
.unwrap()
.parse()
.expect("Wrong step_duration value, should be a number of seconds");
let step_duration_nano: u32 = parameters
.value_of("step_duration_nano")
.unwrap()
.parse()
.expect("Wrong step_duration_nano value, should be a number of nano seconds");
info!("Measurement step is: {}s", step_duration);
while now.elapsed().as_secs() <= timeout_secs {
self.iterate(¶meters);
thread::sleep(Duration::new(step_duration, step_duration_nano));
}
}
}
fn iterate(&mut self, parameters: &ArgMatches) {
self.topology.refresh();
self.retrieve_metrics(¶meters);
}
fn retrieve_metrics(&mut self, parameters: &ArgMatches) {
let host_power = self
.topology
.get_records_diff_power_microwatts()
.map(|record| record.value.parse::<u64>().unwrap())
.unwrap_or(0);
let host_stat = match self.topology.get_stats_diff() {
Some(value) => value,
None => return,
};
let consumers = self.topology.proc_tracker.get_top_consumers(10);
let top_consumers = consumers
.iter()
.map(|(process, value)| {
let host_time = host_stat.total_time_jiffies();
Consumer {
exe: process.exe().unwrap_or_default(),
pid: process.pid,
consumption: ((*value as f32
/ (host_time * procfs::ticks_per_second().unwrap() as f32))
* host_power as f32),
}
})
.collect::<Vec<_>>();
let names = ["core", "uncore", "dram"];
let all_sockets = self
.topology
.get_sockets_passive()
.iter()
.map(|socket| {
let socket_power = socket
.get_records_diff_power_microwatts()
.map(|record| record.value.parse::<u64>().unwrap())
.unwrap_or(0);
let domains = socket
.get_domains_passive()
.iter()
.map(|d| d.get_records_diff_power_microwatts())
.map(|record| record.map(|d| d.value))
.enumerate()
.map(|(index, d)| {
let domain_power =
d.map(|value| value.parse::<u64>().unwrap()).unwrap_or(0);
Domain {
name: names[index].to_string(),
consumption: domain_power as f32,
}
})
.collect::<Vec<_>>();
Socket {
id: socket.id,
consumption: (socket_power as f32),
domains,
}
})
.collect::<Vec<_>>();
let report = Report {
host: host_power as f32,
consumers: top_consumers,
sockets: all_sockets,
};
let file_path = parameters.value_of("file_path").unwrap();
if file_path.is_empty() {
let json: String = serde_json::to_string(&report).expect("Unable to parse report");
println!("{}", &json);
} else {
self.reports.push(report);
let json: String =
serde_json::to_string(&self.reports).expect("Unable to parse report");
let _ = File::create(file_path);
fs::write(file_path, &json).expect("Unable to write file");
}
}
}
#[cfg(test)]
mod tests {
}