$sensor) { $options = []; $sensor['index'] = $index; // Index in descr foreach (explode('.', $index) as $k => $i) { $sensor['index' . $k] = $i; // Index parts } $dot_index = strlen($index) ? '.' . $index : ''; $oid_num = $entry['oid_num'] . $dot_index; // Determine the sensor class $class = entity_class_definition($device, $entry, $sensor, 'sensor'); if (!$class) { continue; // Break foreach. Proceed to next sensor! } // %i% can be used in description, a counter is kept per sensor class $counters[$class]++; // Generate specific keys used during rewrites $sensor['class'] = nicecase($class); // Class in descr $sensor['i'] = $counters[$class]; // i++ counter in descr (per sensor class) $options['i'] = $sensor['i'] - 1; // Rule-based entity linking (associate before discovery_check_requires()). if ($measured = entity_measured_match_definition($device, $entry, $sensor, 'sensor')) { $options = array_merge($options, $measured); $sensor = array_merge($sensor, $measured); // append to $sensor for %descr% tags, ie %port_label% } elseif (isset($entry['measured_match'])) { // In case when measured entity not found //$sensor['port_label'] = 'Port ' . $counters[$class]; $sensor['port_label'] = $index; // End rule-based entity linking } elseif (isset($entry['entPhysicalIndex'])) { // Just set physical index $options['entPhysicalIndex'] = array_tag_replace($sensor, $entry['entPhysicalIndex']); } // Check valid exist with entity tags if (discovery_check_if_type_exist($entry, 'sensor', $sensor)) { continue; } // Check array requirements list if (discovery_check_requires($device, $entry, $sensor, 'sensor')) { continue; } // Addition (before scale) if (isset($entry['addition'])) { // Static unit definition $options['sensor_addition'] = $entry['addition']; } // Scale $scale = entity_scale_definition($device, $entry, $sensor); // Limits $options = array_merge($options, entity_limits_definition($device, $entry, $sensor, $scale)); // Add scale poller option, see: BLUECOAT-SG-SENSOR-MIB, SL-OTN-MIB if (isset($entry['scale_poll'], $entry['oid_scale']) && $entry['scale_poll']) { // walk this oid on every poll if (isset($entry['scale_si']) && $entry['scale_si']) { $options['oid_scale_si'] = $entry['oid_scale']; } print_debug_vars($options); } // Unit if (isset($entry['unit'])) { // Static unit definition $options['sensor_unit'] = $entry['unit']; } if (isset($entry['oid_unit'])) { if (isset($sensor[$entry['oid_unit']])) { // Unit in same table $unit = $sensor[$entry['oid_unit']]; } elseif (str_contains_array($entry['oid_unit'], '.')) { // Unit is outside from table with single index, see VERTIV-V5-MIB $unit = snmp_cache_oid($device, $entry['oid_unit'], $mib); } // Translate unit from specific Oid if (isset($entry['map_unit'][$unit])) { $options['sensor_unit'] = $entry['map_unit'][$unit]; } } $value = snmp_fix_numeric($sensor[$entry['oid']], $options['sensor_unit']); if (!discovery_check_value_valid($device, $value, $entry, 'sensor')) { continue; } // Generate Description $descr = entity_descr_definition('sensor', $entry, $sensor, $sensors_count); // Rename old (converted) RRDs to definition format if (isset($entry['rename_rrd'])) { $options['rename_rrd'] = $entry['rename_rrd']; } elseif (isset($entry['rename_rrd_full'])) { $options['rename_rrd_full'] = $entry['rename_rrd_full']; } print_debug_vars($options); discover_sensor_ng($device, $class, $mib, $entry['oid'], $oid_num, $index, $entry['type'], $descr, $scale, $value, $options); } echo '] '; } // Compatibility wrapper! function discover_sensor($class, $device, $numeric_oid, $index, $type, $sensor_descr, $scale = 1, $value = NULL, $options = [], $poller_type = NULL) { if (!safe_empty($poller_type)) { $options['poller_type'] = $poller_type; } return discover_sensor_ng($device, $class, '', '', $numeric_oid, $index, $type, $sensor_descr, $scale, $value, $options); } // TESTME needs unit testing /** * Discover a new sensor on a device * * This function adds a status sensor to a device, if it does not already exist. * Data on the sensor is updated if it has changed, and an event is logged with regards to the changes. * * Status sensors are handed off to discover_status(). * Current sensor values are rectified in case they are broken (added spaces, etc). * * @param array $device Device array sensor is being discovered on * @param string $class Class of sensor (voltage, temperature, etc.) * @param string $mib SNMP MIB name * @param string $object SNMP Named Oid of sensor (without index) * @param string $oid SNMP Numeric Oid of sensor (without index) * @param string $index SNMP index of sensor * @param string $type Type of sensor * @param string $sensor_descr Description of sensor * @param int $scale Scale of sensor (0.1 for 1:10 scale, 10 for 10:1 scale, etc) * @param string|float $value Current sensor value * @param array $options Options (sensor_unit, limit_auto, limit*, poller_type, scale, measured_*) * * @return bool */ function discover_sensor_ng($device, $class, $mib, $object, $oid, $index, $type, $sensor_descr, $scale = 1, $value = NULL, $options = []) { global $config; //echo 'MIB:'; print_vars($mib); $poller_type = (isset($options['poller_type']) ? $options['poller_type'] : 'snmp'); $sensor_deleted = 0; // If this is actually a status indicator, pass it off to discover_status() then return. if ($class === 'state' || $class === 'status') { print_debug("Redirecting call to discover_status()."); return discover_status_ng($device, $mib, $object, $oid, $index, $type, $sensor_descr, $value, $options); } if ($class && isset($config['counter_types'][$class])) { print_debug("Redirecting call to discover_counter()."); return discover_counter($device, $class, $mib, $object, $oid, $index, $sensor_descr, $scale, $value, $options); } if ($class === 'power' && $options['measured_class'] === 'port' && // Power sensor with measured port entity $config['sensors']['port']['power_to_dbm'] && // Convert option set to TRUE $options['sensor_unit'] !== 'W' && !str_icontains_array($sensor_descr, 'PoE')) { // Not forced W unit, not PoE // DOM Power sensors convert to dBm print_debug("DOM power sensor forced to dBm sensor."); $options['sensor_unit'] = 'W'; return discover_sensor_ng($device, 'dbm', $mib, $object, $oid, $index, $type, $sensor_descr, $scale, $value, $options); } // Init main $param_main = [ 'oid' => 'sensor_oid', 'type' => 'sensor_type', // anyway compare type on update, while db query is case-insensitive 'sensor_descr' => 'sensor_descr', 'scale' => 'sensor_multiplier', 'sensor_deleted' => 'sensor_deleted', 'mib' => 'sensor_mib', 'object' => 'sensor_object' ]; // Init numeric values if (!is_numeric($scale) || $scale == 0) { $scale = 1; } // Generate type if it's not provided if (safe_empty($type)) { $type = $mib . '-' . $object; } // Another hack for FIBERSTORE-MIB/FS-SWITCH-MIB multi lane DOM sensors // Append unit as sensor type part if (isset($options['sensor_unit']) && str_starts($options['sensor_unit'], 'split')) { $type .= '-' . $options['sensor_unit']; } // Skip discovery sensor if value not numeric or null (default) if (strlen($value)) { // Some retarded devices report data with spaces and commas // STRING: " 20,4" $value = snmp_fix_numeric($value, $options['sensor_unit']); } if (is_numeric($value)) { $attrib_type = 'sensor_addition'; if (isset($options[$attrib_type]) && is_numeric($options[$attrib_type])) { // See in FOUNDRY-POE-MIB $value += $options[$attrib_type]; } $value = scale_value($value, $scale); // $value *= $scale; // Scale before unit conversion $value = value_to_si($value, $options['sensor_unit'], $class); // Convert if not SI unit } else { print_debug("Sensor skipped by not numeric value: '$value', '$sensor_descr'"); if (strlen($value)) { print_debug("Perhaps this is named sensor, use discover_status() instead."); } return FALSE; } $param_limits = ['limit_high' => 'sensor_limit', 'limit_high_warn' => 'sensor_limit_warn', 'limit_low' => 'sensor_limit_low', 'limit_low_warn' => 'sensor_limit_low_warn']; foreach ($param_limits as $key => $column) { // Set limits vars and unit convert if required if (is_numeric($options[$key])) { $$key = value_to_si($options[$key], $options['sensor_unit'], $class); // Force disable limit auto if any limit passed if (!isset($options['limit_auto']) && is_numeric($$key)) { $options['limit_auto'] = FALSE; } } else { $$key = NULL; } //$$key = (is_numeric($options[$key]) ? value_to_si($options[$key], $options['sensor_unit'], $class) : NULL); } // Auto calculate high/low limits if not passed $limit_auto = !isset($options['limit_auto']) || (bool)$options['limit_auto']; if (!$limit_auto) { // reset incorrect warning limits, see: // https://jira.observium.org/browse/OBS-3818 if ($limit_high_warn === 0 && $limit_high_warn < $limit_high && ($limit_high_warn < $limit_low_warn || $limit_high_warn < $limit_low)) { print_debug("High Warning Limit was reset to NULL"); $limit_high_warn = NULL; } if ($limit_low_warn === 0 && $limit_low_warn > $limit_low && ($limit_low_warn > $limit_high_warn || $limit_low_warn > $limit_high)) { print_debug("Low Warning Limit was reset to NULL"); $limit_low_warn = NULL; } // another case with incorrect warnings: // -30, 0, 0, 3.008 // -30, -30, 0, 3.008 // 0, -30, 0, 1.99 // https://jira.observium.org/browse/OBS-3597 if (is_numeric($limit_low_warn) && is_numeric($limit_low) && $limit_low > $limit_low_warn) { print_debug("Low Warning Limit was swapped $limit_low (low) <> $limit_low_warn (warn)."); // swap low warn & low $tmp = $limit_low_warn; $limit_low_warn = $limit_low; $limit_low = $tmp; unset($tmp); } if (is_numeric($limit_low_warn) && (($limit_high_warn === $limit_low_warn) || ($limit_low === $limit_low_warn))) { print_debug("Low Warning Limit was reset to NULL"); // reset incorrect low warning $limit_low_warn = NULL; } } // Init optional $param_opt = ['entPhysicalIndex', 'entPhysicalClass', 'entPhysicalIndex_measured', 'measured_class', 'measured_entity', 'measured_entity_label', 'sensor_unit']; foreach ($param_opt as $key) { $$key = $options[$key] ?: NULL; } print_debug("Discover sensor: [class: $class, device: " . $device['hostname'] . ", oid: $oid, index: $index, type: $type, descr: $sensor_descr, scale: $scale, limits: ($limit_low, $limit_low_warn, $limit_high_warn, $limit_high), CURRENT: $value, $entPhysicalIndex, $entPhysicalClass"); // Check sensor ignore filters if (entity_descr_check($sensor_descr, 'sensor')) { return FALSE; } //foreach ($config['ignore_sensor'] as $bi) { if (strcasecmp($bi, $sensor_descr) == 0) { print_debug("Skipped by equals: $bi, $sensor_descr "); return FALSE; } } //foreach ($config['ignore_sensor_string'] as $bi) { if (stripos($sensor_descr, $bi) !== FALSE) { print_debug("Skipped by strpos: $bi, $sensor_descr "); return FALSE; } } //foreach ($config['ignore_sensor_regexp'] as $bi) { if (preg_match($bi, $sensor_descr) > 0) { print_debug("Skipped by regexp: $bi, $sensor_descr "); return FALSE; } } if (!is_null($limit_low_warn) && !is_null($limit_high_warn) && ($limit_low_warn > $limit_high_warn)) { // Fix high/low thresholds (i.e. on negative numbers) [$limit_high_warn, $limit_low_warn] = [$limit_low_warn, $limit_high_warn]; } print_debug_vars($limit_auto); print_debug_vars($limit_high); print_debug_vars($limit_high_warn); print_debug_vars($limit_low_warn); print_debug_vars($limit_low); if (!dbExist('sensors', '`poller_type`= ? AND `sensor_class` = ? AND `device_id` = ? AND `sensor_type` = ? AND `sensor_index` = ?', [$poller_type, $class, $device['device_id'], $type, $index])) { if (!is_numeric($limit_high)) { $limit_high = sensor_limit_high($class, $value, $limit_auto); } if (!is_numeric($limit_low)) { $limit_low = sensor_limit_low($class, $value, $limit_auto); } if (!is_null($limit_low) && !is_null($limit_high) && ($limit_low > $limit_high)) { // Fix high/low thresholds (i.e. on negative numbers) [$limit_high, $limit_low] = [$limit_low, $limit_high]; print_debug("High/low limits swapped."); } $sensor_insert = ['poller_type' => $poller_type, 'sensor_class' => $class, 'device_id' => $device['device_id'], 'sensor_index' => $index, 'sensor_type' => $type]; foreach ($param_main as $key => $column) { $sensor_insert[$column] = $$key; } foreach ($param_limits as $key => $column) { // Convert strings/numbers to (float) or to array('NULL') $$key = is_numeric($$key) ? (float)$$key : ['NULL']; $sensor_insert[$column] = $$key; } foreach ($param_opt as $key) { if (is_null($$key)) { $$key = ['NULL']; } $sensor_insert[$key] = $$key; } $sensor_insert['sensor_value'] = $value; $sensor_insert['sensor_polled'] = time(); // array('NOW()'); // this field is INT(11) $sensor_id = dbInsert($sensor_insert, 'sensors'); // Extra (rare) params foreach (['sensor_addition', 'oid_scale_si'] as $attrib_type) { if (isset($options[$attrib_type]) && !safe_empty($options[$attrib_type])) { // Add sensor attrib for use in poller set_entity_attrib('sensor', $sensor_id, $attrib_type, $options[$attrib_type]); } } print_debug("( $sensor_id inserted )"); echo('+'); log_event("Sensor added: $class $type $index $sensor_descr", $device, 'sensor', $sensor_id); } else { $sensor_entry = dbFetchRow("SELECT * FROM `sensors` WHERE `sensor_class` = ? AND `device_id` = ? AND `sensor_type` = ? AND `sensor_index` = ?", [$class, $device['device_id'], $type, $index]); $sensor_id = $sensor_entry['sensor_id']; // Limits if (!$sensor_entry['sensor_custom_limit']) { if (!is_numeric($limit_high)) { if ($sensor_entry['sensor_limit'] !== '') { // Calculate a reasonable limit $limit_high = sensor_limit_high($class, $value, $limit_auto); } else { // Use existing limit. (this is wrong! --mike) $limit_high = $sensor_entry['sensor_limit']; } } if (!is_numeric($limit_low)) { if ($sensor_entry['sensor_limit_low'] !== '') { // Calculate a reasonable limit $limit_low = sensor_limit_low($class, $value, $limit_auto); } else { // Use existing limit. (this is wrong! --mike) $limit_low = $sensor_entry['sensor_limit_low']; } } // Fix high/low thresholds (i.e. on negative numbers) if (!is_null($limit_low) && !is_null($limit_high) && ($limit_low > $limit_high)) { [$limit_high, $limit_low] = [$limit_low, $limit_high]; print_debug("High/low limits swapped."); } // Update limits $update = []; $update_msg = []; $debug_msg = 'Current sensor value: "' . $value . '", scale: "' . $scale . '"' . PHP_EOL; foreach ($param_limits as $key => $column) { // $key - param name, $$key - param value, $column - column name in DB for $key $debug_msg .= ' ' . $key . ': "' . $sensor_entry[$column] . '" -> "' . $$key . '"' . PHP_EOL; //convert strings/numbers to identical type (float) or to array('NULL') for correct comparison $$key = is_numeric($$key) ? (float)$$key : ['NULL']; $sensor_entry[$column] = is_numeric($sensor_entry[$column]) ? (float)$sensor_entry[$column] : ['NULL']; if (!$limit_auto) { // Hard passed or device reported limits if ($$key !== $sensor_entry[$column]) { $update[$column] = $$key; $update_msg[] = $key . ' -> "' . (is_array($$key) ? 'NULL' : $$key) . '"'; } } elseif (float_cmp($$key, $sensor_entry[$column], 0.01) !== 0) { // Auto generated limits $update[$column] = $$key; $update_msg[] = $key . ' -> "' . (is_array($$key) ? 'NULL' : $$key) . '"'; } } if (count($update)) { echo("L"); print_debug($debug_msg); if ($config['sensors']['limits_events']) { log_event('Sensor updated (limits): ' . implode(', ', $update_msg), $device, 'sensor', $sensor_entry['sensor_id']); } $updated = dbUpdate($update, 'sensors', '`sensor_id` = ?', [$sensor_entry['sensor_id']]); } } $update = []; foreach ($param_main as $key => $column) { if (float_cmp($$key, $sensor_entry[$column]) !== 0) { $update[$column] = $$key; } } foreach ($param_opt as $key) { if ($$key != $sensor_entry[$key]) { $update[$key] = !is_null($$key) ? $$key : ['NULL']; } } // Extra (rare) params foreach (['sensor_addition', 'oid_scale_si'] as $attrib_type) { $attrib = get_entity_attrib('sensor', $sensor_entry['sensor_id'], $attrib_type); if (isset($options[$attrib_type]) && !safe_empty($options[$attrib_type])) { // Add sensor attrib for use in poller if ($attrib != $options[$attrib_type]) { set_entity_attrib('sensor', $sensor_id, $attrib_type, $options[$attrib_type]); } } elseif (!safe_empty($attrib)) { del_entity_attrib('sensor', $sensor_entry['sensor_id'], $attrib_type); } } if (count($update)) { $updated = dbUpdate($update, 'sensors', '`sensor_id` = ?', [$sensor_entry['sensor_id']]); echo('U'); log_event("Sensor updated: $class $type $index $sensor_descr", $device, 'sensor', $sensor_entry['sensor_id']); } else { echo('.'); } } // Rename old (converted) RRDs to definition format // Allow with changing class or without if (isset($options['rename_rrd']) || isset($options['rename_rrd_full'])) { $rrd_tags = ['index' => $index, 'type' => $type, 'mib' => $mib, 'object' => $object, 'oid' => $object, 'i' => $options['i']]; if (isset($options['rename_rrd'])) { $options['rename_rrd'] = array_tag_replace($rrd_tags, $options['rename_rrd']); $old_rrd = 'sensor-' . $class . '-' . $options['rename_rrd']; } elseif (isset($options['rename_rrd_full'])) { $options['rename_rrd_full'] = array_tag_replace($rrd_tags, $options['rename_rrd_full']); $old_rrd = 'sensor-' . $options['rename_rrd_full']; } $new_rrd = 'sensor-' . $class . '-' . $type . '-' . $index; rename_rrd($device, $old_rrd, $new_rrd); } $GLOBALS['valid']['sensor'][$class][$type][$index] = 1; return $sensor_id; //return TRUE; } // TESTME needs unit testing /** * Calculate lower limit on a sensor * * @param string $class Sensor class (voltage, temperature, ...) * @param string $value Current sensor value to use as base * @param bool $auto Set to false to not set an automatic limit * * @return string */ function sensor_limit_low($class, $value, $auto = TRUE) { if (!$auto || $value == 0) { return NULL; } // Do not calculate limit $limit = NULL; switch ($class) { case 'temperature': if ($value > 0) { $limit = 0; // Freezing cold should be enough of a lower limit. } break; case 'voltage': if ($value < 0) { $limit = $value * (1 + (sgn($value) * 0.15)); } else { $limit = $value * (1 - (sgn($value) * 0.15)); } break; case 'humidity': $limit = 20; break; case 'frequency': $limit = $value * 0.95; break; case 'current': $limit = NULL; break; case 'fanspeed': $limit = $value * 0.80; break; case 'power': $limit = NULL; break; } return $limit; } function sensor_limit_low_warn($class, $value, $auto = TRUE) { if (!$auto || $value == 0) { return NULL; } // Do not calculate limit $limit = NULL; switch ($class) { case 'temperature': $limit = NULL; break; case 'voltage': if ($value < 0) { $limit = $value * (1 + (sgn($value) * 0.10)); } else { $limit = $value * (1 - (sgn($value) * 0.10)); } break; case 'humidity': $limit = 25; break; case 'frequency': $limit = $value * 0.97; break; case 'current': $limit = NULL; break; case 'fanspeed': $limit = $value * 0.85; break; case 'power': $limit = NULL; break; } return $limit; } /** * Calculate upper limit on a sensor * * @param string $class Sensor class (voltage, temperature, ...) * @param string $value Current sensor value to use as base * @param bool $auto Set to false to not set an automatic limit * * @return string */ function sensor_limit_high($class, $value, $auto = TRUE) { if (!$auto || $value == 0) { return NULL; } // Do not calculate limit $limit = NULL; switch ($class) { case 'temperature': if ($value < 0) { // Negative temperatures are usually used for "Thermal margins", // indicating how far from the critical point we are. $limit = 0; } else { $limit = $value * 1.60; } break; case 'voltage': if ($value < 0) { $limit = $value * (1 - (sgn($value) * 0.15)); } else { $limit = $value * (1 + (sgn($value) * 0.15)); } break; case 'humidity': $limit = 70; break; case 'frequency': $limit = $value * 1.05; break; case 'current': $limit = $value * 1.50; break; case 'fanspeed': $limit = $value * 1.80; break; case 'power': $limit = $value * 1.50; break; } return $limit; } function sensor_limit_high_warn($class, $value, $auto = TRUE) { if (!$auto || $value == 0) { return NULL; } // Do not calculate limit $limit = NULL; switch ($class) { case 'temperature': if ($value < 0) { // Negative temperatures are usually used for "Thermal margins", // indicating how far from the critical point we are. //$limit = 0; } else { $limit = $value * 1.50; } break; case 'voltage': if ($value < 0) { $limit = $value * (1 - (sgn($value) * 0.10)); } else { $limit = $value * (1 + (sgn($value) * 0.10)); } break; case 'humidity': $limit = 65; break; case 'frequency': $limit = $value * 1.03; break; case 'current': $limit = $value * 1.30; break; case 'fanspeed': $limit = $value * 1.50; break; case 'power': $limit = $value * 1.30; break; } return $limit; } // DOCME needs phpdoc block // TESTME needs unit testing function check_valid_sensors($device, $class, $valid, $poller_type = 'snmp') { $entries = dbFetchRows("SELECT * FROM `sensors` WHERE `device_id` = ? AND `sensor_class` = ? AND `poller_type` = ? AND `sensor_deleted` = '0'", [$device['device_id'], $class, $poller_type]); if (safe_count($entries)) { foreach ($entries as $entry) { $index = $entry['sensor_index']; $type = $entry['sensor_type']; if (!$valid[$class][$type][$index]) { echo("-"); print_debug("Sensor deleted: $index -> $type"); //dbDelete('sensors', "`sensor_id` = ?", array($entry['sensor_id'])); dbUpdate(['sensor_deleted' => '1'], 'sensors', '`sensor_id` = ?', [$entry['sensor_id']]); //dbDelete('sensors-state', "`sensor_id` = ?", array($entry['sensor_id'])); foreach (get_entity_attribs('sensor', $entry['sensor_id']) as $attrib_type => $value) { del_entity_attrib('sensor', $entry['sensor_id'], $attrib_type); } log_event("Sensor deleted: " . $entry['sensor_class'] . " " . $entry['sensor_type'] . " " . $entry['sensor_index'] . " " . $entry['sensor_descr'], $device, 'sensor', $entry['sensor_id']); } } } } // Poll a sensor function poll_sensor($device, $class, $unit, &$oid_cache) { global $config, $graphs, $table_rows; $sql = "SELECT * FROM `sensors`"; //$sql .= " LEFT JOIN `sensors-state` USING(`sensor_id`)"; $sql .= " WHERE `sensor_class` = ? AND `device_id` = ? AND `sensor_deleted` = ?"; $sql .= ' ORDER BY `sensor_oid`'; // This fix polling some OIDs (when not ordered) //print_vars($GLOBALS['cache']['entity_attribs']); foreach (dbFetchRows($sql, [$class, $device['device_id'], '0']) as $sensor_db) { $sensor_poll = []; //print_cli_heading("Sensor: ".$sensor_db['sensor_descr'], 3); if (OBS_DEBUG) { echo("Checking (" . $sensor_db['poller_type'] . ") $class " . $sensor_db['sensor_descr'] . " "); print_debug_vars($sensor_db, 1); } $sensor_poll['sensor_value'] = get_sensor_cached_value($device, $oid_cache, $sensor_db); if ($sensor_poll['sensor_value'] === FALSE) { // no agent/ipmi values continue; } $sensor_polled_time = time(); // Store polled time for current sensor print_debug_vars($sensor_poll, 1); // Addition, Scale and Unit conversion $sensor_poll['sensor_value'] = sensor_value_scale($device, $sensor_poll['sensor_value'], $sensor_db); //print_cli_data("Value", $sensor_poll['sensor_value'] . "$unit ", 3); // FIXME this block and the other block below it are kinda retarded. They should be merged and simplified. if ($sensor_db['sensor_disable']) { $sensor_poll['sensor_event'] = 'ignore'; $sensor_poll['sensor_status'] = 'Sensor disabled.'; } else { $sensor_poll['sensor_event'] = check_thresholds($sensor_db['sensor_limit_low'], $sensor_db['sensor_limit_low_warn'], $sensor_db['sensor_limit_warn'], $sensor_db['sensor_limit'], $sensor_poll['sensor_value']); // Percent based classes, ignore invalid values. // See: CPQIDA-MIB::cpqDaLogDrvPercentRebuild if (in_array($class, ["progress", "load", "capacity"], TRUE) && ($sensor_poll['sensor_value'] < 0 || $sensor_poll['sensor_value'] > 100)) { $sensor_poll['sensor_event'] = 'ignore'; $sensor_poll['sensor_status'] = 'Sensor beyond normal values.'; } if ($sensor_poll['sensor_event'] === 'alert') { $sensor_poll['sensor_status'] = 'Sensor critical thresholds exceeded.'; // Force ignore state if measured entity is in Shutdown state $measured_class = $sensor_db['measured_class']; if (is_numeric($sensor_db['measured_entity']) && isset($config['sensors'][$measured_class]['ignore_shutdown']) && $config['sensors'][$measured_class]['ignore_shutdown']) { $measured_entity = get_entity_by_id_cache($measured_class, $sensor_db['measured_entity']); print_debug_vars($measured_entity); // Currently only for ports if (isset($measured_entity['ifAdminStatus']) && $measured_entity['ifAdminStatus'] === 'down') { $sensor_poll['sensor_event'] = 'ignore'; $sensor_poll['sensor_status'] = 'Sensor critical thresholds exceeded, but ignored.'; } } } elseif ($sensor_poll['sensor_event'] === 'warning') { $sensor_poll['sensor_status'] = 'Sensor warning thresholds exceeded.'; } else { $sensor_poll['sensor_status'] = ''; } // Reset Alert if sensor ignored if ($sensor_poll['sensor_event'] !== 'ok' && $sensor_db['sensor_ignore']) { $sensor_poll['sensor_event'] = 'ignore'; $sensor_poll['sensor_status'] = 'Sensor thresholds exceeded, but ignored.'; } } // If last change never set, use current time if (empty($sensor_db['sensor_last_change'])) { $sensor_db['sensor_last_change'] = $sensor_polled_time; } if ($sensor_poll['sensor_event'] != $sensor_db['sensor_event']) { // Sensor event changed, log and set sensor_last_change $sensor_poll['sensor_last_change'] = $sensor_polled_time; if ($sensor_db['sensor_event'] === 'ignore') { print_message("[%ySensor Ignored%n]", 'color'); } elseif (is_numeric($sensor_db['sensor_limit_low']) && $sensor_db['sensor_value'] >= $sensor_db['sensor_limit_low'] && $sensor_poll['sensor_value'] < $sensor_db['sensor_limit_low']) { // If old value greater than low limit and new value less than low limit $msg = ucfirst($class) . " Alarm: " . $device['hostname'] . " " . $sensor_db['sensor_descr'] . " is under threshold: " . $sensor_poll['sensor_value'] . "$unit (< " . $sensor_db['sensor_limit_low'] . "$unit)"; log_event(ucfirst($class) . ' ' . $sensor_db['sensor_descr'] . " under threshold: " . $sensor_poll['sensor_value'] . " $unit (< " . $sensor_db['sensor_limit_low'] . " $unit)", $device, 'sensor', $sensor_db['sensor_id'], 'warning'); } elseif (is_numeric($sensor_db['sensor_limit']) && $sensor_db['sensor_value'] <= $sensor_db['sensor_limit'] && $sensor_poll['sensor_value'] > $sensor_db['sensor_limit']) { // If old value less than high limit and new value greater than high limit $msg = ucfirst($class) . " Alarm: " . $device['hostname'] . " " . $sensor_db['sensor_descr'] . " is over threshold: " . $sensor_poll['sensor_value'] . "$unit (> " . $sensor_db['sensor_limit'] . "$unit)"; log_event(ucfirst($class) . ' ' . $sensor_db['sensor_descr'] . " above threshold: " . $sensor_poll['sensor_value'] . " $unit (> " . $sensor_db['sensor_limit'] . " $unit)", $device, 'sensor', $sensor_db['sensor_id'], 'warning'); } } else { // If sensor not changed, leave old last_change $sensor_poll['sensor_last_change'] = $sensor_db['sensor_last_change']; } // Send statistics array via AMQP/JSON if AMQP is enabled globally and for the ports module if ($config['amqp']['enable'] == TRUE && $config['amqp']['modules']['sensors']) { $json_data = ['value' => $sensor_poll['sensor_value']]; messagebus_send(['attribs' => ['t' => time(), 'device' => $device['hostname'], 'device_id' => $device['device_id'], 'e_type' => 'sensor', 'e_class' => $sensor_db['sensor_class'], 'e_type' => $sensor_db['sensor_type'], 'e_index' => $sensor_db['sensor_index']], 'data' => $json_data]); } // Add table row $table_rows[] = [$sensor_db['sensor_descr'], $sensor_db['sensor_class'], $sensor_db['sensor_type'], $sensor_db['poller_type'], $sensor_poll['sensor_value'] . $unit, $sensor_poll['sensor_event'], format_unixtime($sensor_poll['sensor_last_change'])]; // Update StatsD/Carbon if ($config['statsd']['enable'] == TRUE) { StatsD ::gauge(str_replace(".", "_", $device['hostname']) . '.' . 'sensor' . '.' . $sensor_db['sensor_class'] . '.' . $sensor_db['sensor_type'] . '.' . $sensor_db['sensor_index'], $sensor_poll['sensor_value']); } // Update RRD - FIXME - can't convert to NG because filename is dynamic! new function should return index instead of filename. $rrd_file = get_sensor_rrd($device, $sensor_db); rrdtool_create($device, $rrd_file, "DS:sensor:GAUGE:600:-20000:U"); rrdtool_update($device, $rrd_file, "N:" . $sensor_poll['sensor_value']); // Enable graph $graphs[$sensor_db['sensor_class']] = TRUE; // Check alerts $metrics = []; $metrics['sensor_value'] = $sensor_poll['sensor_value']; $metrics['sensor_event'] = $sensor_poll['sensor_event']; $metrics['sensor_event_uptime'] = $sensor_polled_time - $sensor_poll['sensor_last_change']; $metrics['sensor_status'] = $sensor_poll['sensor_status']; check_entity('sensor', $sensor_db, $metrics); // Add to MultiUpdate SQL State $GLOBALS['multi_update_db'][] = [ 'sensor_id' => $sensor_db['sensor_id'], // UNIQUE index 'sensor_value' => $sensor_poll['sensor_value'], 'sensor_event' => $sensor_poll['sensor_event'], 'sensor_status' => $sensor_poll['sensor_status'], 'sensor_last_change' => $sensor_poll['sensor_last_change'], 'sensor_polled' => $sensor_polled_time ]; } } /** * Parse output of ipmitool sensor * * @param $device * @param $results * @param string $source * * @return mixed */ function parse_ipmitool_sensor($device, $results, $source = 'ipmi') { global $valid, $config; $index = 0; $ipmi_sensors = []; foreach (explode("\n", $results) as $row) { $index++; # BB +1.1V IOH | 1.089 | Volts | ok | na | 1.027 | 1.054 | 1.146 | 1.177 | na # Fan5 | 0.000 | RPM | nr | 200.000 | 300.000 | 400.000 | na | na | na [$desc, $current, $unit, $state, $low_nonrecoverable, $limit_low, $limit_low_warn, $limit_high_warn, $limit_high, $high_nonrecoverable] = explode('|', $row); // Trim values $current = trim($current); $state = trim($state); $unit = trim($unit); $desc = trim($desc); if ($current !== "na" && $state !== "nr") { if ($unit === 'discrete') { // Statuses if (!$config['ipmi_unit']['discrete']) { print_debug("Discrete statuses disabled."); continue; } print_warning("Discrete statuses support is very unstable!"); $options = []; $ipmi_state = hexdec($state) + 0; print_debug("Descr: $desc, Current: $current, State: $state ($ipmi_state), Unit: $unit"); // 0 - fail, 1 - ok switch ($current) { // Intrusion | 0x0 | discrete | 0x0100| na | na | na | na | na | na // Power Supply | 0x0 | discrete | 0x0000| na | na | na | na | na | na // PS1 Status | 0x1 | discrete | 0x0100| na | na | na | na | na | na // PS2 Status | 0x1 | discrete | 0x0100| na | na | na | na | na | na case '0x0': $state = in_array($state, ['0x0000', '0x01ff']) ? 1 : 0; break; case '0x1': $state = in_array($state, ['0x0000', '0x01ff']) ? 0 : 1; break; } if (str_istarts($desc, ['chassis', 'intrusion'])) { // Chassis $options = ['entPhysicalClass' => 'chassis']; //$state = ($state === '0x0000') ? 1 : 0; } elseif (str_istarts($desc, ['ps', 'power supply'])) { // Power Supply $options = ['entPhysicalClass' => 'powersupply']; //$state = in_array($state, [ '0x0000', '0x01ff' ]) ? 1 : 0; } else { // All others $options = ['entPhysicalClass' => 'other']; //$state = ($state === '0x0000') ? 1 : 0; } $state_type = $source === 'ipmi' ? 'ipmi-state' : 'unix-agent-state'; discover_status($device, '', $index, $state_type, $desc, $state, $options, $source); $ipmi_sensors['state'][$state_type][$index] = ['description' => $desc, 'current' => $state, 'index' => $index]; } elseif (isset($config['ipmi_unit'][$unit])) { print_debug("Descr: $desc, Current: $current, State: $state, Unit: $unit"); // Numeric sensors $class = $config['ipmi_unit'][$unit]; if (($class === 'capacity') && str_istarts($desc, [ 'fan' ])) { $class = 'load'; } $options = []; foreach (['limit_high', 'limit_low', 'limit_high_warn', 'limit_low_warn'] as $limit) { $limit_value = trim($$limit); if (is_numeric($limit_value)) { $options[$limit] = $limit_value; } } discover_sensor($class, $device, '', $index, $source, $desc, 1, $current, $options, $source); $ipmi_sensors[$class][$source][$index] = ['description' => $desc, 'current' => $current, 'index' => $index, 'unit' => $unit]; } } } return $ipmi_sensors; } // DOCME needs phpdoc block // TESTME needs unit testing function get_sensor_rrd($device, $sensor) { global $config; # For IPMI/agent, sensors tend to change order, and there is no index, so we prefer to use the description as key here. if ((isset($config['os'][$device['os']]['sensor_descr']) && $config['os'][$device['os']]['sensor_descr']) || // per os definition (isset($config['mibs'][$sensor['sensor_mib']]['sensor_descr']) && $config['mibs'][$sensor['sensor_mib']]['sensor_descr']) || // per mib definition ($sensor['poller_type'] != "snmp" && $sensor['poller_type'] != '')) { $index = $sensor['sensor_descr']; } else { $index = $sensor['sensor_index']; } // note, in discover_sensor_ng() sensor_type == %mib%-%object% $rrd_file = "sensor-" . $sensor['sensor_class'] . "-" . $sensor['sensor_type'] . "-" . $index . ".rrd"; return $rrd_file; } // DOCME needs phpdoc block // TESTME needs unit testing function get_sensor_by_id($sensor_id) { if (is_numeric($sensor_id)) { $sensor = dbFetchRow("SELECT * FROM `sensors` WHERE `sensor_id` = ?", [$sensor_id]); } if (is_array($sensor)) { return $sensor; } return FALSE; } function get_sensor_cached_value($device, $oid_cache, &$sensor_db) { global $agent_sensors, $ipmi_sensors; $class = $sensor_db['sensor_class']; if ($sensor_db['poller_type'] === "snmp") { if (!str_starts_with($sensor_db['sensor_oid'], '.')) { // Fix first dot in oid for caching $sensor_db['sensor_oid'] = '.' . $sensor_db['sensor_oid']; } $sensor_oid = $sensor_db['sensor_oid']; // Take the value from $oid_cache if we have it, else snmp_get it if (isset($oid_cache[$sensor_oid]) && !safe_empty($oid_cache[$sensor_oid])) { $sensor_value = $oid_cache[$sensor_oid]; $cached = TRUE; } else { $sensor_value = snmp_get_oid($device, $sensor_oid, 'SNMPv2-MIB'); $cached = FALSE; } // Compat with runtime sensors without set unit // Use timetick conversion only when snmpdata is formatted as timetick 0:0:21:00.00 if ($class === "runtime" && empty($sensor_db['sensor_unit']) && str_contains($sensor_value, ':')) { $sensor_db['sensor_unit'] = 'timeticks'; } $sensor_value = snmp_fix_numeric($sensor_value, $sensor_db['sensor_unit']); // Papouch TME hack, was added in r2337 by @adama if (sensor_value_retry($device, $sensor_value, $sensor_db)) { $cached = FALSE; } if ($cached) { print_debug("value taken from oid_cache: $sensor_oid = $sensor_value"); } } elseif ($sensor_db['poller_type'] === "agent") { if (!safe_empty($agent_sensors)) { $sensor_value = snmp_fix_numeric($agent_sensors[$class][$sensor_db['sensor_type']][$sensor_db['sensor_index']]['current']); } else { print_warning("No agent sensor data available."); return FALSE; } } elseif ($sensor_db['poller_type'] === "ipmi") { if (!safe_empty($ipmi_sensors)) { $sensor_value = snmp_fix_numeric($ipmi_sensors[$class][$sensor_db['sensor_type']][$sensor_db['sensor_index']]['current']); $sensor_db['sensor_unit'] = $ipmi_sensors[$class][$sensor_db['sensor_type']][$sensor_db['sensor_index']]['unit']; } else { print_warning("No IPMI sensor data available."); return FALSE; } } else { print_error("Unknown sensor poller type."); return FALSE; } if ($sensor_value == -32768) { // FIXME. Not know why? Was added in r2493 by @adama print_debug("Reset invalid {$sensor_db['sensor_class']} sensor value '-32768' to 0."); return 0; } if ($sensor_db['sensor_unit'] === 'W' && $sensor_value < 0) { // See: https://jira.observium.org/browse/OBS-3200 // -9999 is exclude for Extreme devices, which report incorrect RX power when no power received print_debug("Reset invalid {$sensor_db['sensor_class']} sensor value '$sensor_value' to 0."); return 0; } return $sensor_value; } function sensor_value_retry($device, &$sensor_value, $sensor_db) { // Ie $config['os'][$os]['sensors_temperature_invalid'] = 9999; $os_sensor_def = 'sensors_' . $sensor_db['sensor_class'] . '_invalid'; if (!isset($GLOBALS['config']['os'][$device['os']][$os_sensor_def])) { return FALSE; } $value_invalid = (string)$GLOBALS['config']['os'][$device['os']][$os_sensor_def]; // Papouch TME hack, was added in r2337 by @adama // Papouch TME sometimes sends 999.9 when it is right in the middle of an update // Unified for Fortinet // Try 5 times to get a valid temp reading $i = 0; while ((string)$sensor_value === $value_invalid || !is_numeric($sensor_value)) { sleep(1); // Give the TME some time to reset $i++; print_debug("Retry ($i) get {$sensor_db['sensor_class']} sensor value.."); $sensor_value = snmp_fix_numeric(snmp_get_oid($device, $sensor_db['sensor_oid'], 'SNMPv2-MIB'), $sensor_db['sensor_unit']); if ($i === 4) { break; } } // If we received 999.9 degrees still, reset to 0. if ($sensor_value === $value_invalid) { print_debug("Reset invalid {$sensor_db['sensor_class']} sensor value '$value_invalid' to 0."); $sensor_value = 0; } return (bool)$i; } function sensor_value_scale($device, $sensor_value, &$sensor_db) { // Sensor attribs, by first must be cached $attribs = $GLOBALS['cache']['entity_attribs']['sensor'][$sensor_db['sensor_id']] ?? []; // Addition & Scale if (is_numeric($sensor_value)) { if (isset($attribs['sensor_addition']) && is_numeric($attribs['sensor_addition'])) { $sensor_value += $attribs['sensor_addition']; } // See BLUECOAT-SG-SENSOR-MIB $mib = $sensor_db['sensor_mib']; if (isset($attribs['oid_scale_si']) && !safe_empty($attribs['oid_scale_si'])) { if (str_contains($attribs['oid_scale_si'], '.')) { // single oid if ($scale_si = snmp_cache_oid($device, $attribs['oid_scale_si'], $mib)) { $sensor_db['sensor_multiplier'] = si_to_scale($scale_si); print_debug("Sensor set scale by get oid " . $mib . '::' . $attribs['oid_scale_si'] . ": " . $sensor_db['sensor_multiplier'] . " ($scale_si)."); } } elseif (isset($config['mibs'][$mib]['sensors_walk']) && !$config['mibs'][$mib]['sensors_walk']) { // single oid by index if ($scale_si = snmp_cache_oid($device, $attribs['oid_scale_si'] . '.' . $sensor_db['sensor_index'], $mib)) { $sensor_db['sensor_multiplier'] = si_to_scale($scale_si); print_debug("Sensor set scale by get oid " . $mib . '::' . $attribs['oid_scale_si'] . '.' . $sensor_db['sensor_index'] . ": " . $sensor_db['sensor_multiplier'] . " ($scale_si)."); } } else { // table walk $scale_table = snmp_cache_table($device, $attribs['oid_scale_si'], [], $mib); if (isset($scale_table[$sensor_db['sensor_index']][$attribs['oid_scale_si']])) { $scale_si = $scale_table[$sensor_db['sensor_index']][$attribs['oid_scale_si']]; $sensor_db['sensor_multiplier'] = si_to_scale($scale_si); print_debug("Sensor set scale by walk table " . $mib . '::' . $attribs['oid_scale_si'] . ": " . $sensor_db['sensor_multiplier'] . " ($scale_si)."); } } } } if (isset($sensor_db['sensor_multiplier']) && $sensor_db['sensor_multiplier'] != 0) { $sensor_value = scale_value($sensor_value, $sensor_db['sensor_multiplier']); } // Unit conversion to SI (if required) return value_to_si($sensor_value, $sensor_db['sensor_unit'], $sensor_db['sensor_class']); } /** * Custom value unit conversion functions for some vendors, * who do not know how use snmp float conversions, * do not know physics, mathematics and in general badly studied at school */ function value_unit_accuenergy($value) { return hex2float(dechex($value)); } // See: https://jira.observium.org/browse/OBS-2941 // Oids "pm10010mpMesrlineNetRxInputPwrPortn" and "pm10010mpMesrlineNetTxLaserOutputPwrPortn" in EKINOPS-Pm10010mp-MIB // If AV<32768: Tx_Pwr(dBm) = AV/100 // If AV>=32768: Tx_Pwr(dBm) = (AV-65536)/100 function value_unit_ekinops_dbm1($value) { if ($value >= 32768 && $value <= 65536) { return ($value - 65536) / 100; } if ($value > 65536 || $value < 0) { return FALSE; } return $value / 100; } // See: https://jira.observium.org/browse/OBS-2941 // oids "pm10010mpMesrclientNetTxPwrPortn" and "pm10010mpMesrclientNetRxPwrPortn" in EKINOPS-Pm10010mp-MIB // Power = 10*log(AV)-40) (Unit = dBm) function value_unit_ekinops_dbm2($value) { //return 10 * log10($value) + 30; // this is how watts converted to dbm return 10 * log10($value) - 40; // BUT this how convert it EKINOPS.... WHY?????? } // EOF