pub mod agent;
pub mod error;
pub mod host;
pub mod id;
pub mod interface;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::time::{Duration, SystemTime};
use agent::{AgentInfo, AgentStatus};
use chrono::DateTime;
use error::NetzworkApiError;
use id::agent_uuid;
use interface::{NetInterface, NetInterfaceAddr};
use network_interface::{NetworkInterface, NetworkInterfaceConfig};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::host::HostInfo;
use crate::id::{host_uuid, interface_addr_uuid, interface_uuid};
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub struct Heartbeat {
pub hb_type: HeartbeatType,
pub timestamp: DateTime<chrono::Utc>,
pub hb_uuid: Uuid,
pub payload: Vec<HeartbeatPayload>,
}
impl Heartbeat {
pub fn new(
hb_type: HeartbeatType,
name: String,
agent_status: Option<AgentStatus>,
) -> Heartbeat {
let status = match agent_status {
Some(s) => s,
None => AgentStatus::Undefined,
};
let host_info = HostInfo::new();
let mut hb = Heartbeat {
hb_type: hb_type.clone(),
timestamp: DateTime::from(SystemTime::now()),
hb_uuid: Uuid::new_v4(),
payload: vec![
HeartbeatPayload::AgentInfo(AgentInfo {
host_uuid: host_info.host_uuid,
uuid: agent_uuid(&host_info.host_uuid, &name),
name,
status,
}),
HeartbeatPayload::HostInfo(host_info.clone()),
],
};
if hb_type == HeartbeatType::Full {
hb.payload
.push(HeartbeatPayload::BuildInfo(BuildInfo::new()));
#[cfg(target_os = "linux")]
{
hb.payload
.push(HeartbeatPayload::PSIInfo(PSIInfoResource::CPU));
hb.payload
.push(HeartbeatPayload::PSIInfo(PSIInfoResource::Memory));
hb.payload
.push(HeartbeatPayload::PSIInfo(PSIInfoResource::IO));
}
hb.payload.push(HeartbeatPayload::OSInfo(OSInfo::new()));
match NetworkInterface::show() {
Ok(network_interfaces) => {
for itf in network_interfaces.iter() {
println!("{:?}", itf);
if let Some(mac_addr_string) = &itf.mac_addr {
let maybe_mac_addr: Result<Vec<u8>, _> = mac_addr_string
.split(':')
.map(|s| u8::from_str_radix(s, 16))
.collect();
match maybe_mac_addr {
Ok(mac_addr) => {
let ni_info = NetInterface {
host_uuid: host_info.host_uuid,
uuid: interface_uuid(&host_info.host_uuid, &mac_addr),
name: itf.name.clone(),
mac_addr,
};
hb.payload
.push(HeartbeatPayload::NetInterface(ni_info.clone()));
for a in itf.addr.iter() {
let nia_info = NetInterfaceAddr {
iface_uuid: ni_info.uuid,
uuid: interface_addr_uuid(&ni_info.uuid, a.ip()),
ip_addr: a.ip().to_string(),
active: true,
};
hb.payload
.push(HeartbeatPayload::NetInterfaceAddr(nia_info));
}
},
Err(_) => eprintln!("Interface {:?} produced an error retrieving the MAC address, skipping for now (open issue)", itf),
};
} else {
eprintln!("Interface {:?} has an empty MAC address, skipping for now (open issue)", itf);
};
}
}
Err(_) => {
eprintln!("Could not retrieve network interfaces");
}
};
}
hb
}
pub fn compact(payload: Vec<HeartbeatPayloadType>) -> Heartbeat {
let mut hb = Heartbeat {
hb_type: HeartbeatType::Compact,
timestamp: DateTime::from(SystemTime::now()),
hb_uuid: Uuid::new_v4(),
payload: vec![],
};
for p in payload.iter() {
hb.payload.push(p.to_payload())
}
hb
}
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub enum HeartbeatPayloadType {
AgentInfo(AgentStatus, String),
HostInfo,
BuildInfo,
NetInterface(NetInterface),
NetInterfaceAddr(NetInterfaceAddr),
PSIInfo(PSIInfoResource),
OSInfo,
}
impl HeartbeatPayloadType {
pub fn to_payload(&self) -> HeartbeatPayload {
match self {
HeartbeatPayloadType::AgentInfo(status, name) => {
HeartbeatPayload::AgentInfo(AgentInfo {
status: status.to_owned(),
name: name.to_string(),
host_uuid: host_uuid().unwrap(),
uuid: agent_uuid(&host_uuid().unwrap(), name),
})
}
HeartbeatPayloadType::HostInfo => HeartbeatPayload::HostInfo(HostInfo::new()),
HeartbeatPayloadType::BuildInfo => HeartbeatPayload::BuildInfo(BuildInfo::new()),
HeartbeatPayloadType::NetInterface(ni) => HeartbeatPayload::NetInterface(ni.clone()),
HeartbeatPayloadType::NetInterfaceAddr(nia) => {
HeartbeatPayload::NetInterfaceAddr(nia.clone())
}
HeartbeatPayloadType::PSIInfo(resource) => {
HeartbeatPayload::PSIInfo(PSIInfo::new(resource))
}
HeartbeatPayloadType::OSInfo => HeartbeatPayload::OSInfo(OSInfo::new()),
}
}
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub enum HeartbeatPayload {
AgentInfo(AgentInfo),
HostInfo(HostInfo),
BuildInfo(BuildInfo),
NetInterface(NetInterface),
NetInterfaceAddr(NetInterfaceAddr),
PSIInfo(PSIInfo),
OSInfo(OSInfo),
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub enum HeartbeatType {
Full,
Compact,
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub struct BuildInfo {
build_timestamp: String,
build_date: String,
git_branch: String,
git_timestamp: String,
git_date: String,
git_hash: String,
git_describe: String,
rustc_host_triple: String,
rustc_version: String,
cargo_target_triple: String,
}
impl BuildInfo {
fn new() -> BuildInfo {
BuildInfo {
build_timestamp: String::from(env!("VERGEN_BUILD_TIMESTAMP")),
build_date: String::from(env!("VERGEN_BUILD_DATE")),
git_branch: String::from(env!("VERGEN_GIT_BRANCH")),
git_timestamp: String::from(env!("VERGEN_GIT_COMMIT_TIMESTAMP")),
git_date: String::from(env!("VERGEN_GIT_COMMIT_DATE")),
git_hash: String::from(env!("VERGEN_GIT_SHA")),
git_describe: String::from(env!("VERGEN_GIT_DESCRIBE")),
rustc_host_triple: String::from(env!("VERGEN_RUSTC_HOST_TRIPLE")),
rustc_version: String::from(env!("VERGEN_RUSTC_SEMVER")),
cargo_target_triple: String::from(env!("VERGEN_CARGO_TARGET_TRIPLE")),
}
}
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub struct ConnectivityMeasurementSample {
timestamp: SystemTime,
rtt: Duration,
fingerprint: [u8; 32],
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub struct PSIInfo {
resource: String,
lines: Vec<PSIInfoLine>,
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub enum PSIInfoResource {
CPU,
IO,
Memory,
}
impl PSIInfoResource {
fn to_string(&self) -> String {
match self {
PSIInfoResource::CPU => "cpu".to_string(),
PSIInfoResource::IO => "io".to_string(),
PSIInfoResource::Memory => "memory".to_string(),
}
}
fn to_proc_path(&self) -> String {
match self {
PSIInfoResource::CPU => "/proc/pressure/cpu".to_string(),
PSIInfoResource::IO => "/proc/pressure/io".to_string(),
PSIInfoResource::Memory => "/proc/pressure/memory".to_string(),
}
}
}
impl PSIInfo {
fn new(rs: &PSIInfoResource) -> PSIInfo {
let resource = rs.to_string();
let psifile_path = rs.to_proc_path();
let psifile = File::open(psifile_path).expect("could not open PSI file path");
let reader = BufReader::new(psifile);
let mut lines = Vec::<PSIInfoLine>::new();
for maybe_line in reader.lines() {
if let Ok(line) = maybe_line {
if let Ok(psi_info_line) = PSIInfoLine::from_line(&line) {
lines.push(psi_info_line);
}
}
}
PSIInfo { resource, lines }
}
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub struct PSIInfoLine {
pub share: String,
pub avg10: f64,
pub avg60: f64,
pub avg300: f64,
pub total: u64,
}
impl PSIInfoLine {
fn from_line(line: &str) -> Result<PSIInfoLine, NetzworkApiError> {
let mut line_elements = line.split_whitespace();
let share = match line_elements.next() {
Some(s) => match s {
"some" => Ok("some".to_string()),
"full" => Ok("full".to_string()),
&_ => Err(NetzworkApiError::SomeProblem(
"Error compiling PSI information: parsing share failed".to_string(),
)),
},
None => Err(NetzworkApiError::SomeProblem(
"Error compiling PSI information: parsing share failed".to_string(),
)),
}?;
let avg10: f64 = match line_elements.next() {
Some(s) => match s.replace("avg10=", "").parse::<f64>() {
Ok(a) => Ok(a),
Err(_) => Err(NetzworkApiError::SomeProblem(
"Error compiling PSI information: parsing avg10 failed".to_string(),
)),
},
None => Err(NetzworkApiError::SomeProblem(
"Error compiling PSI information: parsing avg10 failed".to_string(),
)),
}?;
let avg60: f64 = match line_elements.next() {
Some(s) => match s.replace("avg60=", "").parse::<f64>() {
Ok(a) => Ok(a),
Err(_) => Err(NetzworkApiError::SomeProblem(
"Error compiling PSI information: parsing avg60 failed".to_string(),
)),
},
None => Err(NetzworkApiError::SomeProblem(
"Error compiling PSI information: parsing avg60 failed".to_string(),
)),
}?;
let avg300: f64 = match line_elements.next() {
Some(s) => match s.replace("avg300=", "").parse::<f64>() {
Ok(a) => Ok(a),
Err(_) => Err(NetzworkApiError::SomeProblem(
"Error compiling PSI information: parsing avg300 failed".to_string(),
)),
},
None => Err(NetzworkApiError::SomeProblem(
"Error compiling PSI information: parsing avg300 failed".to_string(),
)),
}?;
let total: u64 = match line_elements.next() {
Some(s) => match s.replace("total=", "").parse::<u64>() {
Ok(a) => Ok(a),
Err(_) => Err(NetzworkApiError::SomeProblem(
"Error compiling PSI information: parsing total failed".to_string(),
)),
},
None => Err(NetzworkApiError::SomeProblem(
"Error compiling PSI information: parsing total failed".to_string(),
)),
}?;
Ok(PSIInfoLine {
share,
avg10,
avg60,
avg300,
total,
})
}
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub struct OSInfo {
os_type: String,
version: String,
bitness: String,
architecture: String,
}
impl OSInfo {
fn new() -> OSInfo {
let info = os_info::get();
OSInfo {
os_type: info.os_type().to_string(),
version: info.version().to_string(),
bitness: info.bitness().to_string(),
architecture: info.architecture().unwrap_or("unknown").to_string(),
}
}
}