read values from collectd socket
This commit is contained in:
parent
45939cb382
commit
813b75fbc5
|
|
@ -5,3 +5,10 @@ version = 4
|
|||
[[package]]
|
||||
name = "protocol"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "waybar-collectd"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"protocol",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["protocol"]
|
||||
members = ["protocol", "waybar-collectd"]
|
||||
|
||||
|
|
|
|||
|
|
@ -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<String>),
|
||||
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 {}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
mod err;
|
||||
mod message;
|
||||
mod protocol;
|
||||
|
||||
use message::{create_request_message, parse_response_message};
|
||||
pub use protocol::Protocol;
|
||||
|
|
|
|||
|
|
@ -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<String>),
|
||||
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<f32, MessageError> {
|
||||
let mut lines = resp.split_terminator("\n");
|
||||
let header = lines.next().ok_or(MessageError::EmptyResponse)?;
|
||||
pub fn parse_response_header(header: String) -> Result<i32, ProtocolError> {
|
||||
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::<i32>()
|
||||
.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<String> = 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::<f32>().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<String>, name: String) -> Result<f32, ProtocolError> {
|
||||
let mut values: Vec<String> = 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::<f32>()
|
||||
.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<String> = 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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Protocol, ProtocolError> {
|
||||
let socket = UnixStream::connect(path)
|
||||
.map_err(|e| ProtocolError::CouldNotOpenSocket(e.to_string()))?;
|
||||
Ok(Protocol { socket: socket })
|
||||
}
|
||||
|
||||
pub fn read_line(&mut self) -> Result<String, ProtocolError> {
|
||||
let mut bytes: Vec<u8> = 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<f32, ProtocolError> {
|
||||
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::<Result<Vec<String>, ProtocolError>>()?;
|
||||
parse_response_message(lines, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "waybar-collectd"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
protocol = { path = "../protocol" }
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue