monitor.snmp

otto SNMP manager — general SNMP v2c polling for performance monitoring.

This is the reusable core that lets otto monitor any SNMP speaker — network gear, a Linux box running net-snmp, or a Zephyr device running otto’s test-bed agent — over a separate channel from command execution. It is not embedded-only: a Unix host may also be monitored via SNMP.

Two layers, mirroring the shell-monitoring split in otto.monitor.parsers (where MetricParser owns presentation and lab data owns “what command to run”):

  • AcquisitionSnmpClient: a thin async pysnmp v2c GET wrapper. Lab data (the host’s snmp block) supplies only connection params and the bare list of OIDs to poll. No presentation fields ever live in lab data.

  • PresentationSnmpMetric + the descriptor registry: maps each OID to how it is charted (label, chart group, unit, tab) and how its raw varbind is interpreted (scale). This is the SNMP analog of MetricParser; graphing decisions live here. Built-in descriptors cover a standard OID set; private/device OIDs register a descriptor from an init module via register_snmp_metric(). An OID with no registered descriptor falls back to default styling (resolve_snmp_metric()) so a host can add a bare OID with zero code and still get a chart.

The pysnmp dependency is imported lazily inside SnmpClient.get() so this module imports cleanly without it, and unit tests can mock at the get boundary rather than against pysnmp internals.

class otto.monitor.snmp.SnmpMetric(oid, label, chart, y_title='', unit='', tab='metrics', tab_label='Metrics', scale=1.0)

Bases: object

How a single OID’s value is interpreted and charted.

Mirrors the presentation attributes MetricParser already exposes (chart/y_title/unit/tab/tab_label) plus a scale factor that converts the raw integer varbind into a real value (e.g. sysUpTime is in hundredths of a second → scale=0.01 for seconds; a CPU OID reported in centi-percent → scale=0.01 for percent).

These are deliberately not sourced from lab data — graphing stays in the monitor module.

oid : --is-rst--:py:class:`str`
label : --is-rst--:py:class:`str`
chart : --is-rst--:py:class:`str`
y_title : --is-rst--:py:class:`str`
unit : --is-rst--:py:class:`str`
tab : --is-rst--:py:class:`str`
tab_label : --is-rst--:py:class:`str`
scale : --is-rst--:py:class:`float`
to_point(raw)

Apply scale to a raw numeric varbind, returning a chartable point.

Return type:

MetricDataPoint

otto.monitor.snmp.register_snmp_metric(metric)

Register (or override) the descriptor for metric.oid.

Call from an init module listed in .otto/settings.toml to teach otto how to chart a private/device-specific OID — the same extension pattern as otto.monitor.parsers.register_host_parsers() and otto.host.command_frame.register_command_frame().

Return type:

None

otto.monitor.snmp.get_snmp_metric(oid)

Return the registered descriptor for oid, or None.

Return type:

SnmpMetric | None

otto.monitor.snmp.resolve_snmp_metric(oid)

Return the descriptor for oid, or a default-styled fallback.

The fallback charts the OID under its own label with no unit on the generic metrics tab, so a host can poll a bare OID it declared in lab data without anyone having registered a descriptor for it.

Return type:

SnmpMetric

otto.monitor.snmp.points_from_values(values)

Map {oid: raw_value} to (label, point, descriptor) triples.

Each raw varbind is scaled by its descriptor and labelled for charting. OIDs with a None value (no such instance / error) are skipped.

Return type:

list[tuple[str, MetricDataPoint, SnmpMetric]]

class otto.monitor.snmp.SnmpClient(address, port=161, community='public', version='2c', timeout=2.0, retries=1)

Bases: object

Async SNMP v1/v2c GET client for a single endpoint.

The endpoint (address, port) is whatever is reachable from the otto host — for a device behind a hop, that is the local end of a UDP forward / relay, not the device’s own address. Keeping the endpoint explicit means this client knows nothing about hop topology.

address : --is-rst--:py:class:`str`
port : --is-rst--:py:class:`int`
community : --is-rst--:py:class:`str`
version : --is-rst--:py:data:`~typing.Literal`\ \[``'1'``, ``'2c'``]
timeout : --is-rst--:py:class:`float`
retries : --is-rst--:py:class:`int`
async get(oids)

GET oids in one PDU; return {oid: numeric_value_or_None}.

Non-numeric or errored varbinds map to None so the caller can skip them. On a transport/PDU error the whole batch returns None values and the error is logged — a failed tick is non-fatal, matching the shell collector’s per-host error handling.

Return type:

dict[str, float | None]

class otto.monitor.snmp.SnmpSource(client, oids)

Bases: object

A MonitorTarget’s SNMP collection mode.

Pairs the SnmpClient (where/how to reach the agent) with the bare list of OIDs to poll each tick. Presentation for those OIDs comes from the descriptor registry, not from here — this is acquisition only.

client : --is-rst--:py:class:`~otto.monitor.snmp.SnmpClient`
oids : --is-rst--:py:class:`list`\ \[:py:class:`str`]