Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collect Juniper dom threshold values #2513

Draft
wants to merge 24 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
22aa26d
Add metric path for thresholds
stveit Jun 30, 2022
2324494
Add function for getting thresholds in juniper mib
stveit Jun 30, 2022
e2e869b
Collect threshold values
stveit Jun 30, 2022
ebc385d
Give thresholds generic nameing
stveit Jun 30, 2022
2d6f307
Add threshold properties
stveit Feb 8, 2023
b4f2cb8
Add related sensor for threshold sensors
stveit Feb 10, 2023
867376e
Update SQL tables
stveit Feb 10, 2023
ed62f06
Revert "Collect threshold values"
stveit Feb 11, 2023
fd078b6
Change field to refer to oid of other sensor
stveit Feb 11, 2023
9c8a55f
Add property for accessing related sensor
stveit Feb 11, 2023
5eba3e0
Collect threshold values as sensors
stveit Feb 11, 2023
d21aeb9
Access items correctly
stveit Feb 14, 2023
11a5613
Do not access items
stveit Feb 14, 2023
ebcec4f
fixup! Change field to refer to oid of other sensor
stveit Feb 14, 2023
7a93cea
fixup! Add property for accessing related sensor
stveit Feb 14, 2023
f6088cd
Revert "Add metric path for thresholds"
stveit Feb 14, 2023
f768bdf
Add property for getting relevant thresholds
stveit Feb 14, 2023
2a2ce7e
Add test file for juniper dom mib
stveit Feb 15, 2023
1ec98ce
Add tests for creating sensor dicts
stveit Feb 16, 2023
a58dd65
Allow field to be null
stveit Feb 17, 2023
0d5f2b6
Add tests for sensor model changes
stveit Feb 17, 2023
4fd5cf5
fixup! Add tests for sensor model changes
stveit Feb 17, 2023
ab3d427
Make test work with existing stuff in database
stveit Feb 21, 2023
a9a6080
Reference sensor for oid
stveit Feb 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion python/nav/ipdevpoll/plugins/statsensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@
from nav.ipdevpoll import db
from nav.ipdevpoll.db import run_in_thread
from nav.metrics.carbon import send_metrics
from nav.metrics.templates import metric_path_for_sensor
from nav.metrics.templates import metric_path_for_sensor, metric_path_for_threshold
from nav.models.manage import Sensor
from nav.mibs.juniper_dom_mib import JuniperDomMib
from nav.enterprise.ids import VENDOR_ID_JUNIPER_NETWORKS_INC

# Ask for no more than this number of values in a single SNMP GET operation
MAX_SENSORS_PER_REQUEST = 5

THRESHOLD_MIBS = {VENDOR_ID_JUNIPER_NETWORKS_INC: [JuniperDomMib]}


class StatSensors(Plugin):
"""Collects measurement values from registered sensors and pushes to
Expand Down Expand Up @@ -59,6 +63,11 @@ def handle(self):
defer.returnValue(None)
netboxes = yield db.run_in_thread(self._get_netbox_list)
sensors = yield run_in_thread(self._get_sensors)
yield self._handle_sensor_stats(sensors, netboxes)
yield self._handle_thresholds(sensors, netboxes)

@defer.inlineCallbacks
def _handle_sensor_stats(self, sensors, netboxes):
self._logger.debug("retrieving data from %d sensors", len(sensors))
oids = list(sensors.keys())
requests = [
Expand All @@ -73,6 +82,8 @@ def handle(self):

def _get_sensors(self):
sensors = Sensor.objects.filter(netbox=self.netbox.id).values()
for sensor in sensors:
self._logger.info(f"Internal names: {sensor.get('internal_name', None)}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. This looks more like debug-level stuff, not info-level (not very interesting to a "common" user)
  2. Best practice for Python loggers is to use regular string formatting and expansion arguments. This ensure that the formatted string is only built if the log statement matches the currently configured log level (if debug arguments are expensive to format to strings, it doesn't happen unless DEBUG level logging is actually enabled)
  3. No need to specify a fallback argument of None when using .get() on a dict - None is already the default fallback value.
Suggested change
for sensor in sensors:
self._logger.info(f"Internal names: {sensor.get('internal_name', None)}")
for sensor in sensors:
self._logger.debug("Internal names: %s", sensor.get('internal_name'))

return dict((row['oid'], row) for row in sensors)

def _response_to_metrics(self, result, sensors, netboxes):
Expand All @@ -98,6 +109,52 @@ def _response_to_metrics(self, result, sensors, netboxes):
send_metrics(metrics)
return metrics

@defer.inlineCallbacks
def _handle_thresholds(self, sensors, netboxes):
metrics = yield self._collect_thresholds(netboxes, sensors)
self._logger.info(f"Metrics: {metrics}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about debug logging and argument expansion applies here.

if metrics:
send_metrics(metrics)

@defer.inlineCallbacks
def _collect_thresholds(self, netboxes, sensors):
for mib in self._mibs_for_me(THRESHOLD_MIBS):
try:
metrics = yield self._collect_thresholds_from_mib(
mib, netboxes, sensors
)
except (TimeoutError, defer.TimeoutError):
self._logger.debug(
"collect_thresholds: ignoring timeout in %s", mib.mib['moduleName']
)
else:
if metrics:
defer.returnValue(metrics)
defer.returnValue([])

@defer.inlineCallbacks
def _collect_thresholds_from_mib(self, mib, netboxes, sensors):
metrics = []
timestamp = time.time()
thresholds = yield mib.get_all_thresholds()
self._logger.debug(f"Threshold: {thresholds}")
for threshold in thresholds:
sensor = sensors[threshold['sensor_oid']]
value = self.convert_to_precision(threshold['value'], sensor)
for netbox in netboxes:
path = metric_path_for_threshold(
netbox, sensor['internal_name'], threshold['name']
)
# thresholds will have the same precision etc. as its related sensor
metrics.append((path, (timestamp, value)))
return metrics

def _mibs_for_me(self, mib_class_dict):
vendor = self.netbox.type.get_enterprise_id() if self.netbox.type else None
mib_classes = mib_class_dict.get(vendor, None) or mib_class_dict.get(None, [])
for mib_class in mib_classes:
yield mib_class(self.agent)


def convert_to_precision(value, sensor):
"""Moves the decimal point of a value according to the precision defined
Expand Down
14 changes: 14 additions & 0 deletions python/nav/metrics/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,20 @@ def metric_path_for_sensor(sysname, sensor):
)


