Skip to content

netstat: Use procfs parsers for netstat, snmp and snmp6 - #3796

Open
neoLsH wants to merge 3 commits into
prometheus:masterfrom
neoLsH:netstat-use-procfs-parsers
Open

netstat: Use procfs parsers for netstat, snmp and snmp6#3796
neoLsH wants to merge 3 commits into
prometheus:masterfrom
neoLsH:netstat-use-procfs-parsers

Conversation

@neoLsH

@neoLsH neoLsH commented Aug 26, 2026

Copy link
Copy Markdown

What

Replace the hand-written parsers for /proc/net/netstat, /proc/net/snmp and
/proc/net/snmp6 with the parsers from prometheus/procfs
(Proc.Netstat(), Proc.Snmp(), Proc.Snmp6()), as requested in #2336.

This is a follow-up to #2360, which was closed as stale. Instead of declaring
one prometheus.Desc per field (~3000 lines), the metrics are exposed by
iterating the procfs statistics structs via reflection, the same approach the
NFS collector (collector/nfs_linux.go) already uses.

Details

  • /proc/net/* is network-namespace local; the procfs parsers operate on
    /proc/<pid>/net/*, so the collector now reads /proc/self/net/*. On Linux
    /proc/net is an alias of /proc/self/net, so the collected data and
    namespace semantics are unchanged.
  • procfs models the fields as *float64 (Replace float64 with *float64 for proc_netstat, proc_snmp and proc_snmp6 procfs#464): fields that
    are not present on the system are nil and skipped, so — like before — only
    statistics the kernel actually provides are exported.
  • The --collector.netstat.fields flag and its default are unchanged, as are
    the metric names, types and help strings. proc.Snmp6() already tolerates a
    missing snmp6 file on systems with IPv6 disabled.
  • The exported metrics are byte-for-byte identical: the e2e golden output
    (collector/fixtures/e2e-output.txt) required no changes.

Test fixtures

procfs.FS.Self() resolves the proc/self symlink in the fixtures (pid 10),
so collector/fixtures/proc/10/net was added as a symlink to ../net,
mirroring the kernel layout where /proc/<pid>/net is a per-netns symlink.

Verification

  • go test ./collector/ (includes a new TestNetStats comparing the full
    exposition against the expected 41 metrics via testutil.CollectAndCompare)
  • ./end-to-end-test.sh (golden output unchanged)

Fixes #2336

Depends on

Comment on lines +101 to +103
// emitStruct emits one metric per non-nil field of a procfs netstat/snmp
// statistics struct, using the struct's type name as the protocol name.
func (c *netStatCollector) emitStruct(ch chan<- prometheus.Metric, stats any) {

@SuperQ SuperQ Aug 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When we are converting these collectors we are migrating to explicitly defined metric package var descriptors rather than dynamically generated Descs.

This reduces runtime allocs as well as makes it easier for us to do AST-generated documentation.

@neoLsH

neoLsH commented Aug 26, 2026

Copy link
Copy Markdown
Author

Thanks — that makes sense on both counts (per-scrape allocs and the AST-based doc generation in #3585). I'll rework the exposure layer accordingly, and I found a precedent I'd like to confirm: zoneinfo_linux.go builds an explicit field-name → prometheus.NewDesc map once in the constructor and still reflects over the procfs struct at collection time. Following that shape here keeps the descriptors as source-visible literals allocated once, without hand-expanding the emit loop. Let me know if you'd rather have one package-level *prometheus.Desc var per metric instead.

Two things worth deciding before I write it, since they change the diff shape:

1. Scale / splitting. The procfs structs expose 301 fields (netstat: TcpExt 113 + IpExt 18; snmp: 79; snmp6: 91), of which the default --collector.netstat.fields regex admits 42. For reference the largest explicit-descriptor collector today is mountstats_linux.go at 54. Happy to do one PR, or split it as netstat / snmp / snmp6 — your call on what's reviewable.

2. A metric-contract change I should flag explicitly. Diffing the old dynamic parser's output (using this repo's own fixtures) against the procfs struct fields, 10 metrics disappear: TcpExt_PAWSPassive, TcpExt_TCPPrequeued, TcpExt_TCPPrequeueDropped, TcpExt_TCPDirectCopyFromBacklog, TcpExt_TCPDirectCopyFromPrequeue, TcpExt_TCPLoss, TcpExt_TCPFACKReorder, TcpExt_TCPForwardRetrans, TcpExt_TCPHPHitsToUser, TcpExt_TCPSchedulerFailed. None of them match the default field regex, so neither the default scrape nor the e2e golden file can detect the loss — the green CI here doesn't cover it. More generally, the collector moves from "expose whatever the kernel prints" to "expose what procfs models", so newly added kernel counters will need a procfs update first. My suggestion is to accept this as an intentional change with a CHANGELOG note (those counters are legacy TcpExt fields that procfs deliberately doesn't model), but I'd rather have your call than assume — the alternative is keeping a fallback path for fields procfs doesn't model.

Also: should the help strings stay as today's Statistic <Proto><Field>., keeping this change purely mechanical and easy to review, or would you prefer real per-metric help text? That would be 301 strings, so I'd suggest a follow-up PR for it — which would also be useful input for #3585.

@SuperQ

SuperQ commented Aug 27, 2026

Copy link
Copy Markdown
Member

For the missing metrics if we're missing fields in procfs, we should update the parser there.

I wonder if it would help to do some refactoring of the procfs code to add some helper functions.

For example, the TcpExt struct fields are all *float64.

We could make this a custom type like

type StatValue struct {
  Value float64
  Name string
}

This would allow us to embed the original filename string so that we can more easily use it with the regexp matcher.

@neoLsH

neoLsH commented Aug 27, 2026

Copy link
Copy Markdown
Author

Thanks — I'll update procfs then, and rework this PR's exposure layer to explicit descriptors once that lands.

One thing I verified before writing it: none of those 10 counters (PAWSPassive, TCPPrequeued, TCPDirectCopyFromBacklog, TCPDirectCopyFromPrequeue, TCPPrequeueDropped, TCPLoss, TCPFACKReorder, TCPForwardRetrans, TCPHPHitsToUser, TCPSchedulerFailed) are present in current linus/master include/uapi/linux/snmp.h's TcpExt list, so modern kernels never print them — they only matter for metric parity with older kernels. I've opened prometheus/procfs#863 to model them as *float64 fields + parse cases, keeping the parser fully additive (absent keys stay nil on newer kernels), with tests for both the "older kernel prints them" and "modern kernel doesn't" cases.

On the StatValue idea: making all the netstat/snmp struct fields a custom type would be a breaking API change for every existing consumer of TcpExt, IpExt, and the SNMP structs. Also worth noting the current shape may already be sufficient: collectors get both the field name (reflect.Type.Field(i).Name) and the protocol from the struct's own type name, so the regexp key can be composed without embedding it in the value. Happy to take that discussion to a separate procfs issue rather than gating these two PRs on it.

One more finding from verifying the structures — a gap the fixed structs can't close: the kernel only prints non-zero IcmpMsg/Icmp6Msg per-type counters (icmpmsg_put, net/ipv4/proc.c), so the InTypeN/OutTypeN set is host-dependent and open-ended (up to 512 keys), while procfs models v4 as just InType3/OutType3 and a hand-picked subset for v6. The default --collector.netstat.fields doesn't select any of them, so this doesn't block these PRs — but a custom-regex user matching IcmpMsg_* would silently lose metrics after the switch. Longer term procfs probably wants a different shape there (e.g. a map like NetDev, or an unknown-keys escape hatch). Happy to fold that into the procfs work or a separate issue — your call.

Two things I asked earlier that I still need your call on:

  1. Splitting — one PR or netstat / snmp / snmp6 split? Default: single PR unless you prefer smaller pieces.
  2. Help strings — keep the mechanical Statistic <Proto><Field>. ones to keep this review-focused, real per-metric help text as a follow-up useful for doc: add automated metrics documentation generator #3585? Default: keep + follow-up.

@neoLsH

neoLsH commented Aug 27, 2026

Copy link
Copy Markdown
Author

Update: the exposure layer is now reworked per the review. collector/netstat_descs_linux.go holds the explicit, source-visible prometheus.Desc literals for all 311 netstat/snmp/snmp6 fields (generated from the procfs structs, allocated once at package init), and netstat_linux.go looks them up by <protocol>_<field> at collection time instead of calling prometheus.NewDesc per scrape — same shape as zoneinfo_linux.go. Metric names, help strings and types are unchanged, so the e2e golden output is unaffected.

Note the descriptors for the 10 legacy TcpExt counters are already present in the table; they stay silent (nil) until prometheus/procfs#863 lands and go.mod is bumped to the tagged release, at which point they populate for users whose field regex selects them.

@neoLsH
neoLsH force-pushed the netstat-use-procfs-parsers branch from ddf308a to e5e5818 Compare August 27, 2026 10:06
neoLsH added a commit to neoLsH/procfs that referenced this pull request Aug 27, 2026
PAWSPassive, TCPPrequeued, TCPDirectCopyFromBacklog,
TCPDirectCopyFromPrequeue, TCPPrequeueDropped, TCPLoss, TCPFACKReorder,
TCPForwardRetrans, TCPHPHitsToUser and TCPSchedulerFailed are absent from
the current kernel TcpExt table but are still printed by older kernels.
Model them so consumers keep metric parity with older kernels; the parser
stays additive (absent keys remain nil).

See prometheus/node_exporter#3796.

Signed-off-by: 霏承 <huangleshu.hls@alibaba-inc.com>
SuperQ pushed a commit to prometheus/procfs that referenced this pull request Aug 27, 2026
PAWSPassive, TCPPrequeued, TCPDirectCopyFromBacklog,
TCPDirectCopyFromPrequeue, TCPPrequeueDropped, TCPLoss, TCPFACKReorder,
TCPForwardRetrans, TCPHPHitsToUser and TCPSchedulerFailed are absent from
the current kernel TcpExt table but are still printed by older kernels.
Model them so consumers keep metric parity with older kernels; the parser
stays additive (absent keys remain nil).

See prometheus/node_exporter#3796.

Signed-off-by: 霏承 <huangleshu.hls@alibaba-inc.com>
@neoLsH

neoLsH commented Aug 27, 2026

Copy link
Copy Markdown
Author

FYI: the procfs side has landed — prometheus/procfs#863 (the 10 legacy TcpExt counters) is now merged. I'll hold off on the go.mod bump until procfs cuts a formal release tag, then update it here in one synchronized change so the legacy descriptors already present in netstat_descs_linux.go start populating for users whose field regex selects them. No action needed on this PR until that tag exists.

@SuperQ

SuperQ commented Aug 27, 2026

Copy link
Copy Markdown
Member

I'll cut a new procfs tag today or tomorrow.

neoLsH added a commit to neoLsH/node_exporter that referenced this pull request Aug 28, 2026
procfs v0.22.0 includes prometheus/procfs#863, which models the 10 legacy
TcpExt counters. With this bump, the explicit descriptors already present in
netstat_descs_linux.go start populating for users whose field regex selects
them, restoring parity with older kernels.

Part of prometheus#3796.

Signed-off-by: 霏承 <huangleshu.hls@alibaba-inc.com>
@neoLsH

neoLsH commented Aug 28, 2026

Copy link
Copy Markdown
Author

procfs v0.22.0 is out and includes prometheus/procfs#863, so I've bumped go.mod to it here. The 10 legacy TcpExt descriptors already present in netstat_descs_linux.go now populate for users whose --collector.netstat.fields regex selects them, restoring parity with older kernels. Default-regex output is unchanged.

neoLsH and others added 3 commits August 29, 2026 00:31
Replace the hand-written parsers for /proc/net/netstat, /proc/net/snmp
and /proc/net/snmp6 with the parsers from prometheus/procfs
(Proc.Netstat, Proc.Snmp and Proc.Snmp6). The statistics are read via
/proc/self/net, which is equivalent to /proc/net (both are
network-namespace local views of the current process), so the collected
data is unchanged.

Metrics are exposed by iterating the procfs statistics structs, the
same approach the NFS collector uses: fields are *float64 and only the
ones present on the system are exported, matching the previous
behavior. The --collector.netstat.fields flag and the exported metric
names are unchanged; the e2e golden output is identical.

Signed-off-by: neoLsH <43921685+neoLsH@users.noreply.github.com>
Replace the per-scrape prometheus.NewDesc calls with a pre-built map of
explicit descriptors (netstat_descs_linux.go, generated from the procfs
statistics structs), as requested in review. Collection still reflects over
the procfs structs but looks up the descriptor by "<protocol>_<field>", so
descriptors are allocated once and are source-visible literals for the
AST-based doc generation. Metric names, help strings and types are unchanged.

Signed-off-by: 霏承 <huangleshu.hls@alibaba-inc.com>
procfs v0.22.0 includes prometheus/procfs#863, which models the 10 legacy
TcpExt counters. With this bump, the explicit descriptors already present in
netstat_descs_linux.go start populating for users whose field regex selects
them, restoring parity with older kernels.

Part of prometheus#3796.

Signed-off-by: 霏承 <huangleshu.hls@alibaba-inc.com>
@neoLsH
neoLsH force-pushed the netstat-use-procfs-parsers branch from 3cc3f97 to 1b17714 Compare August 28, 2026 16:32
@@ -0,0 +1,1262 @@
// Code generated by gendesc; DO NOT EDIT.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't see this in the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace netstat parsers with procfs netstat parsers

2 participants