.agents/skills/project-snmp-trap-profiles-authoring/trap-metrics-profiles.md
profile-format.md.Netdata needs a trap-to-metrics system that lets operators convert selected SNMP trap signals into useful metrics without losing the forensic value of trap logs.
The system must support practical, profile-backed use cases across stock and operator-provided trap profiles. The first requirement is to identify the whole goal before designing the schema.
This document records:
This spec intentionally does not define:
Existing open-source NMS implementations mostly treat SNMP traps as events, alarms, or state mutations, not as a profile-driven trap-to-time-series extraction system. Netdata should reuse their operational lessons for resource identity, problem/clear pairing, and cardinality, but should not assume there is a mature open-source trap-metric profile schema to copy.
Evidence:
librenms/librenms @ 5ffbb16324ad7c25f9588801ca9d4d52da45ea21
LibreNMS/Snmptrap/Handlers/LinkDown.php:48 through :74 resolves
ifIndex, updates the matched port, and logs interface-scoped events.librenms/librenms @ 5ffbb16324ad7c25f9588801ca9d4d52da45ea21
LibreNMS/Snmptrap/Handlers/BgpEstablished.php:48 through :65 resolves a
BGP peer from the trap OID suffix and updates peer state.opennms/opennms @ 40cc8535351f09c24978771a8832cfc286b85572
docs/modules/operation/pages/deep-dive/alarms/configuring-alarms.adoc:56
through :142 documents reduction keys, problem/resolution alarm types, and
clear keys.Historical facts from the unreleased SNMP trap collector before Phase A replaced job-level operator metrics with profile-defined metrics:
oid, context, and optional
dimension_from_varbind.varbinds and traps, but no profile-local
metric section.Evidence:
src/go/plugin/go.d/collector/snmp_traps/config.go:72 through
:76.src/go/plugin/go.d/collector/snmp_traps/operator_metric.go:23
through :55 (file removed by Phase A).src/go/plugin/go.d/collector/snmp_traps/operator_metric.go:192 through
:219 (file removed by Phase A).src/go/plugin/go.d/collector/snmp_traps/operator_metric.go:380 through
:420 (file removed by Phase A).src/go/plugin/go.d/collector/snmp_traps/profile.go:156
through :164.Operator question:
Concrete examples:
IF-MIB::linkDown and IF-MIB::linkUp from
many switches.SNMPv2-MIB::authenticationFailure from many
devices.Why it matters:
Evidence:
src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/standard.yaml:15536
through :15555 defines IF-MIB::linkDown and IF-MIB::linkUp.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/standard.yaml:15557
defines SNMPv2-MIB::authenticationFailure.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/standard.yaml:10068
through :10086 includes current BGP established/backward-transition
notifications.Operator question:
Concrete examples:
IF-MIB::linkDown only when ifAdminStatus=up, so planned
administrative shutdowns are not counted as unexpected link failures.console, terminal, virtual, or another explicitly selected enum value.critical.Why it matters:
Evidence:
src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/standard.yaml:2209
through :2215 defines ifAdminStatus enum values.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/standard.yaml:15536
through :15545 shows linkDown carries ifAdminStatus.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/ciscosystems.yaml:4211
through :4220 defines Cisco terminal type enum values.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/ciscosystems.yaml:4221
through :4224 defines the terminal user as DisplayString; this is useful
log detail, not a safe default metric label.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/ciscosystems.yaml:33139
through :33148 shows ccmCLIRunningConfigChanged carries terminal type and
terminal user.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/arista-networks-inc-formerly-arastra-inc.yaml:106
through :113 defines aristaCvAlertSeverity.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/arista-networks-inc-formerly-arastra-inc.yaml:271
through :284 shows CloudVision firing notifications carry the severity
varbind.Operator question:
Concrete examples:
Why it matters:
Evidence:
src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/arista-networks-inc-formerly-arastra-inc.yaml:244
through :253 shows Arista hardware utilization values.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/juniper-networks-inc.yaml:4428
through :4460 shows Juniper DFC packet-rate and watermark values.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/juniper-networks-inc.yaml:4542
through :4565 shows Juniper IDP CPU/memory usage and threshold values.Operator question:
Concrete examples:
Why it matters:
Evidence:
src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/arista-networks-inc-formerly-arastra-inc.yaml:300
through :347 shows Arista CLB flow fields.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/juniper-networks-inc.yaml:4497
through :4523 shows Juniper DFC hard memory fields.src/go/plugin/go.d/collector/snmp_traps/operator_metric.go:27
through :41 shows the removed job-level metrics rejected duplicate OIDs.Operator question:
Concrete examples:
linkDown and linkUp per device interface using ifIndex or an
enriched interface identity.Why it matters:
Evidence:
src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/standard.yaml:2224
through :2227 defines ifIndex as a large interface index range.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/ciscosystems.yaml:37706
through :37735 shows Cisco port-security traps carry interface/VLAN/MAC
detail.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/juniper-networks-inc.yaml:4428
through :4460 shows Juniper DFC packet-rate traps carry interface names.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/arista-networks-inc-formerly-arastra-inc.yaml:300
through :347 shows Arista CLB port-group fields.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/profile-format.md:337
through :342 distinguishes high-cardinality log content from bounded metric
labels.Operator question:
Concrete examples:
IF-MIB::linkDown marks an interface down; matching IF-MIB::linkUp clears
it.Why it matters:
Evidence:
src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/standard.yaml:15536
through :15555 defines linkDown and linkUp.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/arista-networks-inc-formerly-arastra-inc.yaml:254
through :269 defines external alarm asserted/deasserted notifications.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/arista-networks-inc-formerly-arastra-inc.yaml:271
through :299 defines CloudVision firing/resolved notifications.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/juniper-networks-inc.yaml:4428
through :4523 includes exceeded/under-threshold notification pairs.Operator question:
Concrete examples:
aristaCvAlertSeverity with values info, warning, error, and
critical.critical CloudVision firing notifications to feed a
critical-alert metric, while retaining all CloudVision trap logs.Why it matters:
Evidence:
src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/arista-networks-inc-formerly-arastra-inc.yaml:106
through :113 defines aristaCvAlertSeverity.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/arista-networks-inc-formerly-arastra-inc.yaml:271
through :299 shows CloudVision firing/resolved traps carry severity.Operator question:
Concrete examples:
Why it matters:
Evidence:
src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/ciscosystems.yaml:33139
through :33148 shows Cisco config-change user and terminal varbinds.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/ciscosystems.yaml:37706
through :37735 shows Cisco port-security MAC/interface/VLAN detail.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/ciscosystems.yaml:23357
through :23360 defines the last secure MAC address as MacAddress; this is
useful log detail, not a safe default metric label.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/standard.yaml:15557
defines SNMPv2-MIB::authenticationFailure.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/profile-format.md:337
through :342 records the existing bounded-label rule.Operator question:
Concrete examples:
SNMPv2-MIB::coldStart and SNMPv2-MIB::warmStart count device SNMP entity
initialization events.Why it matters:
Evidence:
src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/standard.yaml:15525
through :15534 defines coldStart and warmStart.Operator question:
Concrete examples:
Why it matters:
Evidence:
src/go/plugin/go.d/config/go.d/snmp.trap-profiles/profile-format.md:36
through :46 documents stock and operator trap profile directories.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/profile-format.md:132
through :137 documents converting custom MIBs offline and dropping YAML
files under /etc/netdata/go.d/snmp.trap-profiles/.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/profile-format.md
documents complete same-identity replacement and independent operator files.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/milestone-systems-a-s.yaml:17
shows a shipped profile containing a user-defined event trap example.Operator question:
Concrete examples:
SNMP-ALARM-MIB::snmpAlarmStatusChange carries snmpAlarmLogCond with set
or clear values plus an alarm identifier.SNMP-ALARM-MIB::snmpItuAlarmStatusChange carries ITU alarm class, probable
cause, perceived severity, and additional text.RMON-MIB::risingAlarm and RMON-MIB::fallingAlarm carry the sampled value,
threshold, sample type, and alarmed variable.Why it matters:
Evidence:
src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/standard.yaml:5516
through :5525 defines snmpAlarmLogCond set/clear values and
snmpAlarmLogId.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/standard.yaml:5559
through :5588 defines ITU alarm class, severity, and probable-cause fields.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/standard.yaml:10067
through :10128 defines BGP and RMON rising/falling notifications.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/standard.yaml:15563
through :15590 defines SNMP-ALARM-MIB status-change notifications and their
varbinds.opennms/opennms @ 40cc8535351f09c24978771a8832cfc286b85572
docs/modules/operation/pages/deep-dive/alarms/configuring-alarms.adoc:94
through :134 documents problem/resolution alarms and clear keys.Operator question:
Concrete examples:
Why it matters:
Evidence:
src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/american-power-conversion-corp.yaml:409
through :449 defines UPS overload, on-battery, and low-battery traps.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/american-power-conversion-corp.yaml:481
through :611 defines low-battery clear, battery replacement, contact fault,
fan failure, and battery-pack communication events.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/american-power-conversion-corp.yaml:660
through :684 defines overload-cleared and battery-replaced traps.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/ciscosystems.yaml:32724
through :32781 defines Cisco voltage and temperature notifications with
value and state varbinds.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/ciscosystems.yaml:32745
through :32761 defines Cisco fan and redundant power-supply notifications.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/ciscosystems.yaml:33716
through :33736 defines Cisco CPU rising/falling threshold notifications.Operator question:
Concrete examples:
Why it matters:
Evidence:
src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/standard.yaml:10068
through :10086 defines current BGP established/backward-transition
notifications with bgpPeerRemoteAddr and bgpPeerState.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/nbase-switch-communication.yaml:2757
through :2769 defines OSPF neighbor state-change varbinds.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/ciscosystems.yaml:33708
through :33714 defines Cisco HSRP state-change varbinds.librenms/librenms @ 5ffbb16324ad7c25f9588801ca9d4d52da45ea21
LibreNMS/Snmptrap/Handlers/BgpBackwardTransition.php:48 through :66
resolves a BGP peer and updates peer state.Operator question:
Concrete examples:
Why it matters:
Evidence:
src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/ciscosystems.yaml:34109
through :34133 defines Cisco DHCP free-address low/high threshold traps and
carried scope/value/threshold varbinds.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/ciscosystems.yaml:45656
through :45691 defines Cisco CGN port-usage low/high watermark and clear
traps.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/ciscosystems.yaml:33716
through :33736 defines Cisco CPU threshold value and current interval value
varbinds.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/standard.yaml:10105
through :10128 defines RMON rising/falling alarm value and threshold
varbinds.Operator question:
Concrete examples:
Why it matters:
Evidence:
src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/shanghai-meridian-technologies-co-ltd.yaml:1016
through :1025 defines an LLDP remote-tables-change trap with insert, delete,
drop, and age-out counters.src/go/plugin/go.d/config/go.d/snmp.trap-profiles/default/dell-inc.yaml:21623
through :21632 defines a Dell STP new-root-election trap.Scope disposition:
Operator question:
Concrete examples:
snmpTrapAddress.0; misconfigured trusted
relay settings can affect source-device identity.Why it matters:
Evidence:
src/go/plugin/go.d/collector/snmp_traps/internal/telemetry/job.go collects
receiver pipeline, events, severities, errors, and dedup metrics with
job_name through an explicit per-job handle.TRAP_SOURCE_IP, TRAP_SOURCE_UDP_PEER, and TRAP_ENRICHMENT preserve the
source evidence used for log filtering and audit.src/go/plugin/go.d/collector/snmp_traps/config.go:26 through :28 defines
trusted relay configuration.Collector.warnCatchAllTrustedRelays composition warning explains
that catch-all trusted relays let every peer override source identity via
snmpTrapAddress.0.internal/jobruntime.Job handles receiver events, including INFORM response
failures, and records inform_response_failed through the retained telemetry
handle.This use-case inventory does not require the future design to support:
TRAP_SEVERITY or PRIORITY from varbind values.
Severity-aware metrics must not silently change the trap log severity
contract.External reviewers should answer only use-case coverage questions:
Reviewers should not propose schema syntax in Phase 1 except when needed to explain a missing use case.
Use the existing SNMP trap profile system as the source of truth for trap metric rules. A trap profile should be able to define:
The job configuration should define runtime policy only:
This is intentionally closer to SNMP polling profiles than Prometheus profiles. Prometheus profiles organize an existing metric stream. Trap profiles must also define extraction, because a trap is an event plus varbinds, not an already emitted metric family.
Common operator path:
source.trusted_relays only when traps pass through relays that
must be allowed to set snmpTrapAddress.0./etc/netdata/go.d/snmp.trap-profiles/ only when stock profiles do not
provide the desired metric behavior.Illustrative job configuration:
jobs:
- name: campus-traps
listen:
endpoints:
- protocol: udp
address: 0.0.0.0
port: 162
versions: [v2c, v3]
source:
trusted_relays:
- 192.0.2.0/24
profile_metrics:
enabled: true
include:
- IF-MIB::unexpected-link-down-events
- CISCO-CONFIG-MAN-MIB::cli-config-change-console-events
The responsibilities are:
profile_metrics: enables profile-defined metric rules.metrics: key is not retained as a compatibility
shim in the clean end state because SNMP traps have not shipped as a public
contract.Safe defaults:
profile_metrics is absent, no profile-local metric rules are evaluated.profile_metrics.enabled defaults to false.include entry.Profile-defined metrics are disabled by default and use explicit opt-in only.
There is no automatic rule selection: profile_metrics.include names every
rule selected by the job. A profile rule may set enabled: false; that always
makes explicit selection fail validation.
profile_metrics.include entries select metric rule names, not trap names and
not profile filenames.
Trap profiles have a first-class optional metrics: section. The section
is file-local and validated together with
varbinds:, traps:, and the optional profile-local charts: section.
The profile format has one canonical YAML surface. It is the validation, runtime, generated-stock, source-control review, and operator authoring contract. Unknown fields and removed aliases are rejected at profile load.
Canonical shape:
metrics:
- name: IF-MIB::unexpected-link-down-events
type: counter
on_trap: IF-MIB::linkDown
where:
- varbind: ifAdminStatus
equals: up
identity:
device: source
resource:
class: interface
key_from_varbind: ifIndex
max_per_source: 512
output:
metric: snmp_trap_if_unexpected_link_down_events
dimension: events
chart: unexpected_link_down_events
charts:
- id: unexpected_link_down_events
title: Unexpected Link Down Events
family: Network/Interface/State
context: snmp.trap.if.unexpected_link_down_events
units: events/s
algorithm: incremental
lifecycle:
max_instances: 2000
expire_after_cycles: 60
Canonical rules use these defaults:
identity.device: sourceidentity.resource.key_from_varbind: ifIndexidentity.resource.max_per_source: 512output.metric: deterministic snmp_trap_* name derived from nameoutput.dimension: eventsoutput.chart: deterministic chart ID derived from namecharts.context: deterministic snmp.trap.* context derived from namecharts.units: events/scharts.algorithm: incrementalcharts.lifecycle: job/profile defaults unless explicitly overriddenOperators can override derived output, chart, identity, or lifecycle fields by writing the canonical field explicitly.
Canonical metric rule fields:
name: stable metric-rule identity, unique across the effective profile catalogue. Stock rules
should use a MIB-qualified stable name; operator rules should use a
site-specific prefix to avoid collisions.type: one of the supported extraction types.identity: source-device scope and optional bounded resource identity.output: emitted metric name, dimension name, and referenced chart ID.Rules MAY omit identity and output when their values can be derived
deterministically. Validation rejects derived values that collide with built-in
metrics or other enabled profile-local rules.
Type-specific selector fields:
counter and sample: on_trap is required.state with separate problem and clear traps: problem_trap and
clear_trap are required, and on_trap is invalid.state with same-OID set/clear semantics: on_trap is required together
with state.set_when and state.clear_when.Init() before listener startup.Canonical chart fields:
id: stable chart ID within the profile file.context: chart context, using the snmp.trap.* namespace.title, family, units, and algorithm.lifecycle for every chart that can create per-source or per-resource
instances.Optional chart fields:
type: chart type; defaults to the chart-template engine default. State charts
should use the clearest supported state/line representation available at
implementation time.description: operator-facing chart description for generated documentation.Rule and chart names:
output.metric names should follow the existing collector metric naming style
and must not collide with built-in snmp_trap_* metric names.output.metric names must not collide with built-in metric names emitted by
the current collector, including event, severity, error, and dedup metric
names.output.dimension must match the chart-template dimension name that
selects the rule's output.metric.charts.context values use the chart context namespace and must start with
snmp.trap..output.chart must reference a chart id defined in the same profile file.output.chart validation happens during profile validation or job Init().
Errors must name the profile file and metric rule. Cross-file chart references
are validation errors.Optional rule fields:
where: bounded predicates over profile-known varbinds or static trap fields.missing: explicit behavior for missing varbinds.scale: numeric multiplier/divisor for sampled values, for example
scale: { multiplier: 1, divisor: 100 }.enabled: make this file's rule available or unavailable for job selection.description: author-facing note explaining why the rule exists.The profile charts: section is a profile-local chart-template description. The
loader compiles it into an in-memory charttpl.Spec; unsupported chart-template
fields are rejected during profile validation.
The initial design must support these rule types:
counter: increments a cumulative event counter when the trap and optional
predicates match.sample: stores the last numeric value extracted from a matching trap for the
source/resource identity. Samples are not interpolated and do not imply
continuous polling, but active sample series must still be emitted on every
periodic Collect() cycle until they expire or are cleared by lifecycle
rules.state: stores current trap-derived state for a source or source/resource.The design should not add a separate "multi-value" type. Multiple metric rules may reference the same trap, and chart metadata can group their outputs into one chart. This supports multi-value notifications without making extraction rules harder to validate.
The following examples use the only supported canonical form.
metrics:
- name: CISCO-CONFIG-MAN-MIB::cli-config-change-console-events
type: counter
on_trap: CISCO-CONFIG-MAN-MIB::ccmCLIRunningConfigChanged
where:
- varbind: ccmHistoryEventTerminalType
in: [console, terminal, virtual]
identity:
device: source
output:
metric: snmp_trap_cisco_cli_config_change_events
dimension: changes
chart: cisco_cli_config_changes
charts:
- id: cisco_cli_config_changes
title: Cisco CLI Configuration Changes
family: Configuration/Changes
context: snmp.trap.cisco.cli_config_changes
units: events/s
algorithm: incremental
lifecycle:
max_instances: 2000
expire_after_cycles: 60
metrics:
- name: CISCO-PROCESS-MIB::cpu-threshold-current
type: sample
on_trap: CISCO-PROCESS-MIB::cpmCPURisingThreshold
value_from_varbind: cpmCPUTotalMonIntervalValue
scale: { multiplier: 1, divisor: 1 }
identity:
device: source
output:
metric: snmp_trap_cisco_cpu_threshold_sample_percent
dimension: current
chart: cisco_cpu_threshold
- name: CISCO-PROCESS-MIB::cpu-threshold-limit
type: sample
on_trap: CISCO-PROCESS-MIB::cpmCPURisingThreshold
value_from_varbind: cpmCPURisingThresholdValue
scale: { multiplier: 1, divisor: 1 }
identity:
device: source
output:
metric: snmp_trap_cisco_cpu_threshold_limit_percent
dimension: threshold
chart: cisco_cpu_threshold
charts:
- id: cisco_cpu_threshold
title: Cisco CPU Threshold Trap Values
family: System/CPU
context: snmp.trap.cisco.cpu_threshold
units: percentage
algorithm: absolute
lifecycle:
max_instances: 2000
expire_after_cycles: 60
metrics:
- name: IF-MIB::link-down-state
type: state
problem_trap: IF-MIB::linkDown
clear_trap: IF-MIB::linkUp
identity:
device: source
resource:
class: interface
key_from_varbind: ifIndex
max_per_source: 512
state:
problem_value: 1
clear_value: 0
ttl: 24h
output:
metric: snmp_trap_if_link_down_state
dimension: down
chart: if_link_down_state
charts:
- id: if_link_down_state
title: Trap-Derived Interface Link State
family: Network/Interface/State
context: snmp.trap.if.link_down_state
units: state
algorithm: absolute
lifecycle:
max_instances: 2000
expire_after_cycles: 60
metrics:
- name: SNMP-ALARM-MIB::alarm-set-state
type: state
on_trap: SNMP-ALARM-MIB::snmpAlarmStatusChange
identity:
device: source
resource:
class: alarm
key_from_varbind: snmpAlarmLogId
max_per_source: 1024
state:
set_when:
varbind: snmpAlarmLogCond
equals: set
clear_when:
varbind: snmpAlarmLogCond
equals: clear
problem_value: 1
clear_value: 0
ttl: 24h
output:
metric: snmp_trap_alarm_set_state
dimension: set
chart: snmp_alarm_set_state
charts:
- id: snmp_alarm_set_state
title: SNMP Alarm State
family: Alarms/State
context: snmp.trap.alarm.state
units: state
algorithm: absolute
lifecycle:
max_instances: 2000
expire_after_cycles: 60
Trap metrics must never aggregate all source devices into one listener total unless the metric is explicitly listener-owned and not device-attributable.
Recommended identity model:
SourceVnodeID is not a reason to lose the trap.TRAP_ENRICHMENT or equivalent log evidence.pipeline.write_failed increments at most once and
only when the authoritative output commit fails. Backend-specific
job-level error counters may still record the corresponding failure class.SourceVnodeID.source_id and source_kind labels.source_id is derived from the selected trap source identity:
trusted snmpTrapAddress.0 when accepted from a trusted relay, otherwise the
UDP peer address.synthetic_vnode is a future opt-in mode and must be rejected by the
initial schema until it is implemented deliberately.TRAP_ENRICHMENT.conflict or ambiguous enrichment status,
vnode_mismatch or ambiguous_source reasons, rejected candidates, or an
original-source address supplied by an untrusted relay.Collect() cycle. They must not
become sparse just because no trap arrived in a cycle.Built-in static charts for pipeline progress, trap events, severities, processing errors, and dedup suppression remain listener/job-scoped. This keeps receiver health visible for unattributable packets and global listener failures without paying an always-on per-sender series cost.
Operators investigate senders by filtering and grouping trap rows on
TRAP_SOURCE_IP and TRAP_SOURCE_UDP_PEER, then inspecting TRAP_ENRICHMENT
for relay, ambiguity, and vnode evidence. Per-device vendor semantics are
delivered by explicitly selected profile rules.
Fallback profile-metric source identity priority:
snmpTrapAddress.0 only when the UDP peer is a configured trusted
relay.Required labels for fallback profile metrics:
job_name
source_id
source_kind, for example trusted_trap_address or udp_peer
source_kind is a closed label set: vnode, listener,
trusted_trap_address, udp_peer, entry_source, hostname_or_ip,
trap_varbind, topology_ifindex, source, or other. Unknown future
enrichment methods map to other.
Fallback source_id uses a deterministic one-way hash of the canonical source
address and job name with the agent's stable local identity as salt; expose
only a truncated fixed-length hex value.
Hashing uses SHA-256, truncates to 16 hexadecimal characters, and canonicalizes addresses without transport ports.
Hash mode is not a security boundary. Small source-address spaces can be
enumerated, and an agent reinstall or stable-local-identity reset changes the
salt and therefore changes every hashed source_id.
The salt source must be a persisted Agent-local identity with restart-stable lifetime. The implementation must document which Agent identity is used; the salt value itself must not be exposed as a metric label.
Phase A reads /etc/machine-id, then /var/lib/dbus/machine-id, then the
hostname, and finally uses a fixed last-resort string only when no stable
local identity is available.
The design may later add an explicit opt-in mode for synthetic trap-source vnodes, but that must be an operator decision with caps. It is not the default.
Source identity transitions:
SourceVnodeID.snmp_trap_profile_metrics_source_transitions so
chart discontinuity is explainable.Source cap behavior:
Identity to host-scope mapping:
SourceVnodeID is known, use V2 host scope with
ScopeKey=SourceVnodeID and GUID=SourceVnodeID.source_id and source_kind, including
vnode-scoped series. This keeps one stable chart-template identity across
vnode and fallback source modes while V2 host scope remains the primary node
attribution.SourceVnodeID is absent, use the bounded fallback source identity under
the receiver/default host scope.SourceVnodeID, new emissions for that source use vnode host scope.Framework evidence:
metrix.HostScope GUID and metadata.Netdata does not support sparse receiver metrics. Trap-derived metric state is updated by trap arrival and emitted by the periodic collector cycle.
Required behavior:
Collect() cycle regardless of
trap arrival in that cycle.Collect() cycle while the identity is active.Collect()
cycle until an explicit clear, TTL expiry, job teardown, or chart lifecycle
expiry removes it.Collect() cycle while the sample is fresh.missing: drop does not clear an existing active
sample or state value. It only skips the update for the received trap and
increments the rule-miss counter.missing: error skips the update and increments the
extraction-failure counter.Resource identity is separate from arbitrary labels.
Allowed resource keys:
identity.resource.key_from_varbind: INTEGER, Integer32, Unsigned32,
and Gauge32.ifIndex are valid when capped per source.Rejected as default metric labels:
Each resource rule must define:
class: a stock resource class (interface, peer, neighbor, sensor,
alarm, pool, l2_topology, component) or a site-specific lowercase
class beginning with site_;key_from_varbind or key_from_enrichment;max_per_source when the fixed default of 512 is too high;missing behavior;Cardinality contract:
| Cap | Scope | Required behavior |
|---|---|---|
| Fixed selected-rule limit (500) | job | Maximum enabled metric rules evaluated for the job. |
| Fixed source limit (2,000) | job | Maximum non-listener source identities tracked by the job, including vnode and fallback sources. |
| Fixed resource limit (512) | job | Default upper bound for resources tracked per source and resource class. |
identity.resource.max_per_source | rule | Rule-local upper bound for resources of that class per source. |
charts.lifecycle.max_instances | chart | Upper bound for chart instances created by that chart. |
| Fixed instance limit (50,000) | job | Final upper bound across all profile-derived chart instances in the job. |
Effective runtime caps use the most restrictive applicable limit. A rule cannot create a new source, resource, or chart instance when any applicable job, rule, or chart cap is exhausted.
These caps are collector-enforced before writing to metrix and before the
chart-template engine sees the sample. charts.lifecycle.max_instances remains
a chartengine defense-in-depth limit because chart template lifecycle caps are
best-effort for already-active instances.
The selected-rule limit counts the explicit, post-merge include set. Disabled
rules and rules that fail validation cannot be selected.
When the job-level instance cap has one remaining slot and multiple rules would
create new instances in the same cycle, the implementation must use a
deterministic tie-breaker, for example lexical chart.id then metric rule
name. The tie-breaker must be documented and tested.
Initial stock resource classes should be limited to:
interfacepeerneighborsensoralarmpooll2_topologycomponentOperator-defined classes are allowed only with a site-specific prefix.
The loader must validate stock classes against the stock list above and operator
classes against a documented site-prefix pattern.
Stock class validation failures are hard errors. Operator class names must match
a documented lowercase identifier pattern and include a site-specific prefix, for
example site_foo_sensor.
Overflow drops new source/resource instances beyond the cap and increments a built-in overflow counter. Runtime cap exhaustion does not stop the receiver job or drop accepted traps.
Dropped raw resource keys may be logged at debug level for troubleshooting, but they must not be promoted to labels or durable public artifacts.
The design should support bounded predicates, not a general expression language.
Required operators:
equalsinexistsabsentgreater_thanless_thanrangeAllowed predicate inputs:
Predicate semantics:
varbind or
field. Both, neither, and non-string selectors are invalid; use separate
predicates for additional AND constraints.equals, in,
exists, absent, greater_than, less_than, or range.where makes the predicate false and the rule does not
match.exists: true matches when the referenced varbind is present in the received
trap.exists: false is equivalent to absent: true.absent: true is the only predicate that matches a missing varbind.equals and in over enum-backed varbinds match enum labels. equals and
in over numeric varbinds match numeric values.TimeTicks predicates
compare raw hundredths of a second; sample output conversion to seconds is
separate.range is inclusive on both ends: low <= value <= high.not: true on a single predicate, not with a
general expression language.not: true negates the predicate result only after the referenced varbind is
known to be present. A missing varbind remains false even when not: true is
set.{varbind: ifAdminStatus, equals: up, not: true} means "the varbind is
present and its value is not up".range, not: true means outside the inclusive range. For in, it means
not in the listed set. For greater_than or less_than, it negates that
comparison.absent operator instead of not: true with
exists.in over bounded values instead of regular expressions over free-form text.Missing-varbind behavior must be explicit per rule:
drop: do not emit the metric and increment a rule-miss counter;zero: emit zero only for sample rules that explicitly declare absence means
numeric zero;unknown_dimension: allowed only for bounded dimensions and capped;error: profile validation or runtime error depending on whether the varbind
is statically impossible or just absent from a received trap.zero is invalid for counter and state rules. State rules must use
explicit set/clear predicates or trap pairs.drop, which leaves state
unchanged.Sample validation:
value_from_varbind must reference a profile-known numeric varbind.INTEGER, Integer32, Unsigned32,
Gauge32, Counter32, Counter64, and TimeTicks.DisplayString, OctetString, MacAddress, IpAddress, OBJECT IDENTIFIER, and free-form textual conventions are rejected for sample
rules.sample rules must use the absolute chart algorithm.
Counter32 and Counter64 trap-carried values may be sampled only as
absolute snapshots. Derived counter rates from sporadic trap arrivals are a
deferred feature.TimeTicks samples are converted to seconds by dividing the raw value by
100 before profile scale is applied.scale.divisor must be greater than zero.missing behavior for sample rules is drop.scale before metric emission. Profile
authors must set output.metric names, chart titles, and units so the scaled
semantics are explicit to operators.Profile metric rules should compile into normal go.d V2 chart templates.
Required behavior:
incremental chart algorithm.absolute.chart.id when they describe dimensions of
the same operational chart.charttpl.Spec validation.context values must use the snmp.trap.* namespace.Compilation path:
charttpl.Spec.ChartTemplateYAML() serves the compiled template; the implementation should
not write runtime-generated chart files to satisfy public requests.ChartTemplateYAML().Init(), not on every collection cycle.profile_metrics.include validation uses the compiled metric rule catalog.Chart conflict rules:
context only if
title, family, units, algorithm, chart type, and dimension names are
compatible.identity.resource.class values.events, severity, errors, dedup_suppressed, or
profile_metric_diagnostics, nor their effective contexts such as
snmp.trap.profile_metric_diagnostics.Lifecycle:
lifecycle.max_instances and lifecycle.expire_after_cycles.expire_after_cycles is measured in the periodic go.d Collect() cycle for
the trap listener job. It is not measured by trap receive goroutines. Changing
the listener update_every changes the wall-clock lifetime represented by the
same cycle count.Collect() cycle, not in a
background timer and not during per-trap event processing.where predicates before set/clear logic. If no
set/clear predicate matches, the rule increments a rule-miss counter and does
not change state.internal/profilemetrics.Runtime serializes state and
series mutations within the job.expire_after_cycles must not expire it before state TTL can emit
the configured clear value.This reuses the chart-template engine, but extraction remains a trap-profile responsibility.
Profile-defined metric rules must be discoverable before the first matching trap arrives.
Required behavior:
Init() must validate every profile_metrics.include entry against a
metric rule catalog before listener startup.metric_rule_names for every profile.
During job creation, profile_metrics.include hydrates only the stock files
that own the requested rules.Profiles and metric rules are immutable within a shared catalog epoch while
jobs hold leases. After editing operator profiles, restart the Agent or recreate
all running snmp_traps jobs. The final lease release unloads the epoch; the
next job creation loads profiles and validates profile_metrics.include from
scratch.
Trap metric extraction must preserve current collector ordering unless a later design explicitly changes it:
Evidence:
src/go/plugin/go.d/collector/snmp_traps/internal/jobruntime/pipeline.go returns early for dedup-suppressed traps and
authoritative write failures, then updates profile, event, and severity metrics only after a successful write.src/go/plugin/go.d/collector/snmp_traps/internal/jobruntime/profile_metrics_integration_test.go verifies that profile
metrics are not emitted after authoritative write failures or for dedup-suppressed traps.varbinds:, traps:, metrics:, and charts: are defined and validated as
one profile-file bundle; profile files are not field-merged.enabled: false makes only that file's rule unavailable.extends: key is rejected.Operator profiles that need to add metrics for traps from several stock files can use one metric-only site profile that references those stock traps by MIB-qualified trap name. If the metric needs varbind validation, resource keys, or sample extraction, the referenced stock trap definition must already define the needed varbind metadata.
The current job-level metrics: list is too limited for the target design.
Approved clean end state:
metrics: list is not treated as a public compatibility
contract.metrics: list
as needed for the long-term-best configuration model.metrics: and charts: are the only supported trap metric
authoring surface.metrics:.metrics: list.The stock trap profile generator must not create enabled metrics for every decoded trap.
Required generator behavior:
varbinds: and
traps: sections before writing YAML.Curated metric rules must have a durable source:
curated_metrics.yaml.The durable source must record:
Metric rules for generated profiles are therefore a curation layer on top of MIB decode generation, not a mechanical "one metric per trap" output.
Profile validation must reject:
metrics: and charts:
keys must at minimum be rejected instead of silently ignored;metrics:;charts:;output.metric values after merge;output.metric, output.dimension,
output.chart, or charts.context values after canonical validation;chart.id;where predicates over sensitive varbinds unless the predicate uses an
approved bounded enum or boolean representation;value_from_varbind targeting known sensitive varbinds;output.metric values that collide with built-in metrics emitted by
internal/telemetry/job.go;output.dimension values that do not match the chart dimension name selecting
the rule's output.metric.Reserved metric name prefixes:
snmp_trap_events_snmp_trap_severity_snmp_trap_errors_snmp_trap_dedup_snmp_trap_pipeline_snmp_trap_metric_snmp_trap_profile_metrics_Profile-local rules must not recreate built-in receiver pipeline health. Use profile metrics for vendor or site semantics; built-in receiver metrics cover job-level pipeline progress and processing errors.
The first implementation step that accepts profile-local metrics must:
Metrics field and a Charts field to the profile data model;The loader must not silently ignore a top-level metrics: or charts: section.
Adding profile-local metrics requires updating the profile data model and
validation in the same implementation step.
Loader requirements:
metrics: and charts: use strict known-key validation.Init().The implementation must update every durable surface that currently says trap profiles do not define metrics.
Required updates:
src/go/plugin/go.d/config/go.d/snmp.trap-profiles/profile-format.md.agents/skills/project-snmp-trap-profiles-authoring/SKILL.mdsrc/go/plugin/go.d/collector/snmp_traps/config_schema.jsonsrc/go/plugin/go.d/collector/snmp_traps/metadata.yamlsrc/health/health.d/snmp_traps.conf when chart
contexts, label identity, or vnode scoping changes affect alert matching or
alert textThe SNMP trap profile authoring skill must be updated from "profiles do not
define metrics" to "trap profiles may define metric rules only through the
validated profile-local metrics: and charts: schema described here".
Operator-facing docs, skills, stock generation docs, and contributor docs must present the canonical profile syntax only.
| Use case | Required design support |
|---|---|
| Per-device trap activity | Source vnode host scope or bounded fallback source_id; never listener-only totals for device-attributable metrics. |
| Filtered event counters | counter rules with bounded where predicates. |
| Numeric samples | sample rules with value_from_varbind, numeric validation, units, scale, and explicit algorithm. |
| Multiple metrics from one trap | Several rules can reference the same trap and share one chart. |
| Per-resource metrics | identity.resource with class, key, cap, missing behavior, and overflow behavior. |
| Trap-derived current state | state rules with problem/clear OIDs or same-OID set/clear predicates and TTL. |
| Severity-aware metrics | Bounded predicates or labels over vendor severity varbinds; no dynamic journal severity override. |
| Audit/security counters | Counters with sensitive detail kept in logs; unsafe values rejected as labels. |
| Lifecycle counters | Device-scoped counters for restart/init traps. |
| Operator-defined custom semantics | Custom or override trap profiles under the existing operator profile directory. |
| Standard alarm set/clear | Same-OID state rules using set/clear condition varbinds and bounded alarm resource keys. |
| Environmental/power/component state | State and sample rules with source/resource scope. |
| Routing/HA adjacency state | Resource-scoped counters/state with explicit caps for peers/neighbors/groups. |
| Capacity/pool/utilization thresholds | Sample plus threshold metrics and optional clear-state rules. |
| L2 topology/neighbor counters | Counter/sample rules only; no topology mutation. |
| Receiver pipeline health | Job-scoped built-in receiver metrics preserve trap commitment ordering and remain continuous across collection cycles. |
The implemented receiver pipeline metrics cover job-level receiver-owned signals such as raw receive rate, accepted/committed rate, drop/error stages, unknown OID/MIB gaps, SNMPv3 USM breakdown, INFORM outcomes, and dedup/throttle suppression. Source-level investigation uses trap logs.
The trap-to-metrics implementation MUST preserve the common contract required by both phases:
Receiver/pipeline metrics MUST NOT silently drop accepted traps when enrichment, profile matching, source attribution, or metric extraction fails. Those failures produce diagnostics and/or log evidence; they are not reasons to discard the trap.
Receiver/pipeline metrics MUST emit continuously at job scope. Profile-defined source/resource instances MUST remain bounded by explicit caps and lifecycle rules; receiver-level totals MUST remain available for unattributable errors and global listener state.
The implementation must retain tests for:
metrics: parsing and strict validation;charts: parsing and strict validation;metrics: and
charts: before runtime support is complete;extends: key and duplicate-name detection across
independent profiles;charttpl.Spec
validation;output.chart references to same-file chart IDs;include
validation during job Init();exists and absent predicate semantics;range predicate inclusive bounds;not: true semantics, including missing-varbind behavior and absence via
absent;TimeTicks conversion to seconds;incremental algorithm for initial sample rules;SourceVnodeID;SourceVnodeID;SourceVnodeID is absent;SourceVnodeID;Collect() cycles with no new traps;value_from_varbind rejection;profile_metrics;profile_metrics configuration changes;metrics: authoring path;External reviewers should answer design questions:
metrics: the simplest model consistent with SNMP profile
methodology?