2015-06-18 22:20:50 +01:00
< ? php
2011-04-13 14:00:12 +00:00
2020-05-30 17:42:50 -05:00
use App\Models\DeviceGraph ;
2020-04-17 17:37:56 -05:00
use Illuminate\Support\Str ;
Refactored and update Location Geocoding (#9359)
- Fix location so it is a regular database relation (this allows multiple devices to be accurately linked to one location and saves api calls)
- Parse coordinates from the location more consistently
- Add settings to webui
- ~~Used [PHP Geocoder](http://geocoder-php.org/), which has lots of backends and is well tested. (also includes reverse and geoip)~~
- Google Maps, Bing, Mapquest, and OpenStreetMap supported initially.
- Default to OpenStreetMap, which doesn't require a key. They will liberally hand out bans if you exceed 1 query per second though.
- All other Geocoding APIs require an API key. (Google requires a credit card on file, but seems to be the most accurate)
- Update all (I think) sql queries to handle the new structure
- Remove final vestiges of override_sysLocation as a device attribute
- Update existing device groups and rules in DB
- Tested all APIs with good/bad location, no/bad/good key, and no connection.
- Cannot fix advanced queries that use location
This blocks #8868
DO NOT DELETE THIS TEXT
#### Please note
> Please read this information carefully. You can run `./scripts/pre-commit.php` to check your code before submitting.
- [x] Have you followed our [code guidelines?](http://docs.librenms.org/Developing/Code-Guidelines/)
#### Testers
If you would like to test this pull request then please run: `./scripts/github-apply <pr_id>`, i.e `./scripts/github-apply 5926`
After you are done testing, you can remove the changes with `./scripts/github-remove`. If there are schema changes, you can ask on discord how to revert.
2018-11-28 16:49:18 -06:00
use LibreNMS\Config ;
2020-05-23 03:08:05 +02:00
use LibreNMS\Enum\Alert ;
2018-05-25 21:16:16 -05:00
use LibreNMS\Exceptions\JsonAppBlankJsonException ;
2020-05-30 17:42:50 -05:00
use LibreNMS\Exceptions\JsonAppExtendErroredException ;
2018-05-25 21:16:16 -05:00
use LibreNMS\Exceptions\JsonAppMissingKeysException ;
2020-05-30 17:42:50 -05:00
use LibreNMS\Exceptions\JsonAppParsingFailedException ;
use LibreNMS\Exceptions\JsonAppPollingFailedException ;
2018-05-25 21:16:16 -05:00
use LibreNMS\Exceptions\JsonAppWrongVersionException ;
2020-05-30 17:42:50 -05:00
use LibreNMS\RRD\RrdDefinition ;
2017-02-23 22:45:50 +00:00
2016-10-11 17:34:09 +01:00
function bulk_sensor_snmpget ( $device , $sensors )
{
$oid_per_pdu = get_device_oid_limit ( $device );
$sensors = array_chunk ( $sensors , $oid_per_pdu );
2020-09-21 15:43:38 +02:00
$cache = [];
2016-10-11 17:34:09 +01:00
foreach ( $sensors as $chunk ) {
$oids = array_map ( function ( $data ) {
return $data [ 'sensor_oid' ];
}, $chunk );
$oids = implode ( ' ' , $oids );
2018-01-29 15:01:11 -06:00
$multi_response = snmp_get_multi_oid ( $device , $oids , '-OUQnte' );
2016-10-11 17:34:09 +01:00
$cache = array_merge ( $cache , $multi_response );
}
2020-09-21 15:43:38 +02:00
2016-10-11 17:34:09 +01:00
return $cache ;
}
2017-01-18 08:48:33 +00:00
/**
* @ param $device
2021-09-08 23:35:56 +02:00
* @ param string $type type / class of sensor
2017-01-18 08:48:33 +00:00
* @ return array
*/
2017-08-28 12:57:23 -05:00
function sensor_precache ( $device , $type )
2017-01-18 08:48:33 +00:00
{
2020-09-21 15:43:38 +02:00
$sensor_cache = [];
if ( file_exists ( 'includes/polling/sensors/pre-cache/' . $device [ 'os' ] . '.inc.php' )) {
include 'includes/polling/sensors/pre-cache/' . $device [ 'os' ] . '.inc.php' ;
2017-01-18 08:48:33 +00:00
}
2020-09-21 15:43:38 +02:00
2017-08-28 12:57:23 -05:00
return $sensor_cache ;
2017-01-18 08:48:33 +00:00
}
2017-02-08 04:54:30 +00:00
function poll_sensor ( $device , $class )
2016-08-28 12:32:58 -05:00
{
2019-06-23 00:29:12 -05:00
global $agent_sensors ;
2015-07-13 20:10:26 +02:00
2020-09-21 15:43:38 +02:00
$sensors = [];
$misc_sensors = [];
$all_sensors = [];
2017-01-18 08:48:33 +00:00
2020-09-21 15:59:34 +02:00
foreach ( dbFetchRows ( 'SELECT * FROM `sensors` WHERE `sensor_class` = ? AND `device_id` = ?' , [ $class , $device [ 'device_id' ]]) as $sensor ) {
2016-10-11 17:34:09 +01:00
if ( $sensor [ 'poller_type' ] == 'agent' ) {
2017-02-24 01:25:05 +00:00
// Agent sensors are polled in the unix-agent
2016-10-11 17:34:09 +01:00
} elseif ( $sensor [ 'poller_type' ] == 'ipmi' ) {
$misc_sensors [] = $sensor ;
} else {
$sensors [] = $sensor ;
}
}
$snmp_data = bulk_sensor_snmpget ( $device , $sensors );
2017-08-28 12:57:23 -05:00
$sensor_cache = sensor_precache ( $device , $class );
2017-01-18 08:48:33 +00:00
2016-10-11 17:34:09 +01:00
foreach ( $sensors as $sensor ) {
2020-09-21 15:43:38 +02:00
echo 'Checking (' . $sensor [ 'poller_type' ] . " ) $class " . $sensor [ 'sensor_descr' ] . '... ' . PHP_EOL ;
2015-07-13 20:10:26 +02:00
if ( $sensor [ 'poller_type' ] == 'snmp' ) {
2016-09-28 08:19:28 -05:00
$mibdir = null ;
2016-08-07 11:40:37 +02:00
2016-12-07 00:30:56 +00:00
$sensor_value = trim ( str_replace ( '"' , '' , $snmp_data [ $sensor [ 'sensor_oid' ]]));
2020-09-21 15:43:38 +02:00
if ( file_exists ( 'includes/polling/sensors/' . $class . '/' . $device [ 'os' ] . '.inc.php' )) {
require 'includes/polling/sensors/' . $class . '/' . $device [ 'os' ] . '.inc.php' ;
} elseif ( file_exists ( 'includes/polling/sensors/' . $class . '/' . $device [ 'os_group' ] . '.inc.php' )) {
require 'includes/polling/sensors/' . $class . '/' . $device [ 'os_group' ] . '.inc.php' ;
2015-05-11 22:01:26 +01:00
}
2012-05-20 18:23:40 +00:00
2015-07-13 20:10:26 +02:00
if ( $class == 'temperature' ) {
2016-10-11 17:34:09 +01:00
preg_match ( '/[\d\.\-]+/' , $sensor_value , $temp_response );
2020-09-21 15:43:38 +02:00
if ( ! empty ( $temp_response [ 0 ])) {
2016-10-11 17:34:09 +01:00
$sensor_value = $temp_response [ 0 ];
}
2016-08-23 20:44:45 +02:00
} elseif ( $class == 'state' ) {
2020-09-21 15:43:38 +02:00
if ( ! is_numeric ( $sensor_value )) {
2016-10-11 17:34:09 +01:00
$state_value = dbFetchCell (
2018-11-22 09:05:38 -06:00
' SELECT `state_value`
FROM `state_translations` LEFT JOIN `sensors_to_state_indexes`
ON `state_translations` . `state_index_id` = `sensors_to_state_indexes` . `state_index_id`
WHERE `sensors_to_state_indexes` . `sensor_id` = ?
2016-10-11 17:34:09 +01:00
AND `state_translations` . `state_descr` LIKE ? ' ,
2020-09-21 15:43:38 +02:00
[ $sensor [ 'sensor_id' ], $sensor_value ]
2016-10-11 17:34:09 +01:00
);
2016-04-23 20:14:22 -05:00
d_echo ( 'State value of ' . $sensor_value . ' is ' . $state_value . " \n " );
if ( is_numeric ( $state_value )) {
$sensor_value = $state_value ;
}
}
2015-07-13 20:10:26 +02:00
} //end if
unset ( $mib );
2016-08-07 11:40:37 +02:00
unset ( $mibdir );
2016-10-11 17:34:09 +01:00
$sensor [ 'new_value' ] = $sensor_value ;
$all_sensors [] = $sensor ;
}
}
foreach ( $misc_sensors as $sensor ) {
if ( $sensor [ 'poller_type' ] == 'agent' ) {
2015-07-13 20:10:26 +02:00
if ( isset ( $agent_sensors )) {
$sensor_value = $agent_sensors [ $class ][ $sensor [ 'sensor_type' ]][ $sensor [ 'sensor_index' ]][ 'current' ];
2016-10-11 17:34:09 +01:00
$sensor [ 'new_value' ] = $sensor_value ;
$all_sensors [] = $sensor ;
2016-08-28 12:32:58 -05:00
} else {
2015-07-13 20:10:26 +02:00
echo " no agent data! \n " ;
continue ;
}
2016-08-28 12:32:58 -05:00
} elseif ( $sensor [ 'poller_type' ] == 'ipmi' ) {
2015-07-13 20:10:26 +02:00
echo " already polled. \n " ;
// ipmi should probably move here from the ipmi poller file (FIXME)
continue ;
2016-08-28 12:32:58 -05:00
} else {
2015-07-13 20:10:26 +02:00
echo " unknown poller type! \n " ;
continue ;
} //end if
2016-10-11 17:34:09 +01:00
}
2017-02-24 01:25:05 +00:00
record_sensor_data ( $device , $all_sensors );
} //end poll_sensor()
/**
* @ param $device
* @ param $all_sensors
*/
function record_sensor_data ( $device , $all_sensors )
{
2020-09-21 15:43:38 +02:00
$supported_sensors = [
2017-02-24 01:25:05 +00:00
'current' => 'A' ,
'frequency' => 'Hz' ,
'runtime' => 'Min' ,
'humidity' => '%' ,
'fanspeed' => 'rpm' ,
'power' => 'W' ,
'voltage' => 'V' ,
'temperature' => 'C' ,
'dbm' => 'dBm' ,
'charge' => '%' ,
'load' => '%' ,
'state' => '#' ,
'signal' => 'dBm' ,
'airflow' => 'cfm' ,
2017-05-23 16:28:45 +01:00
'snr' => 'SNR' ,
2017-09-29 21:13:27 +01:00
'pressure' => 'kPa' ,
'cooling' => 'W' ,
2020-09-21 15:43:38 +02:00
];
2015-07-13 20:10:26 +02:00
2016-10-11 17:34:09 +01:00
foreach ( $all_sensors as $sensor ) {
2020-09-21 15:43:38 +02:00
$class = ucfirst ( $sensor [ 'sensor_class' ]);
2021-04-22 17:59:23 -05:00
$unit = $supported_sensors [ $sensor [ 'sensor_class' ]];
2021-03-12 18:10:14 -06:00
$sensor_value = cast_number ( $sensor [ 'new_value' ]);
2017-07-06 02:27:02 -05:00
$prev_sensor_value = $sensor [ 'sensor_current' ];
2020-06-09 13:51:36 -04:00
if ( $sensor_value == - 32768 || is_nan ( $sensor_value )) {
echo 'Invalid (-32768 or NaN)' ;
2015-07-13 20:10:26 +02:00
$sensor_value = 0 ;
2015-04-11 19:47:56 +01:00
}
2011-04-13 14:00:12 +00:00
2016-02-09 14:20:36 -06:00
if ( $sensor [ 'sensor_divisor' ] && $sensor_value !== 0 ) {
2015-07-13 20:10:26 +02:00
$sensor_value = ( $sensor_value / $sensor [ 'sensor_divisor' ]);
}
2011-09-18 15:38:05 +00:00
2015-07-13 20:10:26 +02:00
if ( $sensor [ 'sensor_multiplier' ]) {
$sensor_value = ( $sensor_value * $sensor [ 'sensor_multiplier' ]);
}
2011-04-13 14:00:12 +00:00
2019-04-29 16:46:53 +02:00
if ( isset ( $sensor [ 'user_func' ]) && is_callable ( $sensor [ 'user_func' ])) {
2018-03-29 07:22:17 +02:00
$sensor_value = $sensor [ 'user_func' ]( $sensor_value );
}
2016-07-08 22:58:36 -05:00
$rrd_name = get_sensor_rrd_name ( $device , $sensor );
2017-12-13 03:27:10 +01:00
$rrd_def = RrdDefinition :: make () -> addDataset ( 'sensor' , 'GAUGE' );
2011-04-13 14:00:12 +00:00
2015-07-13 20:10:26 +02:00
echo " $sensor_value $unit\n " ;
2011-04-13 14:00:12 +00:00
2020-09-21 15:43:38 +02:00
$fields = [
2015-08-18 16:26:55 +00:00
'sensor' => $sensor_value ,
2020-09-21 15:43:38 +02:00
];
2015-08-18 16:26:55 +00:00
2020-09-21 15:43:38 +02:00
$tags = [
2016-07-07 01:33:43 -05:00
'sensor_class' => $sensor [ 'sensor_class' ],
'sensor_type' => $sensor [ 'sensor_type' ],
'sensor_descr' => $sensor [ 'sensor_descr' ],
'sensor_index' => $sensor [ 'sensor_index' ],
'rrd_name' => $rrd_name ,
2020-09-21 15:43:38 +02:00
'rrd_def' => $rrd_def ,
];
2016-08-28 12:32:58 -05:00
data_update ( $device , 'sensor' , $tags , $fields );
2015-08-19 20:58:02 +00:00
2017-02-24 01:25:05 +00:00
// FIXME also warn when crossing WARN level!
2017-07-06 02:27:02 -05:00
if ( $sensor [ 'sensor_limit_low' ] != '' && $prev_sensor_value > $sensor [ 'sensor_limit_low' ] && $sensor_value < $sensor [ 'sensor_limit_low' ] && $sensor [ 'sensor_alert' ] == 1 ) {
2020-09-21 15:43:38 +02:00
echo 'Alerting for ' . $device [ 'hostname' ] . ' ' . $sensor [ 'sensor_descr' ] . " \n " ;
2020-04-19 19:57:49 +02:00
log_event ( " $class under threshold: $sensor_value $unit (< { $sensor [ 'sensor_limit_low' ] } $unit ) " , $device , $sensor [ 'sensor_class' ], 4 , $sensor [ 'sensor_id' ]);
2017-07-06 02:27:02 -05:00
} elseif ( $sensor [ 'sensor_limit' ] != '' && $prev_sensor_value < $sensor [ 'sensor_limit' ] && $sensor_value > $sensor [ 'sensor_limit' ] && $sensor [ 'sensor_alert' ] == 1 ) {
2020-09-21 15:43:38 +02:00
echo 'Alerting for ' . $device [ 'hostname' ] . ' ' . $sensor [ 'sensor_descr' ] . " \n " ;
2020-04-19 19:57:49 +02:00
log_event ( " $class above threshold: $sensor_value $unit (> { $sensor [ 'sensor_limit' ] } $unit ) " , $device , $sensor [ 'sensor_class' ], 4 , $sensor [ 'sensor_id' ]);
2015-07-13 20:10:26 +02:00
}
2017-07-06 02:27:02 -05:00
if ( $sensor [ 'sensor_class' ] == 'state' && $prev_sensor_value != $sensor_value ) {
$trans = array_column (
dbFetchRows (
2020-09-21 15:59:34 +02:00
'SELECT `state_translations`.`state_value`, `state_translations`.`state_descr` FROM `sensors_to_state_indexes` LEFT JOIN `state_translations` USING (`state_index_id`) WHERE `sensors_to_state_indexes`.`sensor_id`=? AND `state_translations`.`state_value` IN (?,?)' ,
2018-08-26 07:42:21 -05:00
[ $sensor [ 'sensor_id' ], $sensor_value , $prev_sensor_value ]
2017-07-06 02:27:02 -05:00
),
'state_descr' ,
'state_value'
);
2017-07-07 12:15:53 -05:00
log_event ( " $class sensor { $sensor [ 'sensor_descr' ] } has changed from { $trans [ $prev_sensor_value ] } ( $prev_sensor_value ) to { $trans [ $sensor_value ] } ( $sensor_value ) " , $device , $class , 3 , $sensor [ 'sensor_id' ]);
2016-02-27 15:29:11 +01:00
}
2017-11-20 18:37:02 +00:00
if ( $sensor_value != $prev_sensor_value ) {
2021-04-22 17:59:23 -05:00
dbUpdate ([ 'sensor_current' => $sensor_value , 'sensor_prev' => $prev_sensor_value , 'lastupdate' => [ 'NOW()' ]], 'sensors' , '`sensor_class` = ? AND `sensor_id` = ?' , [ $sensor [ 'sensor_class' ], $sensor [ 'sensor_id' ]]);
2017-11-20 18:37:02 +00:00
}
2016-10-11 17:34:09 +01:00
}
2017-02-24 01:25:05 +00:00
}
2011-04-13 14:00:12 +00:00
2018-06-30 06:19:49 -05:00
/**
2021-09-08 23:35:56 +02:00
* @ param array $device The device to poll
* @ param bool $force_module Ignore device module overrides
2018-06-30 06:19:49 -05:00
* @ return bool
*/
2018-07-12 15:30:39 -05:00
function poll_device ( $device , $force_module = false )
2016-08-28 12:32:58 -05:00
{
2020-09-18 08:12:07 -05:00
global $device , $graphs ;
2011-09-20 14:29:04 +00:00
2018-06-30 06:19:49 -05:00
$device_start = microtime ( true );
2021-10-03 22:45:10 -05:00
$deviceModel = DeviceCache :: getPrimary ();
$attribs = $deviceModel -> getAttribs ();
2017-01-22 09:49:13 +00:00
$device [ 'attribs' ] = $attribs ;
2017-01-09 02:16:09 -06:00
load_os ( $device );
2020-07-23 09:57:22 -05:00
$os = \LibreNMS\OS :: make ( $device );
2016-12-23 17:53:19 +00:00
2015-07-13 20:10:26 +02:00
unset ( $array );
2018-06-30 06:19:49 -05:00
2015-07-13 20:10:26 +02:00
// Start counting device poll time
2019-04-02 15:51:41 -07:00
echo 'Hostname: ' . $device [ 'hostname' ] . PHP_EOL ;
echo 'Device ID: ' . $device [ 'device_id' ] . PHP_EOL ;
echo 'OS: ' . $device [ 'os' ] . PHP_EOL ;
2020-01-31 12:38:39 +01:00
if ( empty ( $device [ 'overwrite_ip' ])) {
$ip = dnslookup ( $device );
} else {
$ip = $device [ 'overwrite_ip' ];
}
2018-07-13 17:08:00 -05:00
2019-04-02 15:51:41 -07:00
$db_ip = null ;
2020-09-21 15:43:38 +02:00
if ( ! empty ( $ip )) {
2020-01-31 12:38:39 +01:00
if ( empty ( $device [ 'overwrite_ip' ])) {
2020-09-21 15:43:38 +02:00
echo 'Resolved IP: ' . $ip . PHP_EOL ;
2020-01-31 12:38:39 +01:00
} else {
2020-09-21 15:43:38 +02:00
echo 'Assigned IP: ' . $ip . PHP_EOL ;
2020-01-31 12:38:39 +01:00
}
2019-04-02 15:51:41 -07:00
$db_ip = inet_pton ( $ip );
}
2016-01-17 23:59:51 +00:00
2020-09-21 15:43:38 +02:00
if ( ! empty ( $db_ip ) && inet6_ntop ( $db_ip ) != inet6_ntop ( $device [ 'ip' ])) {
2017-02-14 00:32:02 +02:00
log_event ( 'Device IP changed to ' . $ip , $device , 'system' , 3 );
2020-09-21 15:43:38 +02:00
dbUpdate ([ 'ip' => $db_ip ], 'devices' , 'device_id=?' , [ $device [ 'device_id' ]]);
2016-01-17 23:59:51 +00:00
}
2019-06-23 00:29:12 -05:00
if ( $os_group = Config :: get ( " os. { $device [ 'os' ] } .group " )) {
$device [ 'os_group' ] = $os_group ;
2020-09-21 15:43:38 +02:00
echo ' (' . $device [ 'os_group' ] . ')' ;
2015-07-13 20:10:26 +02:00
}
2020-09-21 15:43:38 +02:00
echo PHP_EOL . PHP_EOL ;
2011-09-20 14:29:04 +00:00
2015-07-13 20:10:26 +02:00
unset ( $poll_update );
unset ( $poll_update_query );
unset ( $poll_separator );
2020-09-21 15:43:38 +02:00
$poll_update_array = [];
$update_array = [];
2011-09-20 14:29:04 +00:00
2021-03-28 17:25:30 -05:00
$host_rrd = Rrd :: name ( $device [ 'hostname' ], '' , '' );
2020-09-21 15:43:38 +02:00
if ( Config :: get ( 'norrd' ) !== true && ! is_dir ( $host_rrd )) {
2015-07-13 20:10:26 +02:00
mkdir ( $host_rrd );
echo " Created directory : $host_rrd\n " ;
}
2011-09-20 14:29:04 +00:00
2021-10-03 22:45:10 -05:00
$helper = new \LibreNMS\Polling\ConnectivityHelper ( $deviceModel );
$helper -> saveMetrics ();
2015-09-22 02:25:35 +02:00
2021-10-03 22:45:10 -05:00
if ( $helper -> isUp ()) {
2017-12-02 16:55:53 -06:00
if ( $device [ 'snmp_disable' ]) {
2021-10-04 06:33:40 -05:00
// only non-snmp modules
Config :: set ( 'poller_modules' , array_intersect_key ( Config :: get ( 'poller_modules' ), [
'availability' => true ,
'ipmi' => true ,
'unix-agent' => true ,
]));
2017-12-02 16:55:53 -06:00
} else {
// we always want the core module to be included, prepend it
2021-10-04 06:33:40 -05:00
Config :: set ( 'poller_modules' , [ 'core' => true ] + Config :: get ( 'poller_modules' ));
2015-07-13 20:10:26 +02:00
}
2017-12-02 16:55:53 -06:00
2021-10-03 22:45:10 -05:00
// update $device array status
$device [ 'status' ] = $deviceModel -> status ;
$device [ 'status_reason' ] = $deviceModel -> status_reason ;
2021-10-06 17:09:54 -05:00
/** @var \App\Polling\Measure\MeasurementManager $measurements */
$measurements = app ( \App\Polling\Measure\MeasurementManager :: class );
$measurements -> checkpoint (); // don't count previous stats
2019-06-23 00:29:12 -05:00
foreach ( Config :: get ( 'poller_modules' ) as $module => $module_status ) {
$os_module_status = Config :: get ( " os. { $device [ 'os' ] } .poller_modules. $module " );
2020-09-21 15:59:34 +02:00
d_echo ( 'Modules status: Global' . ( isset ( $module_status ) ? ( $module_status ? '+ ' : '- ' ) : ' ' ));
d_echo ( 'OS' . ( isset ( $os_module_status ) ? ( $os_module_status ? '+ ' : '- ' ) : ' ' ));
d_echo ( 'Device' . ( isset ( $attribs [ 'poll_' . $module ]) ? ( $attribs [ 'poll_' . $module ] ? '+ ' : '- ' ) : ' ' ));
2016-11-13 20:46:05 +01:00
if ( $force_module === true ||
2020-09-21 15:43:38 +02:00
$attribs [ 'poll_' . $module ] ||
( $os_module_status && ! isset ( $attribs [ 'poll_' . $module ])) ||
( $module_status && ! isset ( $os_module_status ) && ! isset ( $attribs [ 'poll_' . $module ]))) {
2017-02-08 04:54:30 +00:00
$start_memory = memory_get_usage ();
2016-06-24 15:32:19 +01:00
$module_start = microtime ( true );
echo " \n #### Load poller module $module #### \n " ;
2018-08-25 15:00:03 -05:00
try {
include " includes/polling/ $module .inc.php " ;
} catch ( Exception $e ) {
// isolate module exceptions so they don't disrupt the polling process
2020-09-21 15:43:38 +02:00
echo $e -> getTraceAsString () . PHP_EOL ;
2018-08-25 15:00:03 -05:00
c_echo ( " %rError in $module module.%n " . $e -> getMessage () . PHP_EOL );
logfile ( " Error in $module module. " . $e -> getMessage () . PHP_EOL . $e -> getTraceAsString () . PHP_EOL );
}
2016-06-24 15:32:19 +01:00
$module_time = microtime ( true ) - $module_start ;
2020-09-21 15:43:38 +02:00
$module_mem = ( memory_get_usage () - $start_memory );
2017-02-08 04:54:30 +00:00
printf ( " \n >> Runtime for poller module '%s': %.4f seconds with %s bytes \n " , $module , $module_time , $module_mem );
2021-10-06 17:09:54 -05:00
$measurements -> printChangedStats ();
2016-06-24 15:32:19 +01:00
echo " #### Unload poller module $module #### \n \n " ;
// save per-module poller stats
2020-09-21 15:43:38 +02:00
$tags = [
2016-06-24 15:32:19 +01:00
'module' => $module ,
2017-02-23 22:45:50 +00:00
'rrd_def' => RrdDefinition :: make () -> addDataset ( 'poller' , 'GAUGE' , 0 ),
2020-09-21 15:43:38 +02:00
'rrd_name' => [ 'poller-perf' , $module ],
];
$fields = [
2016-06-24 15:32:19 +01:00
'poller' => $module_time ,
2020-09-21 15:43:38 +02:00
];
2016-06-24 15:32:19 +01:00
data_update ( $device , 'poller-perf' , $tags , $fields );
2020-07-23 09:57:22 -05:00
$os -> enableGraph ( 'poller_perf' );
2016-06-24 15:32:19 +01:00
// remove old rrd
2021-03-28 17:25:30 -05:00
$oldrrd = Rrd :: name ( $device [ 'hostname' ], [ 'poller' , $module , 'perf' ]);
2016-06-24 15:32:19 +01:00
if ( is_file ( $oldrrd )) {
unlink ( $oldrrd );
2016-08-28 12:32:58 -05:00
}
2017-02-08 04:54:30 +00:00
unset ( $tags , $fields , $oldrrd );
2020-09-21 15:43:38 +02:00
} elseif ( isset ( $attribs [ 'poll_' . $module ]) && $attribs [ 'poll_' . $module ] == '0' ) {
2016-11-13 20:46:05 +01:00
echo " Module [ $module ] disabled on host. \n \n " ;
} elseif ( isset ( $os_module_status ) && $os_module_status == '0' ) {
echo " Module [ $module ] disabled on os. \n \n " ;
2016-08-28 12:32:58 -05:00
} else {
2016-11-13 20:46:05 +01:00
echo " Module [ $module ] disabled globally. \n \n " ;
2016-06-24 15:32:19 +01:00
}
}
2015-07-13 20:10:26 +02:00
2016-01-10 19:31:39 +10:00
// Ping response
2021-10-03 22:45:10 -05:00
if ( $helper -> canPing ()) {
2020-07-23 09:57:22 -05:00
$os -> enableGraph ( 'ping_perf' );
2011-09-20 14:29:04 +00:00
}
2021-10-04 14:34:35 -05:00
$device_time = round ( microtime ( true ) - $device_start , 3 );
// Poller performance
if ( ! empty ( $device_time )) {
$tags = [
'rrd_def' => RrdDefinition :: make () -> addDataset ( 'poller' , 'GAUGE' , 0 ),
'module' => 'ALL' ,
];
$fields = [
'poller' => $device_time ,
];
data_update ( $device , 'poller-perf' , $tags , $fields );
$os -> enableGraph ( 'poller_modules_perf' );
}
2017-07-21 17:01:59 -05:00
2020-09-21 15:43:38 +02:00
if ( ! $force_module ) {
2018-06-30 06:19:49 -05:00
// don't update last_polled time if we are forcing a specific module to be polled
2020-09-21 15:43:38 +02:00
$update_array [ 'last_polled' ] = [ 'NOW()' ];
2018-06-30 06:19:49 -05:00
$update_array [ 'last_polled_timetaken' ] = $device_time ;
2020-07-23 09:57:22 -05:00
2020-09-21 15:59:34 +02:00
echo 'Enabling graphs: ' ;
2020-07-23 09:57:22 -05:00
DeviceGraph :: deleted ( function ( $graph ) {
echo '-' ;
});
DeviceGraph :: created ( function ( $graph ) {
echo '+' ;
});
$os -> persistGraphs ();
echo PHP_EOL ;
2018-06-30 06:19:49 -05:00
}
2020-09-21 15:43:38 +02:00
$updated = dbUpdate ( $update_array , 'devices' , '`device_id` = ?' , [ $device [ 'device_id' ]]);
2018-06-30 06:19:49 -05:00
if ( $updated ) {
d_echo ( 'Updating ' . $device [ 'hostname' ] . PHP_EOL );
}
2011-09-20 14:29:04 +00:00
2018-06-30 06:19:49 -05:00
echo " \n Polled in $device_time seconds \n " ;
2015-07-13 20:10:26 +02:00
2017-07-21 17:01:59 -05:00
// check if the poll took to long and log an event
2019-06-23 00:29:12 -05:00
if ( $device_time > Config :: get ( 'rrd.step' )) {
2020-09-21 15:59:34 +02:00
log_event ( 'Polling took longer than ' . round ( Config :: get ( 'rrd.step' ) / 60 , 2 ) .
2017-07-21 17:01:59 -05:00
' minutes! This will cause gaps in graphs.' , $device , 'system' , 5 );
}
2015-07-13 20:10:26 +02:00
unset ( $storage_cache );
// Clear cache of hrStorage ** MAYBE FIXME? **
unset ( $cache );
// Clear cache (unify all things here?)
2018-06-30 06:19:49 -05:00
return true ; // device was polled
}
return false ; // device not polled
2015-07-13 20:10:26 +02:00
} //end poll_device()
2011-09-20 14:29:04 +00:00
2017-03-11 14:39:32 +00:00
/**
* Update the application status and output in the database .
*
2017-12-06 16:13:10 -06:00
* Metric values should have key for of the matching name .
* If you have multiple groups of metrics , you can group them with multiple sub arrays
* The group name ( key ) will be prepended to each metric in that group , separated by an underscore
* The special group " none " will not be prefixed .
*
2021-09-08 23:35:56 +02:00
* @ param array $app app from the db , including app_id
* @ param string $response This should be the return state of Application polling
* @ param array $metrics an array of additional metrics to store in the database for alerting
* @ param string $status This is the current value for alerting
2017-03-11 14:39:32 +00:00
*/
2020-09-21 15:43:38 +02:00
function update_application ( $app , $response , $metrics = [], $status = '' )
2017-03-11 14:39:32 +00:00
{
2020-09-21 15:43:38 +02:00
if ( ! is_numeric ( $app [ 'app_id' ])) {
2017-03-11 14:39:32 +00:00
d_echo ( '$app does not contain app_id, could not update' );
2020-09-21 15:43:38 +02:00
2017-03-11 14:39:32 +00:00
return ;
}
2020-09-21 15:43:38 +02:00
$data = [
2017-05-13 12:55:38 +01:00
'app_state' => 'UNKNOWN' ,
2017-12-01 01:53:26 -06:00
'app_status' => $status ,
2020-09-21 15:43:38 +02:00
'timestamp' => [ 'NOW()' ],
];
2017-03-11 14:39:32 +00:00
if ( $response != '' && $response !== false ) {
2020-09-21 15:43:38 +02:00
if ( Str :: contains ( $response , [
2017-03-11 14:39:32 +00:00
'Traceback (most recent call last):' ,
2020-09-21 15:43:38 +02:00
])) {
2017-03-11 14:39:32 +00:00
$data [ 'app_state' ] = 'ERROR' ;
2020-05-20 02:21:02 +02:00
} elseif ( in_array ( $response , [ 'OK' , 'ERROR' , 'LEGACY' , 'UNSUPPORTED' ])) {
$data [ 'app_state' ] = $response ;
2017-03-11 14:39:32 +00:00
} else {
2020-09-21 15:43:38 +02:00
// should maybe be 'unknown' as state
2017-03-11 14:39:32 +00:00
$data [ 'app_state' ] = 'OK' ;
}
}
if ( $data [ 'app_state' ] != $app [ 'app_state' ]) {
$data [ 'app_state_prev' ] = $app [ 'app_state' ];
2020-05-20 02:21:02 +02:00
2020-09-21 15:43:38 +02:00
$device = dbFetchRow ( 'SELECT * FROM devices LEFT JOIN applications ON devices.device_id=applications.device_id WHERE applications.app_id=?' , [ $app [ 'app_id' ]]);
2020-05-20 02:21:02 +02:00
$app_name = \LibreNMS\Util\StringHelpers :: nicecase ( $app [ 'app_type' ]);
switch ( $data [ 'app_state' ]) {
case 'OK' :
2020-05-23 03:08:05 +02:00
$severity = Alert :: OK ;
2020-09-21 15:59:34 +02:00
$event_msg = 'changed to OK' ;
2020-05-20 02:21:02 +02:00
break ;
case 'ERROR' :
2020-05-23 03:08:05 +02:00
$severity = Alert :: ERROR ;
2020-09-21 15:59:34 +02:00
$event_msg = 'ends with ERROR' ;
2020-05-20 02:21:02 +02:00
break ;
case 'LEGACY' :
2020-05-23 03:08:05 +02:00
$severity = Alert :: WARNING ;
2020-09-21 15:59:34 +02:00
$event_msg = 'Client Agent is deprecated' ;
2020-05-20 02:21:02 +02:00
break ;
case 'UNSUPPORTED' :
2020-05-23 03:08:05 +02:00
$severity = Alert :: ERROR ;
2020-09-21 15:59:34 +02:00
$event_msg = 'Client Agent Version is not supported' ;
2020-05-20 02:21:02 +02:00
break ;
default :
2020-05-23 03:08:05 +02:00
$severity = Alert :: UNKNOWN ;
2020-09-21 15:59:34 +02:00
$event_msg = 'has UNKNOWN state' ;
2020-05-20 02:21:02 +02:00
break ;
}
2020-09-21 15:59:34 +02:00
log_event ( 'Application ' . $app_name . ' ' . $event_msg , $device , 'application' , $severity );
2017-03-11 14:39:32 +00:00
}
2020-09-21 15:43:38 +02:00
dbUpdate ( $data , 'applications' , '`app_id` = ?' , [ $app [ 'app_id' ]]);
2017-12-01 01:53:26 -06:00
// update metrics
2020-09-21 15:43:38 +02:00
if ( ! empty ( $metrics )) {
$db_metrics = dbFetchRows ( 'SELECT * FROM `application_metrics` WHERE app_id=?' , [ $app [ 'app_id' ]]);
2017-12-01 01:53:26 -06:00
$db_metrics = array_by_column ( $db_metrics , 'metric' );
2017-12-06 16:13:10 -06:00
// allow two level metrics arrays, flatten them and prepend the group name
if ( is_array ( current ( $metrics ))) {
$metrics = array_reduce (
array_keys ( $metrics ),
function ( $carry , $metric_group ) use ( $metrics ) {
if ( $metric_group == 'none' ) {
$prefix = '' ;
} else {
$prefix = $metric_group . '_' ;
}
foreach ( $metrics [ $metric_group ] as $metric_name => $value ) {
$carry [ $prefix . $metric_name ] = $value ;
}
2020-09-21 15:43:38 +02:00
2017-12-06 16:13:10 -06:00
return $carry ;
},
2020-09-21 15:43:38 +02:00
[]
2017-12-06 16:13:10 -06:00
);
}
2017-12-01 01:53:26 -06:00
echo ': ' ;
foreach ( $metrics as $metric_name => $value ) {
2020-09-21 15:43:38 +02:00
if ( ! isset ( $db_metrics [ $metric_name ])) {
2017-12-01 01:53:26 -06:00
// insert new metric
dbInsert (
2020-09-21 15:43:38 +02:00
[
2017-12-01 01:53:26 -06:00
'app_id' => $app [ 'app_id' ],
'metric' => $metric_name ,
2018-08-19 17:44:11 -05:00
'value' => $value ,
2020-09-21 15:43:38 +02:00
],
2017-12-01 01:53:26 -06:00
'application_metrics'
);
echo '+' ;
2018-08-19 17:44:11 -05:00
} elseif ( $value != $db_metrics [ $metric_name ][ 'value' ]) {
2017-12-01 01:53:26 -06:00
dbUpdate (
2020-09-21 15:43:38 +02:00
[
2018-08-19 17:44:11 -05:00
'value' => $value ,
2017-12-01 01:53:26 -06:00
'value_prev' => $db_metrics [ $metric_name ][ 'value' ],
2020-09-21 15:43:38 +02:00
],
2017-12-01 01:53:26 -06:00
'application_metrics' ,
'app_id=? && metric=?' ,
2020-09-21 15:43:38 +02:00
[ $app [ 'app_id' ], $metric_name ]
2017-12-01 01:53:26 -06:00
);
echo 'U' ;
} else {
echo '.' ;
}
unset ( $db_metrics [ $metric_name ]);
}
// remove no longer existing metrics (generally should not happen
foreach ( $db_metrics as $db_metric ) {
dbDelete (
'application_metrics' ,
'app_id=? && metric=?' ,
2020-09-21 15:43:38 +02:00
[ $app [ 'app_id' ], $db_metric [ 'metric' ]]
2017-12-01 01:53:26 -06:00
);
echo '-' ;
}
2017-12-06 16:13:10 -06:00
echo PHP_EOL ;
2017-12-01 01:53:26 -06:00
}
2017-03-11 14:39:32 +00:00
}
2017-05-17 22:41:53 +01:00
2018-05-25 21:16:16 -05:00
/**
* This is to make it easier polling apps . Also to help standardize around JSON .
*
* The required keys for the returned JSON are as below .
* version - The version of the snmp extend script . Should be numeric and at least 1.
* error - Error code from the snmp extend script . Should be > 0 ( 0 will be ignored and negatives are reserved )
* errorString - Text to describe the error .
* data - An key with an array with the data to be used .
*
* If the app returns an error , an exception will be raised .
* Positive numbers will be errors returned by the extend script .
*
* Possible parsing related errors :
* - 2 : Failed to fetch data from the device
* - 3 : Could not decode the JSON .
* - 4 : Empty JSON parsed , meaning blank JSON was returned .
* - 5 : Valid json , but missing required keys
* - 6 : Returned version is less than the min version .
*
* Error checking may also be done via checking the exceptions listed below .
* JsonAppPollingFailedException , - 2 : Empty return from SNMP .
* JsonAppParsingFailedException , - 3 : Could not parse the JSON .
* JsonAppBlankJsonException , - 4 : Blank JSON .
* JsonAppMissingKeysException , - 5 : Missing required keys .
* JsonAppWrongVersionException , - 6 : Older version than supported .
* JsonAppExtendErroredException : Polling and parsing was good , but the returned data has an error set .
* This may be checked via $e -> getParsedJson () and then checking the
* keys error and errorString .
* The error value can be accessed via $e -> getCode ()
* The output can be accessed via $ -> getOutput () Only returned for code - 3 or lower .
* The parsed JSON can be access via $e -> getParsedJson ()
*
* All of the exceptions extend JsonAppException .
*
* If the error is less than - 1 , you can assume it is a legacy snmp extend script .
*
2021-09-08 23:35:56 +02:00
* @ param array $device
* @ param string $extend the extend name . For example , if 'zfs' is passed it will be converted to 'nsExtendOutputFull.3.122.102.115' .
* @ param int $min_version the minimum version to accept for the returned JSON . default : 1
2018-05-25 21:16:16 -05:00
* @ return array The json output data parsed into an array
2021-09-10 20:09:53 +02:00
*
2018-08-19 17:44:11 -05:00
* @ throws JsonAppBlankJsonException
* @ throws JsonAppExtendErroredException
* @ throws JsonAppMissingKeysException
* @ throws JsonAppParsingFailedException
2018-05-25 21:16:16 -05:00
* @ throws JsonAppPollingFailedException
2018-08-19 17:44:11 -05:00
* @ throws JsonAppWrongVersionException
2018-05-25 21:16:16 -05:00
*/
function json_app_get ( $device , $extend , $min_version = 1 )
{
2020-09-21 15:43:38 +02:00
$output = snmp_get ( $device , 'nsExtendOutputFull.' . string_to_oid ( $extend ), '-Oqv' , 'NET-SNMP-EXTEND-MIB' );
2018-05-25 21:16:16 -05:00
// make sure we actually get something back
if ( empty ( $output )) {
2020-09-21 15:59:34 +02:00
throw new JsonAppPollingFailedException ( 'Empty return from snmp_get.' , - 2 );
2018-05-25 21:16:16 -05:00
}
// turn the JSON into a array
$parsed_json = json_decode ( stripslashes ( $output ), true );
// improper JSON or something else was returned. Populate the variable with an error.
if ( json_last_error () !== JSON_ERROR_NONE ) {
2020-09-21 15:59:34 +02:00
throw new JsonAppParsingFailedException ( 'Invalid JSON' , $output , - 3 );
2018-05-25 21:16:16 -05:00
}
// There no keys in the array, meaning '{}' was was returned
if ( empty ( $parsed_json )) {
2020-09-21 15:59:34 +02:00
throw new JsonAppBlankJsonException ( 'Blank JSON returned.' , $output , - 4 );
2018-05-25 21:16:16 -05:00
}
// It is a legacy JSON app extend, meaning these are not set
2020-09-21 15:43:38 +02:00
if ( ! isset ( $parsed_json [ 'error' ], $parsed_json [ 'data' ], $parsed_json [ 'errorString' ], $parsed_json [ 'version' ])) {
2020-09-21 15:59:34 +02:00
throw new JsonAppMissingKeysException ( 'Legacy script or extend error, missing one or more required keys.' , $output , $parsed_json , - 5 );
2018-05-25 21:16:16 -05:00
}
if ( $parsed_json [ 'version' ] < $min_version ) {
2020-09-21 15:43:38 +02:00
throw new JsonAppWrongVersionException ( " Script,' " . $parsed_json [ 'version' ] . " ', older than required version of ' $min_version ' " , $output , $parsed_json , - 6 );
2018-05-25 21:16:16 -05:00
}
if ( $parsed_json [ 'error' ] != 0 ) {
throw new JsonAppExtendErroredException ( " Script returned exception: { $parsed_json [ 'errorString' ] } " , $output , $parsed_json , $parsed_json [ 'error' ]);
}
return $parsed_json ;
}
2018-11-22 09:05:38 -06:00
/**
* Some data arrays returned with json_app_get are deeper than
* update_application likes . This recurses through the array
* and flattens it out so it can nicely be inserted into the
* database .
*
* One argument is taken and that is the array to flatten .
*
2021-09-08 23:35:56 +02:00
* @ param array $array
* @ param string $prefix What to prefix to the name . Defaults to '' , nothing .
* @ param string $joiner The string to join the prefix , if set to something other
* than '' , and array keys with .
2018-11-22 09:05:38 -06:00
* @ return array The flattened array .
*/
function data_flatten ( $array , $prefix = '' , $joiner = '_' )
{
2020-09-21 15:43:38 +02:00
$return = [];
2018-11-22 09:05:38 -06:00
foreach ( $array as $key => $value ) {
if ( is_array ( $value )) {
if ( strcmp ( $prefix , '' )) {
2020-09-21 15:43:38 +02:00
$key = $prefix . $joiner . $key ;
2018-11-22 09:05:38 -06:00
}
$return = array_merge ( $return , data_flatten ( $value , $key , $joiner ));
} else {
if ( strcmp ( $prefix , '' )) {
2020-09-21 15:43:38 +02:00
$key = $prefix . $joiner . $key ;
2018-11-22 09:05:38 -06:00
}
$return [ $key ] = $value ;
}
}
return $return ;
}