# Collectors Snmp Profiles

> Use when editing Netdata SNMP profile YAMLs, topology SNMP profiles, ddsnmp profile parsing, or profile-format documentation. Requires checking source MIB field accessibility, especially MAX-ACCESS not-accessible INDEX objects, before adding or changing profile symbols.

- Skill: `netdata/collectors-snmp-profiles` (Agent Skill)
- Install (CLI): `npx skillmds@latest add netdata/collectors-snmp-profiles`
- Raw SKILL.md: https://api.skillmd.com/api/skills/netdata/collectors-snmp-profiles/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: netdata (https://skillmd.com/u/netdata)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/netdata/collectors-snmp-profiles

---


# SNMP Profile Authoring

Use this skill before editing files under:

- `src/go/plugin/go.d/config/go.d/snmp.profiles/`
- `src/go/plugin/go.d/collector/snmp/ddsnmp/`
- `src/go/plugin/go.d/collector/snmp/profile-format.md`
- `src/go/plugin/go.d/collector/snmp_topology/`

Boundary: this skill owns the profile `topology:` rows, which OIDs feed the SNMP topology producer and how they are
extracted. What the producer emits from those observations (actors, links, evidence, modals) is the payload contract
in `src/plugins.d/FUNCTION_TOPOLOGY_DEVELOPER_GUIDE.md`; the developer workflow for changing it is
`.agents/skills/topology-authoring/SKILL.md`.

## Required Checks

1. Identify the source MIB object for every profile field being added or changed.
2. Check the object's `MAX-ACCESS`.
3. If the object is `not-accessible`, do not configure it as a readable `symbol.OID` unless it satisfies every
   device-scoped MIB-deviation requirement below.
4. If a `not-accessible` object appears in the table `INDEX`, derive it from the row OID index using `index` or `index_transform`.
5. Keep index extraction and value formatting separate:
   - use `index` for one index component;
   - use `index_transform` for multiple components;
   - use `symbol.format` only for final formatting such as `ip_address`, `mac_address`, or `hex`.
6. Put SNMP topology rows under top-level `topology:` with a required closed
   `kind`. Do not mark topology rows by naming metrics `_topology_*`.
7. Do not use chart/export-only value fields on topology row anchor symbols:
   `chart_meta`, `metric_type`, `mapping`, `transform`, `scale_factor`,
   or `format`.
8. Keep regular `systemUptime` rows under `metrics:`. Do not model uptime as a
   topology row kind; topology-specific uptime acquisition belongs in collector
   code, not profile topology schema.
9. Put SNMP licensing rows under top-level `licensing:`. Do not model licensing
   telemetry as underscore-prefixed hidden metrics or `_license_*` tag
   protocols.
10. Licensing row value symbols may use `format` and exact `mapping`, but must
   not use chart/export fields, transforms, scale factors, or
   underscore-prefixed generated names.
11. For scalar licensing rows that combine multiple scalar signal OIDs into one
   license row, declare an explicit stable `id:`. For table licensing rows,
   keep every effective OID source (`symbol.OID`, `from`, or legacy `OID`) inside the same table OID and derive
   `not-accessible` INDEX values from the row index.
12. Put SNMP BGP rows under top-level `bgp:`. Do not model BGP telemetry as
   vendor-specific raw metrics, `virtual_metrics`, or underscore-prefixed tag
   protocols when adding or migrating BGP coverage.
13. BGP peer-state mappings must use the six RFC 4271 canonical states
   (`idle`, `connect`, `active`, `opensent`, `openconfirm`, `established`).
   Use `partial: true` plus `partial_states` only when the source MIB is
   intentionally partial.
14. For BGP table rows, derive `not-accessible` INDEX objects with exactly one
   row-index selector: `index`, `index_from_end`, or `index_transform`. Use
   `index_from_end` for trailing AFI/SAFI-like components after variable-length
   indexes such as `InetAddress`.
15. To constrain a topology table to a fixed structural index prefix, use one
    readable anchor symbol and set `table.OID` to `<anchor-column>.<prefix>`.
    A simple same-index cross-table dependency inherits that prefix and is
    anchor-gated only when its target table has no symbol-bearing producer.
    A target route with a real symbol owner remains eager. Do not rely on
    propagation for ordinary metrics, multiple anchors, `lookup_symbol`,
    `index_transform`, or conflicting dependency scopes.

## Device-Scoped MIB Deviations

A vendor profile MAY poll an object that the source MIB marks `not-accessible` only when all of these requirements hold:

- **Scope:** The polling row is declared in a selector-matched device or topology-role profile. A generic standards
  profile MUST NOT carry the deviation.
- **Proof:** A checked repository fixture or repeatable live capture proves that the matched device returns the object as
  a readable column and omits the standards-readable anchor needed to obtain the row otherwise.
- **Isolation:** The device row overrides the generic row identity instead of adding a second generic fallback or a second
  topology kind.
- **Documentation:** A short profile comment explains the vendor deviation and why index-only decoding cannot be used.
- **Regression:** Tests pin both the selector-scoped override and real fixture collection through the profile parser and
  consumer path.

Do not generalize a device deviation to related models or vendors without equivalent evidence.

## Canonical Profile Syntax

