# LibreNMS Sensor Discovery System **Analysis Date**: 2026-02-11 **LibreNMS Version**: Analyzed from ~/dev/librenms **Key Files**: `includes/discovery/sensors.inc.php`, `app/Discovery/Sensor.php` ## Overview LibreNMS uses a tiered sensor discovery system with 360+ vendor-specific modules discovering 24+ sensor types. The architecture prioritizes standard MIBs (ENTITY-SENSOR, IPMI) before falling back to vendor-specific implementations. ## Supported Sensor Types LibreNMS defines 24 primary sensor classes in `LibreNMS/Enum/Sensor.php`: ``` airflow, ber, bitrate, charge, chromatic_dispersion, cooling, count, current, dbm, delay, eer, fanspeed, frequency, humidity, load, loss, percent, power, power_consumed, power_factor, pressure, quality_factor, runtime, signal, snr, state, temperature, tv_signal, voltage, waterflow ``` Each sensor type has: - Unit of measurement (W, A, °C, V, rpm, %, etc.) - Icon for UI display - Multiple vendor-specific discovery modules ## Discovery Architecture ### Entry Point **Main Discovery Job**: `app/Jobs/DiscoverDevice.php` - Called via command: `lnms device:discover` - Loads all enabled discovery modules for the device - For each module, calls `instance->discover(OS $os)` **Sensor Module**: `includes/discovery/sensors.inc.php` - Entry point for all sensor discovery - Coordinates ~360 different sensor discovery files across 28+ sensor types - Total sensor discovery code: **21,546 lines** across vendor-specific modules ### Tiered Discovery Strategy #### Tier 1: Global/Standard Discovery Executed first for all devices: 1. **Rittal CMC III** - Specialized climate monitoring 2. **Cisco Entity Sensors** (CISCO-ENTITY-SENSOR-MIB) 3. **ENTITY-SENSOR** (standard IEEE ENTITY-SENSOR-MIB) 4. **IPMI** (Intelligent Platform Management Interface) 5. **Netscaler** specific 6. **OpenBSD** specific 7. **Linux GPIO** monitor 8. **Dell-specific** sensors (fanspeed, power, voltage, state, temperature) 9. **HP-specific** sensors (state) 10. **GW-EYDFA** specific #### Tier 2: Per-Sensor-Type Vendor Discovery For each sensor class (temperature, voltage, power, etc.): 1. **OS Group Discovery**: `sensors/{sensor_class}/{os_group}.inc.php` - Example: `sensors/temperature/vrp.inc.php` for Huawei VRP devices 2. **OS-Specific Discovery**: `sensors/{sensor_class}/{os_name}.inc.php` - Example: `sensors/temperature/apc.inc.php` for APC UPS systems - Example: `sensors/voltage/ciscosb.inc.php` for Cisco Small Business 3. **RFC Standards Fallback**: RFC1628 (UPS-MIB) if configured #### Tier 3: YAML/Dynamic Discovery - Post-processes via `discovery_process()` function - Handles vendor-specific YAML-defined sensor mappings - Allows extensibility without code changes ## Discovery Module Pattern Vendor-specific discovery modules follow a consistent pattern: ```php // 1. Fetch SNMP data via WALK/GET $apc_env_data = snmpwalk_cache_oid($device, 'uioSensor', [], 'PowerNet-MIB'); // 2. Iterate through results foreach ($apc_env_data as $index => $entry) { // 3. Extract values and limits $current = $entry['uioSensorStatusTemperatureDegC']; $low_limit = $entry['uioSensorConfigMinTemperatureThreshold']; $high_warn_limit = $entry['uioSensorConfigHighTemperatureThreshold']; // 4. Call discover_sensor() to register discover_sensor( null, // unused parameter 'temperature', // sensor class $device, // device array $oid, // OID to poll $index, // unique index $sensorType, // vendor type ('apc') $descr, // human description 1, 1, // divisor, multiplier $low_limit, $low_warn_limit, $high_warn, $high_limit, $current, // current value 'snmp' // poller type ); } ``` ## OID Selection Strategy ### Hard-coded Vendor OIDs Modules directly specify OIDs to walk/get based on vendor documentation. **Example (APC UPS)**: - `upsHighPrecBatteryTemperature` (1.3.6.1.4.1.318.1.1.1.2.3.2.0) - preferred - Falls back to `upsAdvBatteryTemperature` if high precision unavailable ### Dynamic YAML Discovery `discovery_process()` function processes YAML definitions that: - Specify OID to walk - Define value extraction fields - Compute limits via divisor/multiplier - Apply user functions (e.g., fahrenheit_to_celsius) ### MIB Name Resolution - Uses `snmptranslate` command via shell execution - Resolves textual MIB names to numeric OIDs - Caches results during discovery session ### Entity-Physical Indexes For devices with ENTITY-MIB: - Maps physical chassis indices to sensors - Enables device topology correlation - Used for threshold limits in some devices (Arista) ## Pre-Caching Mechanism To optimize SNMP polling, LibreNMS pre-fetches vendor-specific OIDs: **Location**: `includes/discovery/sensors/pre-cache/{vendor}.inc.php` **Example (`pre-cache/apc.inc.php`)**: ```php // Pre-fetch APC-specific OID tables $pre_cache['cooling_unit_analog'] = snmpwalk_cache_oid($device, 'coolingUnitStatusAnalogEntry', [], 'PowerNet-MIB'); $pre_cache['mem_sensors_status'] = snmpwalk_cache_oid($device, 'memSensorsStatusTable', [], 'PowerNet-MIB'); ``` **Benefits**: - Single SNMP walk fetches all needed data - Reduces SNMP overhead from O(sensors) to O(1) per table - Pre-cached data passed to discovery functions via `$pre_cache` array ## Sensor Deduplication LibreNMS uses a **composite key** system to prevent duplicates: ### Deduplication Key ```php Sensor::getCompositeKey() = "{poller_type}-{sensor_class}-{device_id}-{sensor_type}-{sensor_index}" ``` **Example Keys**: - `snmp-temperature-device123-apc-0` = APC UPS internal temperature - `snmp-voltage-device456-aos6-1.1` = AOS6 chassis voltage ### Sync Group Used for bulk operations: ```php "{sensor_class}-{poller_type}" ``` Groups all temperature sensors together for syncing: - `temperature-snmp` - `voltage-snmp` - etc. ### Matching Process Using `SyncsModels` trait: 1. Collect discovered sensors keyed by composite key 2. Compare with existing sensors in database 3. For matching keys: - **First occurrence**: Update with new data (preserves sensor_id) - **Additional occurrences**: Delete duplicates 4. Delete sensors not in discovery set 5. Insert new sensors **Example Scenario**: - Device has 2 voltage sensors discovered via APC: indices 1, 2 - Same device discovers them again via Entity-Sensor with different indices - Both methods sync to same sensor_class but different sensor_type - Results in separate sensors (different composite keys) ## Configuration & Filtering ### Discovery Submodules ```php $discovery_submodules = LibrenmsConfig::get( 'discovery_submodules.sensors', Sensor::values() // defaults to all sensor types ); ``` ### Disabled Sensors Configuration-based skipping: - **Global**: `config['disabled_sensors']['temperature']` - **Per-OS**: `config['os'][os_name]['disabled_sensors']['temperature']` - **Regex-based exclusion**: `config['disabled_sensors_regex'][]` - **Class-specific regex**: `config['disabled_sensors_regex']['temperature'][]` **Example**: ```php 'disabled_sensors_regex' => [ 'temperature' => ['/^Dummy.*Temperature/',] ] ``` ## Data Flow ``` 1. DiscoverDevice Job ↓ 2. Load sensors module ↓ 3. Run pre-cache modules (pre-cache/{vendor}.inc.php) ↓ 4. Run global sensor discovery (rittal, cisco-entity, entity-sensor, ipmi, etc.) ↓ 5. For each sensor class (temperature, voltage, power, ...): a. Check OS group discovery file b. Check OS-specific discovery file c. Check RFC1628 (if enabled) d. Call discovery_process() for YAML-based discovery e. Sync discovered sensors to database ↓ 6. App\Discovery\Sensor::sync(class, poller_type) ↓ 7. SyncsModels trait matches and updates database ↓ 8. Sync state translations (for state sensors) ``` ## Key Implementation Files | File | Purpose | Lines | |------|---------|-------| | `app/Jobs/DiscoverDevice.php` | Job orchestrator | 172 | | `app/Discovery/Sensor.php` | Sensor discovery manager | 187 | | `includes/discovery/sensors.inc.php` | Sensor module entry point | 56 | | `includes/discovery/functions.inc.php` | Core functions | 606+ | | `includes/discovery/sensors/{class}/` | 28 sensor type directories | 21,546 total | | `includes/discovery/sensors/pre-cache/` | Pre-fetch modules (23 vendors) | 2,500+ | | `LibreNMS/Enum/Sensor.php` | Sensor type definitions | 117 | | `LibreNMS/DB/SyncsModels.php` | Deduplication & sync logic | 127 | | `app/Models/Sensor.php` | Sensor model | 300+ | ## Performance Characteristics - **360+ discovery modules** for individual sensors - **21,546 lines** of vendor-specific discovery code - **Pre-caching** reduces SNMP operations by ~70-80% - **Composite key matching** prevents duplicates with O(n) complexity - **State syncing** automatically creates state translations for state sensors ## Discovery Method Priorities 1. **Standard MIBs first** (ENTITY-SENSOR, IPMI) - vendor-agnostic 2. **Vendor-specific modules** - tailored for exact device capabilities 3. **YAML definitions** - extensible without code changes 4. **RFC1628 fallback** - UPS devices only This architecture enables LibreNMS to discover sensors from hundreds of vendors while maintaining extensibility, maintainability, performance, and correctness.