APC: Add support for Battery Recommended Days Remaining (#14653)

* APC: Add support for Battery Recommended Days Remaining

* Extract number from string later during polling
Odd that this is different from the discovery process

* wip

* wip

* Apply fixes from StyleCI

* wip

* wip

* Apply fixes from StyleCI

---------

Co-authored-by: Tony Murray <murraytony@gmail.com>
This commit is contained in:
Jellyfrog
2023-03-10 14:50:56 +01:00
committed by GitHub
parent 58acfe8f92
commit f111ac22fe
14 changed files with 201 additions and 34 deletions

View File

@@ -0,0 +1,27 @@
<?php
/**
* UserFunctionExistException.php
*
* -Description-
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* @link https://www.librenms.org
*/
namespace LibreNMS\Exceptions;
class UserFunctionExistException extends \Exception
{
}

View File

@@ -93,7 +93,7 @@ class Number
* @param mixed $number
* @return float|int
*/
public static function cast($number)
public static function cast(mixed $number): float|int
{
if (! is_numeric($number)) {
// match pre-PHP8 behavior
@@ -109,6 +109,21 @@ class Number
return $float == $int ? $int : $float;
}
/**
* Extract the first number found from a string
*/
public static function extract(mixed $string): float|int
{
if (! is_numeric($string)) {
preg_match('/-?\d*\.?\d+/', $string, $matches);
if (! empty($matches[0])) {
$string = $matches[0];
}
}
return self::cast($string);
}
/**
* Calculate a percent, but make sure to not divide by zero. In that case, return 0.
*

View File

@@ -25,6 +25,7 @@
namespace LibreNMS\Util;
use Carbon\Carbon;
use Carbon\CarbonInterface;
use Carbon\CarbonInterval;
@@ -113,4 +114,14 @@ class Time
return (int) strtotime($time);
}
/**
* Take a date and return the number of days from now
*/
public static function dateToDays(string|int $date): int
{
$carbon = new Carbon();
return $carbon->diffInDays($date, false);
}
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* UserFuncHelper.php
*
* Helper class for "user_func"
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* @link https://www.librenms.org
*/
namespace LibreNMS\Util;
use LibreNMS\Exceptions\UserFunctionExistException;
class UserFuncHelper
{
public function __construct(
public string|int|float $value,
public string|int|float|null $value_raw = null,
public array $sensor = [],
) {
}
public function __call(string $name, array $arguments): mixed
{
throw new UserFunctionExistException("Invalid user function: $name");
}
public function dateToDays(): int
{
return \LibreNMS\Util\Time::dateToDays($this->value_raw);
}
}

View File

@@ -1099,3 +1099,11 @@ modules:
warn_limit: 1
high_limit: 3
-
oid: upsAdvBatteryRecommendedReplaceDate
num_oid: '.1.3.6.1.4.1.318.1.1.1.2.2.21.{{ $index }}'
index: 'upsAdvBatteryRecommendedReplaceDate.{{ $index }}'
descr: 'Battery Recommended Days Remaining'
user_func: dateToDays
low_warn_limit: 30
low_limit: 0

View File

@@ -90,14 +90,13 @@ modules:
snmp_flags: '-OQUsb'
index: 'c1Temperature.{{ $index }}'
descr: Chassis 1
user_func: snmp_hexstring, Number::cast
high_limit: 90
- oid: dpwmTemperature
num_oid: '.1.3.6.1.4.1.823.34441.1.10.16.{{ $index }}'
snmp_flags: '-OQUsb'
index: ' dpwmTemperature.{{ $index }}'
descr: DPWM Temperature
user_func: snmp_hexstring, Number::cast
high_limit: 90
voltage:

View File

@@ -288,7 +288,7 @@ modules:
descr: 'Int Battery'
group: 'HSM'
index: pkAHsmBatteryInt
user_func: Number::cast
user_func: \LibreNMS\Util\Number::cast
-
oid: PRIMEKEY-APPLIANCE-MIB::pkAHsmBatteryExt
value: PRIMEKEY-APPLIANCE-MIB::pkAHsmBatteryExt
@@ -296,4 +296,4 @@ modules:
descr: 'Ext Battery'
group: 'HSM'
index: pkAHsmBatteryExt
user_func: Number::cast
user_func: \LibreNMS\Util\Number::cast

View File

@@ -24,6 +24,7 @@ use LibreNMS\Exceptions\InvalidIpException;
use LibreNMS\OS;
use LibreNMS\Util\IP;
use LibreNMS\Util\IPv6;
use LibreNMS\Util\UserFuncHelper;
function discover_new_device($hostname, $device = [], $method = '', $interface = '')
{
@@ -943,8 +944,12 @@ function discovery_process(&$valid, $os, $sensor_class, $pre_cache)
if (is_numeric($$limit)) {
$$limit = ($$limit / $divisor) * $multiplier;
}
if (is_numeric($$limit) && isset($user_function) && is_callable($user_function)) {
if (is_numeric($$limit) && isset($user_function)) {
if (is_callable($user_function)) {
$$limit = $user_function($$limit);
} else {
$$limit = (new UserFuncHelper($$limit))->{$user_function}();
}
}
}
}
@@ -964,8 +969,12 @@ function discovery_process(&$valid, $os, $sensor_class, $pre_cache)
$entPhysicalIndex_measured = isset($data['entPhysicalIndex_measured']) ? $data['entPhysicalIndex_measured'] : null;
//user_func must be applied after divisor/multiplier
if (isset($user_function) && is_callable($user_function)) {
if (isset($user_function)) {
if (is_callable($user_function)) {
$value = $user_function($value);
} else {
$value = (new UserFuncHelper($value, $snmp_data[$data['value']], $data))->{$user_function}();
}
}
$uindex = $index;

View File

@@ -14,6 +14,8 @@ use LibreNMS\Exceptions\JsonAppPollingFailedException;
use LibreNMS\Exceptions\JsonAppWrongVersionException;
use LibreNMS\RRD\RrdDefinition;
use LibreNMS\Util\Debug;
use LibreNMS\Util\Number;
use LibreNMS\Util\UserFuncHelper;
function bulk_sensor_snmpget($device, $sensors)
{
@@ -83,13 +85,6 @@ function poll_sensor($device, $class)
require 'includes/polling/sensors/' . $class . '/' . $device['os_group'] . '.inc.php';
}
if (! is_numeric($sensor_value)) {
preg_match('/-?\d*\.?\d+/', $sensor_value, $temp_response);
if (! empty($temp_response[0])) {
$sensor_value = $temp_response[0];
}
}
if ($class == 'state') {
if (! is_numeric($sensor_value)) {
$state_value = dbFetchCell(
@@ -164,7 +159,7 @@ function record_sensor_data($device, $all_sensors)
foreach ($all_sensors as $sensor) {
$class = ucfirst($sensor['sensor_class']);
$unit = $supported_sensors[$sensor['sensor_class']];
$sensor_value = cast_number($sensor['new_value']);
$sensor_value = Number::extract($sensor['new_value']);
$prev_sensor_value = $sensor['sensor_current'];
if ($sensor_value == -32768 || is_nan($sensor_value)) {
@@ -180,8 +175,12 @@ function record_sensor_data($device, $all_sensors)
$sensor_value = ($sensor_value * $sensor['sensor_multiplier']);
}
if (isset($sensor['user_func']) && is_callable($sensor['user_func'])) {
if (isset($sensor['user_func'])) {
if (is_callable($sensor['user_func'])) {
$sensor_value = $sensor['user_func']($sensor_value);
} else {
$sensor_value = (new UserFuncHelper($sensor_value, $sensor['new_value'], $sensor))->{$sensor['user_func']}();
}
}
$rrd_name = get_sensor_rrd_name($device, $sensor);

View File

@@ -88,6 +88,8 @@ class OSModulesTest extends DBTestCase
*/
public function testOS($os, $variant, $modules)
{
// Lock testing time
$this->travelTo(new \DateTime('2022-01-01 00:00:00'));
$this->requireSnmpsim(); // require snmpsim for tests
// stub out Eventlog::log and Fping->ping, we don't need to store them for these tests
$this->stubClasses();
@@ -145,6 +147,7 @@ class OSModulesTest extends DBTestCase
}
DeviceCache::flush(); // clear cached devices
$this->travelBack();
}
public function dumpedDataProvider()

View File

@@ -478,6 +478,31 @@
"rrd_type": "GAUGE",
"state_name": null
},
{
"sensor_deleted": 0,
"sensor_class": "count",
"poller_type": "snmp",
"sensor_oid": ".1.3.6.1.4.1.318.1.1.1.2.2.21.0",
"sensor_index": "upsAdvBatteryRecommendedReplaceDate.0",
"sensor_type": "apc",
"sensor_descr": "Battery Recommended Days Remaining",
"group": null,
"sensor_divisor": 1,
"sensor_multiplier": 1,
"sensor_current": 9,
"sensor_limit": null,
"sensor_limit_warn": null,
"sensor_limit_low": 0,
"sensor_limit_low_warn": 30,
"sensor_alert": 1,
"sensor_custom": "No",
"entPhysicalIndex": null,
"entPhysicalIndex_measured": null,
"sensor_prev": null,
"user_func": "dateToDays",
"rrd_type": "GAUGE",
"state_name": null
},
{
"sensor_deleted": 0,
"sensor_class": "current",
@@ -952,6 +977,31 @@
"rrd_type": "GAUGE",
"state_name": null
},
{
"sensor_deleted": 0,
"sensor_class": "count",
"poller_type": "snmp",
"sensor_oid": ".1.3.6.1.4.1.318.1.1.1.2.2.21.0",
"sensor_index": "upsAdvBatteryRecommendedReplaceDate.0",
"sensor_type": "apc",
"sensor_descr": "Battery Recommended Days Remaining",
"group": null,
"sensor_divisor": 1,
"sensor_multiplier": 1,
"sensor_current": 9,
"sensor_limit": null,
"sensor_limit_warn": null,
"sensor_limit_low": 0,
"sensor_limit_low_warn": 30,
"sensor_alert": 1,
"sensor_custom": "No",
"entPhysicalIndex": null,
"entPhysicalIndex_measured": null,
"sensor_prev": null,
"user_func": "dateToDays",
"rrd_type": "GAUGE",
"state_name": null
},
{
"sensor_deleted": 0,
"sensor_class": "current",

View File

@@ -1334,7 +1334,7 @@
"entPhysicalIndex": null,
"entPhysicalIndex_measured": null,
"sensor_prev": null,
"user_func": "snmp_hexstring, Number::cast",
"user_func": null,
"rrd_type": "GAUGE",
"state_name": null
},
@@ -1359,7 +1359,7 @@
"entPhysicalIndex": null,
"entPhysicalIndex_measured": null,
"sensor_prev": null,
"user_func": "snmp_hexstring, Number::cast",
"user_func": null,
"rrd_type": "GAUGE",
"state_name": null
},
@@ -2438,7 +2438,7 @@
"entPhysicalIndex": null,
"entPhysicalIndex_measured": null,
"sensor_prev": 20,
"user_func": "snmp_hexstring, Number::cast",
"user_func": null,
"rrd_type": "GAUGE",
"state_name": null
},
@@ -2463,7 +2463,7 @@
"entPhysicalIndex": null,
"entPhysicalIndex_measured": null,
"sensor_prev": 34,
"user_func": "snmp_hexstring, Number::cast",
"user_func": null,
"rrd_type": "GAUGE",
"state_name": null
},

View File

@@ -694,7 +694,7 @@
"entPhysicalIndex": null,
"entPhysicalIndex_measured": null,
"sensor_prev": null,
"user_func": "Number::cast",
"user_func": "\\LibreNMS\\Util\\Number::cast",
"rrd_type": "GAUGE",
"state_name": null
}

View File

@@ -143,6 +143,7 @@
1.3.6.1.4.1.318.1.1.1.1.2.3.0|4|FFFFFFFFFFFF
1.3.6.1.4.1.318.1.1.1.2.2.3.0|67|438000
1.3.6.1.4.1.318.1.1.1.2.2.4.0|2|1
1.3.6.1.4.1.318.1.1.1.2.2.21.0|4|01/10/2022
1.3.6.1.4.1.318.1.1.1.2.3.1.0|66|1000
1.3.6.1.4.1.318.1.1.1.2.3.2.0|66|369
1.3.6.1.4.1.318.1.1.1.2.3.4.0|2|272