Source code for duct.sources.power.raritan_px

"""
.. module:: raritan_px
   :platform: Unix
   :synopsis: A source module for Raritan PX PDU monitoring via SNMP/PDU2-MIB

.. moduleauthor:: Colin Alston <colin@tamvera.com>
"""
import logging

from zope.interface import implementer

from duct.interfaces import IDuctSource
from duct.sources.snmp import SNMP

log = logging.getLogger(__name__)

_SENSOR_MAP = {
    'rmsVoltage':    (int,   'voltage'),
    'rmsCurrent':    (float, 'current'),
    'activePower':   (int,   'power'),
    'apparentPower': (int,   'apparent_power'),
    'powerFactor':   (float, 'pf'),
    'activeEnergy':  (int,   'energy'),
}

_TABLES = [
    ('measurementsOutletSensorValue', 'outlet'),
    ('measurementsInletSensorValue',  'inlet'),
]


[docs] @implementer(IDuctSource) class RaritanPX(SNMP): """Monitors a Raritan PX PDU via SNMP using the PDU2-MIB. Requires compiled PDU2-MIB Python modules in ``mib_path``. :param ip: PDU hostname or IP (default: 127.0.0.1) :type ip: str. :param port: SNMP port (default: 161) :type port: int. :param community: SNMP read community (default: public) :type community: str. :param mib_path: Directory containing compiled MIB modules (default: /var/lib/duct/mibs) :type mib_path: str. **Metrics emitted** (prefixed by service name): :outlet.N.voltage: Outlet N RMS voltage (V) :outlet.N.current: Outlet N RMS current (A) :outlet.N.power: Outlet N active power (W) :outlet.N.apparent_power: Outlet N apparent power (VA) :outlet.N.pf: Outlet N power factor :outlet.N.energy: Outlet N active energy (Wh) :inlet.N.*: Same metrics for inlet N """ def __init__(self, *a, **kw): SNMP.__init__(self, *a, **kw) self.mib_path = self.config.get('mib_path', '/var/lib/duct/mibs') def _parse_sensor(self, prefix, oid, val): """Parse one sensor varBind into an event, or return None if unrecognised.""" oid_str = oid.prettyPrint() if '::' in oid_str: # Symbolic form: "PDU2-MIB::measurementsOutletSensorValue.1.rmsVoltage" _, name_and_instance = oid_str.split('::', 1) parts = name_and_instance.split('.') # parts[0] = object name, parts[1:] = index components # last = sensor type, second-to-last = outlet/inlet index if len(parts) < 3: return None index = parts[-2] sensor_key = parts[-1] else: # Numeric OID fallback - last two components are index and sensor type parts = oid_str.split('.') if len(parts) < 2: return None index = parts[-2] sensor_key = parts[-1] if sensor_key not in _SENSOR_MAP: return None dtype, metric_name = _SENSOR_MAP[sensor_key] try: value = dtype(val) except (ValueError, TypeError): return None return self.createEvent( 'ok', f'Raritan PX {prefix}-{index} {metric_name}={value}', value, prefix=f'{prefix}.{index}.{metric_name}', )
[docs] async def get(self): events = [] mib_dirs = [self.mib_path] for table, label in _TABLES: try: results = await self.snmp.walkMib('PDU2-MIB', table, mib_dirs=mib_dirs) except Exception as exc: log.warning('Raritan PX SNMP error querying %s: %s', table, exc) continue for oid, val in results: event = self._parse_sensor(label, oid, val) if event: events.append(event) return events