1pub mod agent;
2pub mod error;
3pub mod host;
4pub mod id;
5pub mod interface;
6
7use std::fs::File;
8use std::io::{BufRead, BufReader};
9use std::time::{Duration, SystemTime};
10
11use agent::{AgentInfo, AgentStatus};
12use chrono::DateTime;
13use error::NetzworkApiError;
14use id::agent_uuid;
15use interface::{NetInterface, NetInterfaceAddr};
16use network_interface::{NetworkInterface, NetworkInterfaceConfig};
17use serde::{Deserialize, Serialize};
18use uuid::Uuid;
19
20use crate::host::HostInfo;
21use crate::id::{host_uuid, interface_addr_uuid, interface_uuid};
22
23#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
24pub struct Heartbeat {
25 pub hb_type: HeartbeatType,
27 pub timestamp: DateTime<chrono::Utc>,
29 pub hb_uuid: Uuid,
31 pub payload: Vec<HeartbeatPayload>,
33}
34
35impl Heartbeat {
36 pub fn new(
37 hb_type: HeartbeatType,
38 name: String,
39 agent_status: Option<AgentStatus>,
40 ) -> Heartbeat {
41 let status = match agent_status {
42 Some(s) => s,
43 None => AgentStatus::Undefined,
44 };
45 let host_info = HostInfo::new();
46 let mut hb = Heartbeat {
47 hb_type: hb_type.clone(),
48 timestamp: DateTime::from(SystemTime::now()),
49 hb_uuid: Uuid::new_v4(),
50 payload: vec![
51 HeartbeatPayload::AgentInfo(AgentInfo {
52 host_uuid: host_info.host_uuid,
53 uuid: agent_uuid(&host_info.host_uuid, &name),
54 name,
55 status,
56 }),
57 HeartbeatPayload::HostInfo(host_info.clone()),
58 ],
59 };
60 if hb_type == HeartbeatType::Full {
61 hb.payload
62 .push(HeartbeatPayload::BuildInfo(BuildInfo::new()));
63 #[cfg(target_os = "linux")]
64 {
65 hb.payload
66 .push(HeartbeatPayload::PSIInfo(PSIInfoResource::CPU));
67 hb.payload
68 .push(HeartbeatPayload::PSIInfo(PSIInfoResource::Memory));
69 hb.payload
70 .push(HeartbeatPayload::PSIInfo(PSIInfoResource::IO));
71 }
72 hb.payload
73 .push(HeartbeatPayload::OSInfo(OSInfo::new(host_uuid().unwrap())));
74 match NetworkInterface::show() {
75 Ok(network_interfaces) => {
76 for itf in network_interfaces.iter() {
77 println!("{:?}", itf);
78 if let Some(mac_addr_string) = &itf.mac_addr {
79 let maybe_mac_addr: Result<Vec<u8>, _> = mac_addr_string
80 .split(':')
81 .map(|s| u8::from_str_radix(s, 16))
82 .collect();
83 match maybe_mac_addr {
84 Ok(mac_addr) => {
85 let ni_info = NetInterface {
86 host_uuid: host_info.host_uuid,
87 uuid: interface_uuid(&host_info.host_uuid, &mac_addr),
88 name: itf.name.clone(),
89 mac_addr,
90 };
91 hb.payload
92 .push(HeartbeatPayload::NetInterface(ni_info.clone()));
93 for a in itf.addr.iter() {
94 let nia_info = NetInterfaceAddr {
95 iface_uuid: ni_info.uuid,
96 uuid: interface_addr_uuid(&ni_info.uuid, a.ip()),
97 ip_addr: a.ip().to_string(),
98 active: true,
99 };
100 hb.payload
101 .push(HeartbeatPayload::NetInterfaceAddr(nia_info));
102 }
103 },
104 Err(_) => eprintln!("Interface {:?} produced an error retrieving the MAC address, skipping for now (open issue)", itf),
105 };
106 } else {
107 eprintln!("Interface {:?} has an empty MAC address, skipping for now (open issue)", itf);
108 };
109 }
110 }
111 Err(_) => {
112 eprintln!("Could not retrieve network interfaces");
113 }
114 };
115 }
116 hb
117 }
141 pub fn compact(payload: Vec<HeartbeatPayloadType>) -> Heartbeat {
142 let mut hb = Heartbeat {
143 hb_type: HeartbeatType::Compact,
144 timestamp: DateTime::from(SystemTime::now()),
145 hb_uuid: Uuid::new_v4(),
146 payload: vec![],
147 };
148 for p in payload.iter() {
149 hb.payload.push(p.to_payload())
150 }
151 hb
152 }
153}
154
155#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
156pub enum HeartbeatPayloadType {
157 AgentInfo(AgentStatus, String),
158 HostInfo,
159 BuildInfo,
160 NetInterface(NetInterface),
161 NetInterfaceAddr(NetInterfaceAddr),
162 PSIInfo(PSIInfoResource),
163 OSInfo,
164}
165impl HeartbeatPayloadType {
166 pub fn to_payload(&self) -> HeartbeatPayload {
167 match self {
168 HeartbeatPayloadType::AgentInfo(status, name) => {
169 HeartbeatPayload::AgentInfo(AgentInfo {
170 status: status.to_owned(),
171 name: name.to_string(),
172 host_uuid: host_uuid().unwrap(),
173 uuid: agent_uuid(&host_uuid().unwrap(), name),
174 })
175 }
176 HeartbeatPayloadType::HostInfo => HeartbeatPayload::HostInfo(HostInfo::new()),
177 HeartbeatPayloadType::BuildInfo => HeartbeatPayload::BuildInfo(BuildInfo::new()),
178 HeartbeatPayloadType::NetInterface(ni) => HeartbeatPayload::NetInterface(ni.clone()),
179 HeartbeatPayloadType::NetInterfaceAddr(nia) => {
180 HeartbeatPayload::NetInterfaceAddr(nia.clone())
181 }
182 HeartbeatPayloadType::PSIInfo(resource) => {
183 HeartbeatPayload::PSIInfo(PSIInfo::new(resource))
184 }
185 HeartbeatPayloadType::OSInfo => {
186 HeartbeatPayload::OSInfo(OSInfo::new(host_uuid().unwrap()))
187 }
188 }
189 }
190}
191
192#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
193pub enum HeartbeatPayload {
194 AgentInfo(AgentInfo),
195 HostInfo(HostInfo),
196 BuildInfo(BuildInfo),
197 NetInterface(NetInterface),
198 NetInterfaceAddr(NetInterfaceAddr),
199 PSIInfo(PSIInfo),
200 OSInfo(OSInfo),
201}
202
203#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
204pub enum HeartbeatType {
205 Full,
207 Compact,
210}
211
212#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
213pub struct BuildInfo {
214 build_timestamp: String,
217 build_date: String,
218 git_branch: String,
219 git_timestamp: String,
220 git_date: String,
221 git_hash: String,
222 git_describe: String,
223 rustc_host_triple: String,
224 rustc_version: String,
225 cargo_target_triple: String,
226}
227
228impl BuildInfo {
229 fn new() -> BuildInfo {
230 BuildInfo {
231 build_timestamp: String::from(env!("VERGEN_BUILD_TIMESTAMP")),
232 build_date: String::from(env!("VERGEN_BUILD_DATE")),
233 git_branch: String::from(env!("VERGEN_GIT_BRANCH")),
234 git_timestamp: String::from(env!("VERGEN_GIT_COMMIT_TIMESTAMP")),
235 git_date: String::from(env!("VERGEN_GIT_COMMIT_DATE")),
236 git_hash: String::from(env!("VERGEN_GIT_SHA")),
237 git_describe: String::from(env!("VERGEN_GIT_DESCRIBE")),
238 rustc_host_triple: String::from(env!("VERGEN_RUSTC_HOST_TRIPLE")),
239 rustc_version: String::from(env!("VERGEN_RUSTC_SEMVER")),
240 cargo_target_triple: String::from(env!("VERGEN_CARGO_TARGET_TRIPLE")),
241 }
242 }
243}
244
245#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
246pub struct ConnectivityMeasurementSample {
247 timestamp: SystemTime,
248 rtt: Duration,
249 fingerprint: [u8; 32],
250}
251
252#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
253pub struct PSIInfo {
254 resource: String,
255 lines: Vec<PSIInfoLine>,
256}
257
258#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
259pub enum PSIInfoResource {
260 CPU,
261 IO,
262 Memory,
263}
264
265impl PSIInfoResource {
266 fn to_string(&self) -> String {
267 match self {
268 PSIInfoResource::CPU => "cpu".to_string(),
269 PSIInfoResource::IO => "io".to_string(),
270 PSIInfoResource::Memory => "memory".to_string(),
271 }
272 }
273 fn to_proc_path(&self) -> String {
274 match self {
275 PSIInfoResource::CPU => "/proc/pressure/cpu".to_string(),
276 PSIInfoResource::IO => "/proc/pressure/io".to_string(),
277 PSIInfoResource::Memory => "/proc/pressure/memory".to_string(),
278 }
279 }
280}
281
282impl PSIInfo {
283 fn new(rs: &PSIInfoResource) -> PSIInfo {
284 let resource = rs.to_string();
285 let psifile_path = rs.to_proc_path();
286 let psifile = File::open(psifile_path).expect("could not open PSI file path");
287 let reader = BufReader::new(psifile);
288 let mut lines = Vec::<PSIInfoLine>::new();
289 for maybe_line in reader.lines() {
290 if let Ok(line) = maybe_line {
291 if let Ok(psi_info_line) = PSIInfoLine::from_line(&line) {
292 lines.push(psi_info_line);
293 }
294 }
295 }
296 PSIInfo { resource, lines }
297 }
298}
299
300#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
301pub struct PSIInfoLine {
302 pub share: String,
303 pub avg10: f64,
304 pub avg60: f64,
305 pub avg300: f64,
306 pub total: u64,
307}
308
309impl PSIInfoLine {
310 fn from_line(line: &str) -> Result<PSIInfoLine, NetzworkApiError> {
311 let mut line_elements = line.split_whitespace();
312 let share = match line_elements.next() {
313 Some(s) => match s {
314 "some" => Ok("some".to_string()),
315 "full" => Ok("full".to_string()),
316 &_ => Err(NetzworkApiError::SomeProblem(
317 "Error compiling PSI information: parsing share failed".to_string(),
318 )),
319 },
320 None => Err(NetzworkApiError::SomeProblem(
321 "Error compiling PSI information: parsing share failed".to_string(),
322 )),
323 }?;
324 let avg10: f64 = match line_elements.next() {
325 Some(s) => match s.replace("avg10=", "").parse::<f64>() {
326 Ok(a) => Ok(a),
327 Err(_) => Err(NetzworkApiError::SomeProblem(
328 "Error compiling PSI information: parsing avg10 failed".to_string(),
329 )),
330 },
331 None => Err(NetzworkApiError::SomeProblem(
332 "Error compiling PSI information: parsing avg10 failed".to_string(),
333 )),
334 }?;
335 let avg60: f64 = match line_elements.next() {
336 Some(s) => match s.replace("avg60=", "").parse::<f64>() {
337 Ok(a) => Ok(a),
338 Err(_) => Err(NetzworkApiError::SomeProblem(
339 "Error compiling PSI information: parsing avg60 failed".to_string(),
340 )),
341 },
342 None => Err(NetzworkApiError::SomeProblem(
343 "Error compiling PSI information: parsing avg60 failed".to_string(),
344 )),
345 }?;
346 let avg300: f64 = match line_elements.next() {
347 Some(s) => match s.replace("avg300=", "").parse::<f64>() {
348 Ok(a) => Ok(a),
349 Err(_) => Err(NetzworkApiError::SomeProblem(
350 "Error compiling PSI information: parsing avg300 failed".to_string(),
351 )),
352 },
353 None => Err(NetzworkApiError::SomeProblem(
354 "Error compiling PSI information: parsing avg300 failed".to_string(),
355 )),
356 }?;
357 let total: u64 = match line_elements.next() {
358 Some(s) => match s.replace("total=", "").parse::<u64>() {
359 Ok(a) => Ok(a),
360 Err(_) => Err(NetzworkApiError::SomeProblem(
361 "Error compiling PSI information: parsing total failed".to_string(),
362 )),
363 },
364 None => Err(NetzworkApiError::SomeProblem(
365 "Error compiling PSI information: parsing total failed".to_string(),
366 )),
367 }?;
368 Ok(PSIInfoLine {
369 share,
370 avg10,
371 avg60,
372 avg300,
373 total,
374 })
375 }
376}
377
378#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
433pub struct OSInfo {
434 host_uuid: Uuid,
435 os_type: String,
436 version: String,
437 bitness: String,
438 architecture: String,
439}
440impl OSInfo {
441 fn new(host_uuid: Uuid) -> OSInfo {
442 let info = os_info::get();
443 OSInfo {
444 host_uuid,
445 os_type: info.os_type().to_string(),
446 version: info.version().to_string(),
447 bitness: info.bitness().to_string(),
448 architecture: info.architecture().unwrap_or("unknown").to_string(),
449 }
450 }
451}