Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to calculate a UPS power value when none available - "UPS_POWER_FROM_LOAD_PERCENTAGE" #37

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ Use e.g. `1` for stable v1.y.z releases and `latest` for bleeding/dev/unstable r
- `HTTP_PORT` (defaults to `9995`): The HTTP server port.
- `HTTP_PATH` (defaults to `nut`): The HTTP server metrics path. You may want to set it to `/metrics` on new setups to avoid extra Prometheus configuration (not changed here due to compatibility).
- `PRINT_METRICS_AND_EXIT` (defaults to `false`): Print a Markdown-formatted table consisting of all metrics and then immediately exit. Used mainly for generating documentation.
- `UPS_POWER_FROM_LOAD_PERCENTAGE` (defaults to `false`): when set to `true` and a value for `ups.power` is not available from a UPS, an approximate value will be calculated for the metric `nut_power_watts` (Apparent Power). Useful for UPS units that do not report the load in Watts, like some CyberPower units. The calculation will be: `( ups.load / 100.00 ) * ups.realpower.nominal`.

## Metrics

Expand Down
9 changes: 8 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub struct Config {
pub http_port: u16,
pub http_path: String,
pub print_metrics_and_exit: bool,
pub ups_power_from_load_percentage: bool,
}

impl Config {
Expand All @@ -16,6 +17,7 @@ impl Config {
const DEFAULT_HTTP_PORT: u16 = 9995;
const DEFAULT_HTTP_PATH: &'static str = "/nut";
const DEFAULT_PRINT_METRICS_AND_EXIT: bool = false;
const DEFAULT_UPS_POWER_FROM_LOAD_PERCENTAGE: bool = false;
}

pub fn read_config() -> Config {
Expand All @@ -24,14 +26,14 @@ pub fn read_config() -> Config {
http_port: Config::DEFAULT_HTTP_PORT,
http_path: Config::DEFAULT_HTTP_PATH.to_owned(),
print_metrics_and_exit: Config::DEFAULT_PRINT_METRICS_AND_EXIT,
ups_power_from_load_percentage: Config::DEFAULT_UPS_POWER_FROM_LOAD_PERCENTAGE,
};

if let Ok(http_address_str) = std::env::var("HTTP_ADDRESS") {
if let Ok(http_address) = http_address_str.parse::<IpAddr>() {
config.http_address = http_address;
}
}

if let Ok(http_port_str) = std::env::var("HTTP_PORT") {
if let Ok(http_port) = http_port_str.parse::<u16>() {
config.http_port = http_port;
Expand All @@ -47,6 +49,11 @@ pub fn read_config() -> Config {
config.print_metrics_and_exit = print_metrics_and_exit;
}
}
if let Ok(ups_power_from_load_percentage_str) = std::env::var("UPS_POWER_FROM_LOAD_PERCENTAGE") {
if let Ok(ups_power_from_load_percentage) = ups_power_from_load_percentage_str.parse::<bool>() {
config.ups_power_from_load_percentage = ups_power_from_load_percentage;
}
}

config
}
57 changes: 41 additions & 16 deletions src/nut_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::TcpStream;

use crate::common::ErrorResult;
use crate::config;
use crate::metrics::{NutVersion, UPS_DESCRIPTION_PSEUDOVAR, UpsVarMap, VarMap};

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -99,28 +100,52 @@ async fn query_nut_vars(stream: &mut BufReader<TcpStream>, upses: &mut UpsVarMap
}

for (ups, vars) in upses.iter_mut() {
let line_consumer = |line: &str| {
let captures_opt = VAR_PATTERN.captures(line);
match captures_opt {
Some(captures) => {
let variable = captures["var"].to_owned();
let value = captures["val"].to_owned();
vars.insert(variable, value);
},
None => {
return Err("Malformed list element for VAR list query.".into());
},
}

Ok(())
};
{
let line_consumer = |line: &str| {
let captures_opt = VAR_PATTERN.captures(line);
match captures_opt {
Some(captures) => {
let variable = captures["var"].to_owned();
let value = captures["val"].to_owned();
vars.insert(variable, value);
},
None => {
return Err("Malformed list element for VAR list query.".into());
},
}

Ok(())
};

query_nut_list(stream, format!("LIST VAR {}", ups).as_str(), line_consumer).await?;
}

query_nut_list(stream, format!("LIST VAR {}", ups).as_str(), line_consumer).await?;
override_nut_values(vars).await;
}

Ok(())
}


async fn override_nut_values(vars: &mut HashMap<String, String>) {
let config = config::read_config();

// For UPS systems where ups.power is not available, but ups.load and ups.realpower.nominal are.
// Provide a calculated value for ups.power with the following algorithm:
// power (W) = ( load(%) / 100.00 ) * nominal power (W)
if config.ups_power_from_load_percentage && !( vars.contains_key("ups.power") ) && vars.contains_key("ups.load") && vars.contains_key("ups.realpower.nominal") {
if let (Ok(load), Ok(nominal_power)) = (
vars.get("ups.load").unwrap().parse::<f64>(),
vars.get("ups.realpower.nominal").unwrap().parse::<f64>(),
) {
let calculated_power = (load / 100.0) * nominal_power;
vars.insert(String::from("ups.power"), calculated_power.to_string());
} else {
log::error!("Unable to parse ups.load or ups.realpower.nominal as floats, skipping calculation of ups.power from these values");
}
}
}

async fn query_nut_list<F>(stream: &mut BufReader<TcpStream>, query: &str, mut line_consumer: F) -> ErrorResult<()>
where F: FnMut(&str) -> ErrorResult<()> + Send {
let query_line = format!("{}\n", query);
Expand Down
Loading