From cc6d988d0fe0030ad94524555b2b095b5e7a5922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9C=8F=E6=89=BF?= Date: Thu, 27 Aug 2026 17:36:01 +0800 Subject: [PATCH] netstat: model legacy TcpExt counters removed from recent kernels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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: 霏承 --- proc_netstat.go | 34 +++++++++++++++++++++++++++ proc_netstat_test.go | 56 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/proc_netstat.go b/proc_netstat.go index 7f94cc89..b91423fc 100644 --- a/proc_netstat.go +++ b/proc_netstat.go @@ -146,6 +146,20 @@ type TcpExt struct { // nolint:revive TCPMTUPFail *float64 TCPMTUPSuccess *float64 TCPWqueueTooBig *float64 + + // Legacy TcpExt counters dropped from recent kernels' TcpExt table; + // modeled so consumers retain metric parity with older kernels that + // still print them in /proc/net/netstat. + TCPLoss *float64 + PAWSPassive *float64 + TCPForwardRetrans *float64 + TCPSchedulerFailed *float64 + TCPPrequeued *float64 + TCPDirectCopyFromBacklog *float64 + TCPDirectCopyFromPrequeue *float64 + TCPPrequeueDropped *float64 + TCPFACKReorder *float64 + TCPHPHitsToUser *float64 } type IpExt struct { // nolint:revive @@ -396,6 +410,26 @@ func parseProcNetstat(r io.Reader, fileName string) (ProcNetstat, error) { procNetstat.TCPMTUPSuccess = &value case "TCPWqueueTooBig": procNetstat.TCPWqueueTooBig = &value + case "TCPLoss": + procNetstat.TCPLoss = &value + case "PAWSPassive": + procNetstat.PAWSPassive = &value + case "TCPForwardRetrans": + procNetstat.TCPForwardRetrans = &value + case "TCPSchedulerFailed": + procNetstat.TCPSchedulerFailed = &value + case "TCPPrequeued": + procNetstat.TCPPrequeued = &value + case "TCPDirectCopyFromBacklog": + procNetstat.TCPDirectCopyFromBacklog = &value + case "TCPDirectCopyFromPrequeue": + procNetstat.TCPDirectCopyFromPrequeue = &value + case "TCPPrequeueDropped": + procNetstat.TCPPrequeueDropped = &value + case "TCPFACKReorder": + procNetstat.TCPFACKReorder = &value + case "TCPHPHitsToUser": + procNetstat.TCPHPHitsToUser = &value } case "IpExt": switch key { diff --git a/proc_netstat_test.go b/proc_netstat_test.go index c1409654..2cf4ac14 100644 --- a/proc_netstat_test.go +++ b/proc_netstat_test.go @@ -14,6 +14,7 @@ package procfs import ( + "strings" "testing" ) @@ -48,3 +49,58 @@ func TestProcNetstat(t *testing.T) { } } } + +// TestParseProcNetstatLegacyFields covers TcpExt counters that are no longer +// part of the kernel's TcpExt table but were still printed by older kernels. +func TestParseProcNetstatLegacyFields(t *testing.T) { + payload := `TcpExt: SyncookiesSent PAWSPassive TCPPrequeued TCPDirectCopyFromBacklog TCPDirectCopyFromPrequeue TCPPrequeueDropped TCPLoss TCPFACKReorder TCPForwardRetrans TCPHPHitsToUser TCPSchedulerFailed +TcpExt: 1 2 3 4 5 6 7 8 9 10 11 +IpExt: InNoRoutes OutOctets +IpExt: 12 13` + + procNetstat, err := parseProcNetstat(strings.NewReader(payload), "net/netstat") + if err != nil { + t.Fatal(err) + } + + for _, test := range []struct { + name string + want float64 + have float64 + }{ + {name: "TcpExt:SyncookiesSent", want: 1, have: *procNetstat.SyncookiesSent}, + {name: "TcpExt:PAWSPassive", want: 2, have: *procNetstat.PAWSPassive}, + {name: "TcpExt:TCPPrequeued", want: 3, have: *procNetstat.TCPPrequeued}, + {name: "TcpExt:TCPDirectCopyFromBacklog", want: 4, have: *procNetstat.TCPDirectCopyFromBacklog}, + {name: "TcpExt:TCPDirectCopyFromPrequeue", want: 5, have: *procNetstat.TCPDirectCopyFromPrequeue}, + {name: "TcpExt:TCPPrequeueDropped", want: 6, have: *procNetstat.TCPPrequeueDropped}, + {name: "TcpExt:TCPLoss", want: 7, have: *procNetstat.TCPLoss}, + {name: "TcpExt:TCPFACKReorder", want: 8, have: *procNetstat.TCPFACKReorder}, + {name: "TcpExt:TCPForwardRetrans", want: 9, have: *procNetstat.TCPForwardRetrans}, + {name: "TcpExt:TCPHPHitsToUser", want: 10, have: *procNetstat.TCPHPHitsToUser}, + {name: "TcpExt:TCPSchedulerFailed", want: 11, have: *procNetstat.TCPSchedulerFailed}, + {name: "IpExt:InNoRoutes", want: 12, have: *procNetstat.InNoRoutes}, + {name: "IpExt:OutOctets", want: 13, have: *procNetstat.OutOctets}, + } { + if test.want != test.have { + t.Errorf("want %s %f, have %f", test.name, test.want, test.have) + } + } + + modern := `TcpExt: SyncookiesSent +TcpExt: 1 +IpExt: InNoRoutes +IpExt: 12` + + pn, err := parseProcNetstat(strings.NewReader(modern), "net/netstat") + if err != nil { + t.Fatal(err) + } + + if pn.SyncookiesSent == nil || *pn.SyncookiesSent != 1 { + t.Error("want TcpExt:SyncookiesSent 1") + } + if pn.TCPPrequeued != nil { + t.Error("TCPPrequeued should be nil when the kernel does not report it") + } +}