def metric_prefix_for_thresholds(sysname):
tmpl = "{prefix}.thresholds"
return tmpl.format(prefix=metric_prefix_for_sensors(sysname))


def metric_path_for_threshold(sysname, sensor, threshold):
tmpl = "{prefix}.{sensor}.{threshold}"
return tmpl.format(
prefix=metric_prefix_for_thresholds(sysname),
sensor=escape_metric_name(sensor),
threshold=escape_metric_name(threshold),
)


def metric_path_for_service_availability(sysname, handler, service_id):
tmpl = "{service}.availability"
return tmpl.format(service=metric_prefix_for_service(sysname, handler, service_id))
Expand Down
63 changes: 59 additions & 4 deletions python/nav/mibs/juniper_dom_mib.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from nav.mibs.mibretriever import MibRetriever
from nav.models.manage import Sensor

COLUMNS = {
SENSOR_COLUMNS = {
"jnxDomCurrentRxLaserPower": {
"unit_of_measurement": Sensor.UNIT_DBM,
"precision": 2,
Expand Down Expand Up @@ -51,6 +51,33 @@
},
}

THRESHOLD_COLUMNS = {
"jnxDomCurrentRxLaserPower": {
"high_alarm_threshold": "jnxDomCurrentRxLaserPowerHighAlarmThreshold",
"low_alarm_threshold": "jnxDomCurrentRxLaserPowerLowAlarmThreshold",
"high_warning_threshold": "jnxDomCurrentRxLaserPowerHighWarningThreshold",
"low_warning_threshold": "jnxDomCurrentRxLaserPowerLowWarningThreshold",
},
"jnxDomCurrentTxLaserBiasCurrent": {
"high_alarm_threshold": "jnxDomCurrentTxLaserBiasCurrentHighAlarmThreshold",
"low_alarm_threshold": "jnxDomCurrentTxLaserBiasCurrentLowAlarmThreshold",
"high_warning_threshold": "jnxDomCurrentTxLaserBiasCurrentHighWarningThreshold",
"low_warning_threshold": "jnxDomCurrentTxLaserBiasCurrentLowWarningThreshold",
},
"jnxDomCurrentTxLaserOutputPower": {
"high_alarm_threshold": "jnxDomCurrentTxLaserOutputPowerHighAlarmThreshold",
"low_alarm_threshold": "jnxDomCurrentTxLaserOutputPowerLowAlarmThreshold",
"high_warning_threshold": "jnxDomCurrentTxLaserOutputPowerHighWarningThreshold",
"low_warning_threshold": "jnxDomCurrentTxLaserOutputPowerLowWarningThreshold",
},
"jnxDomCurrentModuleTemperature": {
"high_alarm_threshold": "jnxDomCurrentModuleTemperatureHighAlarmThreshold",
"low_alarm_threshold": "jnxDomCurrentModuleTemperatureLowAlarmThreshold",
"high_warning_threshold": "jnxDomCurrentModuleTemperatureHighWarningThreshold",
"low_warning_threshold": "jnxDomCurrentModuleTemperatureLowWarningThreshold",
},
}


class JuniperDomMib(MibRetriever):
"""MibRetriever for Juniper DOM Sensors"""
Expand All @@ -63,12 +90,12 @@ def get_all_sensors(self):
device.
"""
sensors = []
for column, config in COLUMNS.items():
sensors += yield self.handle_column(column, config)
for column, config in SENSOR_COLUMNS.items():
sensors += yield self.handle_sensor_column(column, config)
returnValue(sensors)

@defer.inlineCallbacks
def handle_column(self, column, config):
def handle_sensor_column(self, column, config):
"""Returns the sensors of the given type"""
result = []
value_oid = self.nodes[column].oid
Expand All @@ -84,3 +111,31 @@ def handle_column(self, column, config):
sensor.update(config)
result.append(sensor)
returnValue(result)

@defer.inlineCallbacks
def get_all_thresholds(self):
thresholds = []
for sensor_column, thresholds in THRESHOLD_COLUMNS.items():
sensor_oid = self.nodes[sensor_column].oid
for threshold_name, threshold_column in thresholds.items():
thresholds += yield self.handle_threshold_column(
threshold_column, threshold_name, sensor_oid
)
returnValue(thresholds)

@defer.inlineCallbacks
def handle_threshold_column(self, column, name, sensor_oid):
"""Returns the sensor thresholds of the given type"""
result = []
value_oid = self.nodes[column].oid
rows = yield self.retrieve_column(column)
for row, value in rows.items():
threshold = dict(
oid=str(value_oid + row),
mib=self.get_module_name(),
name=name,
value=value,
sensor_oid=str(sensor_oid + row),
)
result.append(threshold)
returnValue(result)