towerops-agent/src/poller/executor.rs

232 lines
7.3 KiB
Rust

use crate::buffer::Storage;
use crate::buffer::StorageError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ExecutorError {
#[error("Storage error: {0}")]
Storage(#[from] StorageError),
#[error("SNMP error: {0}")]
Snmp(#[from] crate::snmp::SnmpError),
#[error("Conversion error: {0}")]
Conversion(String),
}
pub type Result<T> = std::result::Result<T, ExecutorError>;
use crate::config::EquipmentConfig;
use crate::metrics::{InterfaceStat, Metric, SensorReading};
use crate::snmp::SnmpClient;
use crate::metrics::Timestamp;
use log::{error, info, warn};
/// Executor handles polling individual pieces of equipment
pub struct Executor {
snmp_client: SnmpClient,
storage: Storage,
}
impl Executor {
pub fn new(snmp_client: SnmpClient, storage: Storage) -> Self {
Self {
snmp_client,
storage,
}
}
/// Poll sensors for a piece of equipment
pub async fn poll_sensors(&self, equipment: &EquipmentConfig) -> Result<()> {
if !equipment.snmp.enabled || equipment.sensors.is_empty() {
return Ok(());
}
info!(
"Polling {} sensors for equipment: {}",
equipment.sensors.len(),
equipment.name
);
for sensor in &equipment.sensors {
match self
.snmp_client
.get(
&equipment.ip_address,
&equipment.snmp.community,
&equipment.snmp.version,
equipment.snmp.port,
&sensor.oid,
)
.await
{
Ok(value) => {
let raw_value = match value.as_f64() {
Some(v) => v,
None => {
warn!(
"Could not convert sensor value to number for sensor {}",
sensor.id
);
continue;
}
};
// Apply divisor if specified
let final_value = if let Some(divisor) = sensor.divisor {
if divisor != 0 {
raw_value / divisor as f64
} else {
raw_value
}
} else {
raw_value
};
let reading = SensorReading {
sensor_id: sensor.id.clone(),
value: final_value,
status: "ok".to_string(),
timestamp: Timestamp::now(),
};
if let Err(e) = self.storage.store_metric(&Metric::SensorReading(reading)) {
error!("Failed to store sensor reading: {}", e);
}
}
Err(e) => {
warn!(
"Failed to poll sensor {} for {}: {}",
sensor.oid, equipment.name, e
);
}
}
}
Ok(())
}
/// Poll interfaces for a piece of equipment
pub async fn poll_interfaces(&self, equipment: &EquipmentConfig) -> Result<()> {
if !equipment.snmp.enabled || equipment.interfaces.is_empty() {
return Ok(());
}
info!(
"Polling {} interfaces for equipment: {}",
equipment.interfaces.len(),
equipment.name
);
for interface in &equipment.interfaces {
// OIDs for interface statistics (from IF-MIB)
let if_in_octets_oid = format!("1.3.6.1.2.1.2.2.1.10.{}", interface.if_index);
let if_out_octets_oid = format!("1.3.6.1.2.1.2.2.1.16.{}", interface.if_index);
let if_in_errors_oid = format!("1.3.6.1.2.1.2.2.1.14.{}", interface.if_index);
let if_out_errors_oid = format!("1.3.6.1.2.1.2.2.1.20.{}", interface.if_index);
let if_in_discards_oid = format!("1.3.6.1.2.1.2.2.1.13.{}", interface.if_index);
let if_out_discards_oid = format!("1.3.6.1.2.1.2.2.1.19.{}", interface.if_index);
// Poll each counter
let in_octets = self
.get_counter(
&equipment.ip_address,
&equipment.snmp.community,
&equipment.snmp.version,
equipment.snmp.port,
&if_in_octets_oid,
)
.await
.unwrap_or(0);
let out_octets = self
.get_counter(
&equipment.ip_address,
&equipment.snmp.community,
&equipment.snmp.version,
equipment.snmp.port,
&if_out_octets_oid,
)
.await
.unwrap_or(0);
let in_errors = self
.get_counter(
&equipment.ip_address,
&equipment.snmp.community,
&equipment.snmp.version,
equipment.snmp.port,
&if_in_errors_oid,
)
.await
.unwrap_or(0);
let out_errors = self
.get_counter(
&equipment.ip_address,
&equipment.snmp.community,
&equipment.snmp.version,
equipment.snmp.port,
&if_out_errors_oid,
)
.await
.unwrap_or(0);
let in_discards = self
.get_counter(
&equipment.ip_address,
&equipment.snmp.community,
&equipment.snmp.version,
equipment.snmp.port,
&if_in_discards_oid,
)
.await
.unwrap_or(0);
let out_discards = self
.get_counter(
&equipment.ip_address,
&equipment.snmp.community,
&equipment.snmp.version,
equipment.snmp.port,
&if_out_discards_oid,
)
.await
.unwrap_or(0);
let stat = InterfaceStat {
interface_id: interface.id.clone(),
if_in_octets: in_octets,
if_out_octets: out_octets,
if_in_errors: in_errors,
if_out_errors: out_errors,
if_in_discards: in_discards,
if_out_discards: out_discards,
timestamp: Timestamp::now(),
};
if let Err(e) = self.storage.store_metric(&Metric::InterfaceStat(stat)) {
error!("Failed to store interface stat: {}", e);
}
}
Ok(())
}
async fn get_counter(
&self,
ip_address: &str,
community: &str,
version: &str,
port: u16,
oid: &str,
) -> Result<i64> {
let value = self
.snmp_client
.get(ip_address, community, version, port, oid)
.await?;
value
.as_i64()
.ok_or_else(|| ExecutorError::Conversion("Could not convert value to i64".into()))
}
}