From 813b75fbc54507ff166340620e52209f6eb02e63 Mon Sep 17 00:00:00 2001 From: Ondrej Novak Date: Sun, 29 Mar 2026 18:49:23 +0200 Subject: [PATCH] read values from collectd socket --- Cargo.lock | 7 ++ Cargo.toml | 2 +- protocol/src/err.rs | 45 +++++++++++ protocol/src/lib.rs | 4 +- protocol/src/message.rs | 148 +++++++++++++++--------------------- protocol/src/protocol.rs | 53 +++++++++++++ waybar-collectd/Cargo.toml | 7 ++ waybar-collectd/src/main.rs | 12 +++ 8 files changed, 188 insertions(+), 90 deletions(-) create mode 100644 protocol/src/err.rs create mode 100644 protocol/src/protocol.rs create mode 100644 waybar-collectd/Cargo.toml create mode 100644 waybar-collectd/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index a3de8bf..2ee57ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,3 +5,10 @@ version = 4 [[package]] name = "protocol" version = "0.1.0" + +[[package]] +name = "waybar-collectd" +version = "0.1.0" +dependencies = [ + "protocol", +] diff --git a/Cargo.toml b/Cargo.toml index fa26fdb..9c1303c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,4 @@ [workspace] resolver = "3" -members = ["protocol"] +members = ["protocol", "waybar-collectd"] diff --git a/protocol/src/err.rs b/protocol/src/err.rs new file mode 100644 index 0000000..ab730f0 --- /dev/null +++ b/protocol/src/err.rs @@ -0,0 +1,45 @@ +use std::error::Error; +use std::fmt; + +#[derive(PartialEq, Debug)] +pub enum ProtocolError { + EmptyResponse, + HeaderMissingValueCount, + HeaderValueCountIncorrectFormat(String), + MalformedValueReceived(String), + ValueNotFound(String, Vec), + CouldNotOpenSocket(String), + CouldNotDecodeValue, + CouldNotReadOrWriteSocket(String), +} + +impl fmt::Display for ProtocolError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let message = match *self { + Self::EmptyResponse => "Got an empty response".to_string(), + Self::HeaderMissingValueCount => "Value count not found in the header".to_string(), + Self::HeaderValueCountIncorrectFormat(ref val) => { + format!("String could not be parsed as value count: {}", val) + } + Self::MalformedValueReceived(ref val) => { + format!("Could not parse value as float: {}", val) + } + Self::ValueNotFound(ref name, ref values) => format!( + "Value not found in the received values: {} in [{}]", + name, + values.join(", ") + ), + Self::CouldNotOpenSocket(ref reason) => { + format!("Could not open unix socket: {}", reason) + } + Self::CouldNotDecodeValue => "Could not decode received bytes".to_string(), + Self::CouldNotReadOrWriteSocket(ref reason) => { + format!("Could not read or write socket: {}", reason) + } + }; + + f.write_str(&message) + } +} + +impl Error for ProtocolError {} diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index 5eba4e2..afd8956 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -1,3 +1,5 @@ +mod err; mod message; +mod protocol; -use message::{create_request_message, parse_response_message}; +pub use protocol::Protocol; diff --git a/protocol/src/message.rs b/protocol/src/message.rs index f9a463b..610bbce 100644 --- a/protocol/src/message.rs +++ b/protocol/src/message.rs @@ -1,77 +1,46 @@ -use std::error::Error; -use std::fmt; +use crate::err::ProtocolError; -#[derive(PartialEq, Debug)] -pub enum MessageError { - EmptyResponse, - HeaderMissingValueCount, - HeaderValueCountIncorrectFormat(String), - MalformedValueReceived(String), - ValueNotFound(String, Vec), +pub fn create_request_message(value: &String) -> String { + format!("GETVAL {}\n", value) } -impl fmt::Display for MessageError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let message = match *self { - Self::EmptyResponse => "Got an empty response".to_string(), - Self::HeaderMissingValueCount => "Value count not found in the header".to_string(), - Self::HeaderValueCountIncorrectFormat(ref val) => { - format!("String could not be parsed as value count: {}", val) - } - Self::MalformedValueReceived(ref val) => { - format!("Could not parse value as float: {}", val) - } - Self::ValueNotFound(ref name, ref values) => format!( - "Value not found in the received values: {} in [{}]", - name, - values.join(", ") - ), - }; - - f.write_str(&message) - } -} - -impl Error for MessageError {} - -pub fn create_request_message(value: String) -> String { - format!("GETVAL {}", value) -} - -pub fn parse_response_message(resp: String, name: String) -> Result { - let mut lines = resp.split_terminator("\n"); - let header = lines.next().ok_or(MessageError::EmptyResponse)?; +pub fn parse_response_header(header: String) -> Result { let num_values_raw = header .split(" ") .next() - .ok_or(MessageError::HeaderMissingValueCount)?; - let num_values = num_values_raw + .ok_or(ProtocolError::HeaderMissingValueCount)?; + let num = num_values_raw .parse::() - .map_err(|_| MessageError::HeaderValueCountIncorrectFormat(num_values_raw.to_string()))?; + .map_err(|_| ProtocolError::HeaderValueCountIncorrectFormat(num_values_raw.to_string()))?; - if num_values < 0 { - Err(MessageError::ValueNotFound(name, vec![])) + if num < 1 { + Err(ProtocolError::EmptyResponse) } else { - let mut values: Vec = vec![]; - - for line in lines { - let mut line_segment = line.split("="); - if let Some(val_name) = line_segment.next() { - if val_name == name - && let Some(val_raw) = line_segment.next() - { - return val_raw.parse::().map_err(|_| { - MessageError::MalformedValueReceived(num_values_raw.to_string()) - }); - } - values.push(val_name.to_string()); - } - } - - Err(MessageError::ValueNotFound(name, values)) + Ok(num) } } +pub fn parse_response_message(lines: Vec, name: String) -> Result { + let mut values: Vec = vec![]; + + for line in lines { + let mut line_segment = line.split("="); + if let Some(val_name) = line_segment.next() { + if val_name == name + && let Some(val_raw) = line_segment.next() + { + return val_raw + .trim() + .parse::() + .map_err(|_| ProtocolError::MalformedValueReceived(val_raw.to_string())); + } + values.push(val_name.to_string()); + } + } + + Err(ProtocolError::ValueNotFound(name, values)) +} + #[cfg(test)] mod tests { use super::*; @@ -85,65 +54,68 @@ mod tests { #[test] fn test_create_request() { assert_eq!( - create_request_message("some/value".to_string()), - "GETVAL some/value", + create_request_message(&"some/value".to_string()), + "GETVAL some/value\n", ); } + #[test] + fn test_parse_header_single_value() { + let result = parse_response_header("1 Value found\n".to_string()); + assert_eq!(result.unwrap(), 1); + } + + #[test] + fn test_parse_header_multiple_values() { + let result = parse_response_header("5 Values found\n".to_string()); + assert_eq!(result.unwrap(), 5); + } + #[test] fn test_parse_response_message_simple() { - let resp = vec!["1 Value found", "value=4.5"].join("\n"); - let result = parse_response_message(resp, "value".to_string()); + let result = parse_response_message(vec!["value=4.5".to_string()], "value".to_string()); assert_almost_equal!(result.unwrap(), 4.5, 0.1); } #[test] fn test_parse_response_message_exponential() { - let resp = vec!["1 Value found", "value=4.514824e+02"].join("\n"); - let result = parse_response_message(resp, "value".to_string()); + let result = + parse_response_message(vec!["value=9.401652e+01".to_string()], "value".to_string()); assert_almost_equal!(result.unwrap(), 451.4824, 0.1); } #[test] fn test_parse_response_message_multiple_values() { - let resp = vec!["3 Values found", "v1=4.514824e+09", "v2=1.234", "v3=0.1"].join("\n"); + let resp: Vec = vec!["v1=4.514824e+09", "v2=1.234", "v3=0.1"] + .iter() + .map(|s| s.to_string()) + .collect(); let result = parse_response_message(resp, "v2".to_string()); assert_almost_equal!(result.unwrap(), 1.234, 0.1); } #[test] fn test_parse_response_message_multiple_values_not_found() { - let resp = vec!["3 Values found", "v1=4.514824e+09", "v2=1.234", "v3=0.1"].join("\n"); + let resp = vec!["v1=4.514824e+09", "v2=1.234", "v3=0.1"] + .iter() + .map(|s| s.to_string()) + .collect(); let result = parse_response_message(resp, "v10".to_string()); assert!(result.is_err()); assert_eq!( result, - Err(MessageError::ValueNotFound( + Err(ProtocolError::ValueNotFound( "v10".to_string(), vec!["v1".to_string(), "v2".to_string(), "v3".to_string()] )) ); } - #[test] - fn test_parse_response_message_no_values() { - let resp = "0 Values found".to_string(); - let result = parse_response_message(resp, "value".to_string()); - assert!(result.is_err()); - assert_eq!( - result, - Err(MessageError::ValueNotFound("value".to_string(), vec![])) - ); - } - #[test] fn test_parse_response_message_field_not_found() { - let resp = "-1 Type `loadx' is unknown.".to_string(); - let result = parse_response_message(resp, "value".to_string()); + let resp = "-1 Type `loadx' is unknown.\n".to_string(); + let result = parse_response_header(resp); assert!(result.is_err()); - assert_eq!( - result, - Err(MessageError::ValueNotFound("value".to_string(), vec![])) - ); + assert_eq!(result, Err(ProtocolError::EmptyResponse)); } } diff --git a/protocol/src/protocol.rs b/protocol/src/protocol.rs new file mode 100644 index 0000000..ad1ccb4 --- /dev/null +++ b/protocol/src/protocol.rs @@ -0,0 +1,53 @@ +use std::io::{Read, Write}; +use std::os::unix::net::UnixStream; + +use crate::err::ProtocolError; +use crate::message::{create_request_message, parse_response_header, parse_response_message}; + +pub struct Protocol { + socket: UnixStream, +} + +impl Protocol { + pub fn new(path: &str) -> Result { + let socket = UnixStream::connect(path) + .map_err(|e| ProtocolError::CouldNotOpenSocket(e.to_string()))?; + Ok(Protocol { socket: socket }) + } + + pub fn read_line(&mut self) -> Result { + let mut bytes: Vec = vec![]; + let mut buf = [0u8; 1]; + loop { + self.socket + .read_exact(buf.as_mut_slice()) + .map_err(|e| ProtocolError::CouldNotReadOrWriteSocket(e.to_string()))?; + bytes.push(buf[0]); + + if buf[0] as char == '\n' { + break; + } + } + + String::from_utf8(bytes.clone()).map_err(|_| ProtocolError::CouldNotDecodeValue) + } + + pub fn get(&mut self, path: String, name: String) -> Result { + let req = create_request_message(&path); + let raw_req = req.as_bytes(); + self.socket + .write_all(raw_req) + .map_err(|e| ProtocolError::CouldNotReadOrWriteSocket(e.to_string()))?; + + let header = self.read_line()?; + let num_values = parse_response_header(header)?; + if num_values < 1 { + Err(ProtocolError::ValueNotFound(name, vec![])) + } else { + let lines = (0..num_values) + .map(|_| self.read_line()) + .collect::, ProtocolError>>()?; + parse_response_message(lines, name) + } + } +} diff --git a/waybar-collectd/Cargo.toml b/waybar-collectd/Cargo.toml new file mode 100644 index 0000000..447620d --- /dev/null +++ b/waybar-collectd/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "waybar-collectd" +version = "0.1.0" +edition = "2024" + +[dependencies] +protocol = { path = "../protocol" } diff --git a/waybar-collectd/src/main.rs b/waybar-collectd/src/main.rs new file mode 100644 index 0000000..30605ae --- /dev/null +++ b/waybar-collectd/src/main.rs @@ -0,0 +1,12 @@ +use protocol::Protocol; + +fn main() { + if let Ok(mut proto) = Protocol::new("/var/run/collectd-unixsock") { + let rec = proto.get("tp-on/cpu-0/cpu-idle".to_string(), "value".to_string()); + if let Ok(val) = rec { + println!("Got {}", val); + } else { + println!("{}", rec.unwrap_err()); + } + } +}