Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions collector/fixtures/proc/10/net
78 changes: 78 additions & 0 deletions collector/netstat_descs_drift_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package collector

import (
"os"
"reflect"
"regexp"
"sort"
"testing"

"github.com/prometheus/procfs"
)

// TestNetStatDescsInSync guards against drift between the committed descriptor
// table (netstat_descs_linux.go) and the procfs structs it is generated from.
// If a procfs upgrade adds or removes statistics fields, this test fails until
// the table is regenerated with `GOOS=linux go generate ./collector`.
func TestNetStatDescsInSync(t *testing.T) {
src, err := os.ReadFile("netstat_descs_linux.go")
if err != nil {
t.Fatalf("read netstat_descs_linux.go: %v", err)
}

re := regexp.MustCompile(`"([A-Za-z0-9]+_[A-Za-z0-9]+)": prometheus\.NewDesc`)
committed := map[string]bool{}
for _, m := range re.FindAllSubmatch(src, -1) {
committed[string(m[1])] = true
}

var n procfs.ProcNetstat
var s procfs.ProcSnmp
var s6 procfs.ProcSnmp6
expected := map[string]bool{}
for _, v := range []any{
n.TcpExt, n.IpExt,
s.Ip, s.Icmp, s.IcmpMsg, s.Tcp, s.Udp, s.UdpLite,
s6.Ip6, s6.Icmp6, s6.Udp6, s6.UdpLite6,
} {
tt := reflect.TypeOf(v)
for i := 0; i < tt.NumField(); i++ {
f := tt.Field(i)
if f.Type != reflect.TypeFor[*float64]() {
continue
}
expected[tt.Name()+"_"+f.Name] = true
}
}

var missing, extra []string
for k := range expected {
if !committed[k] {
missing = append(missing, k)
}
}
for k := range committed {
if !expected[k] {
extra = append(extra, k)
}
}
sort.Strings(missing)
sort.Strings(extra)

if len(missing) != 0 || len(extra) != 0 {
t.Fatalf("netstat_descs_linux.go is out of sync with procfs (missing=%v extra=%v); run `GOOS=linux go generate ./collector`", missing, extra)
}
}
96 changes: 96 additions & 0 deletions collector/netstat_descs_gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build ignore

// Command netstat-descs regenerates collector/netstat_descs_linux.go from the
// procfs netstat/snmp/snmp6 statistics structs.
//
// Run from the repository root:
//
// go generate ./collector
//
// The output is a table of explicit, source-visible prometheus.Desc literals
// (one per *float64 field of each statistics struct), keyed by
// "<Protocol>_<Field>". Keeping the descriptors as literals lets the AST-based
// documentation tooling read them and avoids allocating descriptors per scrape.
package main

import (
"fmt"
"os"
"reflect"
"strings"

"github.com/prometheus/procfs"
)

type entry struct {
key string
help string
}

func main() {
var n procfs.ProcNetstat
var s procfs.ProcSnmp
var s6 procfs.ProcSnmp6

structs := []any{
n.TcpExt, n.IpExt,
s.Ip, s.Icmp, s.IcmpMsg, s.Tcp, s.Udp, s.UdpLite,
s6.Ip6, s6.Icmp6, s6.Udp6, s6.UdpLite6,
}

var b strings.Builder
b.WriteString(`// Code generated by netstat-descs; DO NOT EDIT.

//go:build !nonetstat

package collector

import (
"github.com/prometheus/client_golang/prometheus"
)

// netStatMetricDescs returns the explicit, source-visible metric descriptors
// for every field of the procfs netstat/snmp/snmp6 statistics structs, keyed by
// "<Protocol>_<Field>". Descriptors are allocated once; collection looks them up
// by name instead of calling prometheus.NewDesc on every scrape.
func netStatMetricDescs() map[string]*prometheus.Desc {
return map[string]*prometheus.Desc{
`)

count := 0
for _, v := range structs {
t := reflect.TypeOf(v)
proto := t.Name()
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if f.Type != reflect.TypeFor[*float64]() {
continue
}
key := proto + "_" + f.Name
help := "Statistic " + proto + f.Name + "."
fmt.Fprintf(&b, "\t\t%q: prometheus.NewDesc(\n\t\t\tprometheus.BuildFQName(namespace, netStatsSubsystem, %q),\n\t\t\t%q,\n\t\t\tnil, nil),\n", key, key, help)
count++
}
}

b.WriteString("\t}\n}\n")

if err := os.WriteFile("netstat_descs_linux.go", []byte(b.String()), 0o644); err != nil {
fmt.Fprintln(os.Stderr, "write netstat_descs_linux.go:", err)
os.Exit(1)
}
fmt.Fprintf(os.Stderr, "wrote netstat_descs_linux.go (%d descriptors)\n", count)
}
Loading
Loading