Stock profiles MUST use symbol objects for OID/name pairs, including metric tags and typed licensing/BGP values.
Put metric type overrides on each affected `symbol` or `symbols` entry instead of the deprecated row-level `metric_type`.
Legacy custom profile aliases remain supported; typed OID precedence is `symbol.OID`, then `from`, then legacy `OID`.
Collection, boundary validation and inheritance identity use that same source. Prefer one OID source form per value.
Metric and topology column rows require `table.OID`; `table.name` is optional for those rows.
The retired `constant_value_one` key is ignored. Ordinary metric symbols still require OIDs; do not use the key to generate or suppress metrics.

## Index Rules

- `index` is 1-based.
- `index_transform.start` and `index_transform.end` are 0-based and inclusive.
- `index_transform: [{start: N}]` keeps index component `N` through the last component when `N > 0`.
- `index_transform: [{start: 0, end: 0}]` keeps only the first index component.
- `drop_right` can be used when the right side has fixed trailing components.

## Common Patterns

Q-BRIDGE learned FDB MAC:

```yaml
- tag: dot1q_fdb_mac
  symbol:
    format: mac_address
  index_transform:
    - start: 1
```

IP-MIB `ipNetToPhysicalTable` address:

```yaml
- tag: arp_ip
  symbol:
    format: ip_address
  index_transform:
    - start: 3
```

The `start: 3` skips `ifIndex`, address type, and the InetAddress length byte.

IP-MIB `ipAddressTable` IPv4 address:

```yaml
table:
  OID: 1.3.6.1.2.1.4.34.1.3.1.4
  name: ipAddressTableIPv4
symbols:
  - OID: 1.3.6.1.2.1.4.34.1.3
    name: ip_if_index
metric_tags:
  - tag: topo_ip_addr
    symbol:
      name: ipAddressAddr
    index_transform:
      - start: 2
```

The table root anchors on readable `ipAddressIfIndex` and appends index prefix
`1.4` for IPv4 type and its required four-octet length. The address is the
not-accessible `ipAddressAddr` index component; `start: 2` skips address type
and length. The open-ended slice is intentional: a fixed `end` would hide
malformed trailing components instead of letting the strict consumer reject
the row. Keep the transformed components raw and require the topology
consumer to decode exactly four decimal octets in `0..255`; structural walk
scope alone does not validate the remaining instance suffix. A malformed
descendant can activate dependency walks, but it MUST NOT emit topology.

LLDP-MIB local management address:

```yaml
- tag: lldp_loc_mgmt_addr
  symbol:
    name: lldpLocManAddr
    format: hex
  index_transform:
    - start: 2
```

The `start: 2` skips management-address subtype and length. Use `hex`, not
`ip_address`, because LLDP management addresses can carry non-IP subtypes; the
topology runtime normalizes IP-compatible bytes later.

## Audit Recipe

When a profile reads a table column, verify that the MIB object is readable:

```bash
rg -n -C 4 'OBJECT-TYPE|MAX-ACCESS[[:space:]]+not-accessible|ACCESS[[:space:]]+not-accessible' path/to/MIB
```

For known topology-sensitive symbols, scan profile YAMLs before committing:

```bash
rg -n 'name:[[:space:]]*(dot1qTpFdbAddress|ipAddressAddr|ipNetToPhysicalIfIndex|ipNetToPhysicalNetAddressType|ipNetToPhysicalNetAddress|lldpLocManAddrSubtype|lldpLocManAddr|lldpRemManAddrSubtype|lldpRemManAddr)\b' src/go/plugin/go.d/config/go.d/snmp.profiles
```

Any hit must be reviewed. It is valid only when the tag is index-derived without a `symbol.OID`, or when it satisfies every
device-scoped MIB-deviation requirement above.

When adding a new topology kind, update all three parts together:

- profile YAML using `topology: - kind: <kind>`;
- the Go `TopologyKind` enum and validation;
- the topology cache handler registry and tests.

Verify that topology rows are delivered through `ProfileMetrics.TopologyMetrics`,
not through underscore-prefixed `HiddenMetrics`.

When adding or migrating licensing profile coverage, update all related parts
together:

- profile YAML using `licensing:`;
- the closed licensing signal/state/sentinel enums and validation when adding
  new policy names;
- typed `ProfileMetrics.LicenseRows` producer/consumer tests;
- MIB evidence for every OID and every `not-accessible` index-derived field.

Verify that licensing rows are delivered through `ProfileMetrics.LicenseRows`,
not through underscore-prefixed `HiddenMetrics`.

When adding or migrating BGP profile coverage, update all related parts
together:

- profile YAML using `bgp:`;
- closed BGP row kind, peer-state, AFI/SAFI, and typed field validation when
  adding new policy names or value domains;
- typed `ProfileMetrics.BGPRows` producer/consumer tests;
- MIB evidence for every OID and every `not-accessible` index-derived field;
- SNMP integration metadata and generated docs when public BGP capability
  claims change.

Verify that BGP rows are delivered through `ProfileMetrics.BGPRows`, not
through `metrics:`, `virtual_metrics:`, or underscore-prefixed hidden metrics.

When adding or refactoring SNMP profile, parser, or topology tests, prefer
table-driven cases using `map[string]struct{}` keyed by test-case name when
the cases share setup and assertion shape. Use separate test functions only for
materially different setup or assertions.

## Validation

Run the narrow suites for the changed area:

```bash
cd src/go
go test ./plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition
go test ./plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector
go test ./plugin/go.d/collector/snmp_topology
```

See `src/go/plugin/go.d/collector/snmp/profile-format.md` for the full profile syntax and the "Field accessibility" section.

