Skip to content

Commit 5d8dd55

Browse files
Vasckodiscordianfish
authored andcommitted
Add support for per-interface SNMP6 stats
Signed-off-by: Felix Geschwindner <[email protected]>
1 parent 64d7b47 commit 5d8dd55

File tree

4 files changed

+533
-3
lines changed

4 files changed

+533
-3
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,15 @@ However, most of the API includes unit tests which can be run with `make test`.
4747
The procfs library includes a set of test fixtures which include many example files from
4848
the `/proc` and `/sys` filesystems. These fixtures are included as a [ttar](https://github.com/ideaship/ttar) file
4949
which is extracted automatically during testing. To add/update the test fixtures, first
50-
ensure the `fixtures` directory is up to date by removing the existing directory and then
51-
extracting the ttar file using `make fixtures/.unpacked` or just `make test`.
50+
ensure the `testdata/fixtures` directory is up to date by removing the existing directory and then
51+
extracting the ttar file using `make testdata/fixtures/.unpacked` or just `make test`.
5252

5353
```bash
5454
rm -rf testdata/fixtures
5555
make test
5656
```
5757

58-
Next, make the required changes to the extracted files in the `fixtures` directory. When
58+
Next, make the required changes to the extracted files in the `testdata/fixtures` directory. When
5959
the changes are complete, run `make update_fixtures` to create a new `fixtures.ttar` file
6060
based on the updated `fixtures` directory. And finally, verify the changes using
6161
`git diff testdata/fixtures.ttar`.

net_dev_snmp6.go

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright 2018 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package procfs
15+
16+
import (
17+
"bufio"
18+
"errors"
19+
"io"
20+
"os"
21+
"strconv"
22+
"strings"
23+
)
24+
25+
// NetDevSNMP6 is parsed from files in /proc/net/dev_snmp6/ or /proc/<PID>/net/dev_snmp6/.
26+
// The outer map's keys are interface names and the inner map's keys are stat names.
27+
//
28+
// If you'd like a total across all interfaces, please use the Snmp6() method of the Proc type.
29+
type NetDevSNMP6 map[string]map[string]uint64
30+
31+
// Returns kernel/system statistics read from interface files within the /proc/net/dev_snmp6/
32+
// directory.
33+
func (fs FS) NetDevSNMP6() (NetDevSNMP6, error) {
34+
return newNetDevSNMP6(fs.proc.Path("net/dev_snmp6"))
35+
}
36+
37+
// Returns kernel/system statistics read from interface files within the /proc/<PID>/net/dev_snmp6/
38+
// directory.
39+
func (p Proc) NetDevSNMP6() (NetDevSNMP6, error) {
40+
return newNetDevSNMP6(p.path("net/dev_snmp6"))
41+
}
42+
43+
// newNetDevSNMP6 creates a new NetDevSNMP6 from the contents of the given directory.
44+
func newNetDevSNMP6(dir string) (NetDevSNMP6, error) {
45+
netDevSNMP6 := make(NetDevSNMP6)
46+
47+
// The net/dev_snmp6 folders contain one file per interface
48+
ifaceFiles, err := os.ReadDir(dir)
49+
if err != nil {
50+
// On systems with IPv6 disabled, this directory won't exist.
51+
// Do nothing.
52+
if errors.Is(err, os.ErrNotExist) {
53+
return netDevSNMP6, err
54+
}
55+
return netDevSNMP6, err
56+
}
57+
58+
for _, iFaceFile := range ifaceFiles {
59+
f, err := os.Open(dir + "/" + iFaceFile.Name())
60+
if err != nil {
61+
return netDevSNMP6, err
62+
}
63+
defer f.Close()
64+
65+
netDevSNMP6[iFaceFile.Name()], err = parseNetDevSNMP6Stats(f)
66+
if err != nil {
67+
return netDevSNMP6, err
68+
}
69+
}
70+
71+
return netDevSNMP6, nil
72+
}
73+
74+
func parseNetDevSNMP6Stats(r io.Reader) (map[string]uint64, error) {
75+
m := make(map[string]uint64)
76+
77+
scanner := bufio.NewScanner(r)
78+
for scanner.Scan() {
79+
stat := strings.Fields(scanner.Text())
80+
if len(stat) < 2 {
81+
continue
82+
}
83+
key, val := stat[0], stat[1]
84+
85+
// Expect stat name to contain "6" or be "ifIndex"
86+
if strings.Contains(key, "6") || key == "ifIndex" {
87+
v, err := strconv.ParseUint(val, 10, 64)
88+
if err != nil {
89+
return m, err
90+
}
91+
92+
m[key] = v
93+
}
94+
}
95+
return m, scanner.Err()
96+
}

net_dev_snmp6_test.go

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright 2025 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package procfs
15+
16+
import (
17+
"fmt"
18+
"testing"
19+
)
20+
21+
func TestNetDevSNMP6(t *testing.T) {
22+
fs, err := NewFS(procTestFixtures)
23+
if err != nil {
24+
t.Fatal(err)
25+
}
26+
27+
netDevSNMP6, err := fs.NetDevSNMP6()
28+
if err != nil {
29+
t.Fatal(err)
30+
}
31+
32+
if err := validateNetDevSNMP6(netDevSNMP6); err != nil {
33+
t.Error(err.Error())
34+
}
35+
}
36+
37+
func TestProcNetDevSNMP6(t *testing.T) {
38+
p, err := getProcFixtures(t).Proc(26231)
39+
if err != nil {
40+
t.Fatal(err)
41+
}
42+
43+
procNetDevSNMP6, err := p.NetDevSNMP6()
44+
if err != nil {
45+
t.Fatal(err)
46+
}
47+
48+
if err := validateNetDevSNMP6(procNetDevSNMP6); err != nil {
49+
t.Error(err.Error())
50+
}
51+
}
52+
53+
func validateNetDevSNMP6(have NetDevSNMP6) error {
54+
var wantNetDevSNMP6 = map[string]map[string]uint64{
55+
"eth0": {
56+
"ifIndex": 1,
57+
"Ip6InOctets": 14064059261,
58+
"Ip6OutOctets": 811213622,
59+
"Icmp6InMsgs": 53293,
60+
"Icmp6OutMsgs": 20400,
61+
},
62+
"eth1": {
63+
"ifIndex": 2,
64+
"Ip6InOctets": 303177290674,
65+
"Ip6OutOctets": 29245052746,
66+
"Icmp6InMsgs": 37911,
67+
"Icmp6OutMsgs": 114015,
68+
},
69+
}
70+
71+
for wantIface, wantData := range wantNetDevSNMP6 {
72+
if haveData, ok := have[wantIface]; ok {
73+
for wantStat, wantVal := range wantData {
74+
if haveVal, ok := haveData[wantStat]; !ok {
75+
return fmt.Errorf("stat %s missing from %s test data", wantStat, wantIface)
76+
} else if wantVal != haveVal {
77+
return fmt.Errorf("%s - %s: want %d, have %d", wantIface, wantStat, wantVal, haveVal)
78+
}
79+
}
80+
} else {
81+
return fmt.Errorf("%s not found in test data", wantIface)
82+
}
83+
}
84+
85+
return nil
86+
}

0 commit comments

Comments
 (0)