diff --git a/sysfs/net_class_aer.go b/sysfs/net_class_aer.go new file mode 100644 index 00000000..46dc5e2f --- /dev/null +++ b/sysfs/net_class_aer.go @@ -0,0 +1,255 @@ +// Copyright 2024 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 linux +// +build linux + +package sysfs + +import ( + "fmt" + "path/filepath" + "strconv" + "strings" + + "github.com/prometheus/procfs/internal/util" +) + +// CorrectableAerCounters contains values from /sys/class/net//device/aer_dev_correctable +// for single interface (iface). +type CorrectableAerCounters struct { + RxErr uint64 + BadTLP uint64 + BadDLLP uint64 + Rollover uint64 + Timeout uint64 + NonFatalErr uint64 + CorrIntErr uint64 + HeaderOF uint64 +} + +// NonCorrectableAerCounters contains values from /sys/class/net//device/aer_dev_[non]fatal +// for single interface (iface). +type NonCorrectableAerCounters struct { + Undefined uint64 + DLP uint64 + SDES uint64 + TLP uint64 + FCP uint64 + CmpltTO uint64 + CmpltAbrt uint64 + UnxCmplt uint64 + RxOF uint64 + MalfTLP uint64 + ECRC uint64 + UnsupReq uint64 + ACSViol uint64 + UncorrIntErr uint64 + BlockedTLP uint64 + AtomicOpBlocked uint64 + TLPBlockedErr uint64 + PoisonTLPBlocked uint64 +} + +// AerCounters contains AER counters from files in /sys/class/net//device +// for single interface (iface). +type AerCounters struct { + Name string // Interface name + Correctable CorrectableAerCounters + Fatal NonCorrectableAerCounters + NonFatal NonCorrectableAerCounters +} + +// AllAerCounters is collection of AER counters for every interface (iface) in /sys/class/net. +// The map keys are interface (iface) names. +type AllAerCounters map[string]AerCounters + +// AerCounters returns info for a single net interfaces (iface). +func (fs FS) AerCountersByIface(devicePath string) (*AerCounters, error) { + _, err := fs.NetClassByIface(devicePath) + if err != nil { + return nil, err + } + + path := fs.sys.Path(netclassPath) + counters, err := parseAerCounters(filepath.Join(path, devicePath)) + if err != nil { + return nil, err + } + counters.Name = devicePath + + return counters, nil +} + +// AerCounters returns AER counters for all net interfaces (iface) read from /sys/class/net//device. +func (fs FS) AerCounters() (AllAerCounters, error) { + devices, err := fs.NetClassDevices() + if err != nil { + return nil, err + } + + path := fs.sys.Path(netclassPath) + allAerCounters := AllAerCounters{} + for _, devicePath := range devices { + counters, err := parseAerCounters(filepath.Join(path, devicePath)) + if err != nil { + return nil, err + } + counters.Name = devicePath + allAerCounters[devicePath] = *counters + } + + return allAerCounters, nil +} + +// parseAerCounters scans predefined files in /sys/class/net//device +// directory and gets their contents. +func parseAerCounters(devicePath string) (*AerCounters, error) { + counters := AerCounters{} + err := parseCorrectableAerCounters(devicePath, &counters.Correctable) + if err != nil { + return nil, err + } + err = parseUncorrectableAerCounters(devicePath, "fatal", &counters.Fatal) + if err != nil { + return nil, err + } + err = parseUncorrectableAerCounters(devicePath, "nonfatal", &counters.NonFatal) + if err != nil { + return nil, err + } + return &counters, nil +} + +// parseCorrectableAerCounters parses correctable error counters in +// /sys/class/net//device/aer_dev_correctable. +func parseCorrectableAerCounters(devicePath string, counters *CorrectableAerCounters) error { + path := filepath.Join(devicePath, "device", "aer_dev_correctable") + value, err := util.SysReadFile(path) + if err != nil { + if canIgnoreError(err) { + return nil + } + return fmt.Errorf("failed to read file %q: %w", path, err) + } + + for _, line := range strings.Split(string(value), "\n") { + if line == "" { + continue + } + fields := strings.Fields(line) + if len(fields) != 2 { + return fmt.Errorf("unexpected number of fields: %v", fields) + } + counterName := fields[0] + value, err := strconv.ParseUint(fields[1], 10, 64) + if err != nil { + return fmt.Errorf("error parsing value for %s: %v", counterName, err) + } + + switch counterName { + case "RxErr": + counters.RxErr = value + case "BadTLP": + counters.BadTLP = value + case "BadDLLP": + counters.BadDLLP = value + case "Rollover": + counters.Rollover = value + case "Timeout": + counters.Timeout = value + case "NonFatalErr": + counters.NonFatalErr = value + case "CorrIntErr": + counters.CorrIntErr = value + case "HeaderOF": + counters.HeaderOF = value + default: + continue + } + } + + return nil +} + +// parseUncorrectableAerCounters parses uncorrectable error counters in +// /sys/class/net//device/aer_dev_[non]fatal. +func parseUncorrectableAerCounters(devicePath string, counterType string, + counters *NonCorrectableAerCounters) error { + path := filepath.Join(devicePath, "device", "aer_dev_"+counterType) + value, err := util.ReadFileNoStat(path) + if err != nil { + if canIgnoreError(err) { + return nil + } + return fmt.Errorf("failed to read file %q: %w", path, err) + } + + for _, line := range strings.Split(string(value), "\n") { + if line == "" { + continue + } + fields := strings.Fields(line) + if len(fields) != 2 { + return fmt.Errorf("unexpected number of fields: %v", fields) + } + counterName := fields[0] + value, err := strconv.ParseUint(fields[1], 10, 64) + if err != nil { + return fmt.Errorf("error parsing value for %s: %v", counterName, err) + } + + switch counterName { + case "Undefined": + counters.Undefined = value + case "DLP": + counters.DLP = value + case "SDES": + counters.SDES = value + case "TLP": + counters.TLP = value + case "FCP": + counters.FCP = value + case "CmpltTO": + counters.CmpltTO = value + case "CmpltAbrt": + counters.CmpltAbrt = value + case "UnxCmplt": + counters.UnxCmplt = value + case "RxOF": + counters.RxOF = value + case "MalfTLP": + counters.MalfTLP = value + case "ECRC": + counters.ECRC = value + case "UnsupReq": + counters.UnsupReq = value + case "ACSViol": + counters.ACSViol = value + case "UncorrIntErr": + counters.UncorrIntErr = value + case "BlockedTLP": + counters.BlockedTLP = value + case "AtomicOpBlocked": + counters.AtomicOpBlocked = value + case "TLPBlockedErr": + counters.TLPBlockedErr = value + case "PoisonTLPBlocked": + counters.PoisonTLPBlocked = value + default: + continue + } + } + + return nil +} diff --git a/sysfs/net_class_aer_test.go b/sysfs/net_class_aer_test.go new file mode 100644 index 00000000..239b6e20 --- /dev/null +++ b/sysfs/net_class_aer_test.go @@ -0,0 +1,98 @@ +// Copyright (c) 2024 Arista Networks, Inc. All rights reserved. +// Arista Networks, Inc. Confidential and Proprietary. + +package sysfs + +import ( + "reflect" + "testing" +) + +func TestAerCountersByIface(t *testing.T) { + fs, err := NewFS(sysTestFixtures) + if err != nil { + t.Fatal(err) + } + + _, err = fs.AerCountersByIface("non-existent") + if err == nil { + t.Fatal("expected error, have none") + } + + device, err := fs.AerCountersByIface("eth0") + if err != nil { + t.Fatal(err) + } + + if device.Name != "eth0" { + t.Errorf("Found unexpected device, want %s, have %s", "eth0", device.Name) + } +} + +func TestAerCounters(t *testing.T) { + fs, err := NewFS(sysTestFixtures) + if err != nil { + t.Fatal(err) + } + + ac, _ := fs.AerCounters() + aerCounters := AllAerCounters{ + "eth0": AerCounters{ + Name: "eth0", + Correctable: CorrectableAerCounters{ + RxErr: 1, + BadTLP: 2, + BadDLLP: 3, + Rollover: 4, + Timeout: 5, + NonFatalErr: 6, + CorrIntErr: 7, + HeaderOF: 8, + }, + Fatal: NonCorrectableAerCounters{ + Undefined: 10, + DLP: 11, + SDES: 12, + TLP: 13, + FCP: 14, + CmpltTO: 15, + CmpltAbrt: 16, + UnxCmplt: 17, + RxOF: 18, + MalfTLP: 19, + ECRC: 20, + UnsupReq: 21, + ACSViol: 22, + UncorrIntErr: 23, + BlockedTLP: 24, + AtomicOpBlocked: 25, + TLPBlockedErr: 26, + PoisonTLPBlocked: 27, + }, + NonFatal: NonCorrectableAerCounters{ + Undefined: 30, + DLP: 31, + SDES: 32, + TLP: 33, + FCP: 34, + CmpltTO: 35, + CmpltAbrt: 36, + UnxCmplt: 37, + RxOF: 38, + MalfTLP: 39, + ECRC: 40, + UnsupReq: 41, + ACSViol: 42, + UncorrIntErr: 43, + BlockedTLP: 44, + AtomicOpBlocked: 45, + TLPBlockedErr: 46, + PoisonTLPBlocked: 47, + }, + }, + } + + if !reflect.DeepEqual(aerCounters, ac) { + t.Errorf("Result not correct: want %v, have %v", aerCounters, ac) + } +} diff --git a/testdata/fixtures.ttar b/testdata/fixtures.ttar index 07137af8..325d922a 100644 --- a/testdata/fixtures.ttar +++ b/testdata/fixtures.ttar @@ -5841,6 +5841,65 @@ Mode: 644 Path: fixtures/sys/class/net/eth0/device SymlinkTo: ../../../devices/pci0000:00/0000:00:1f.6/ # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/sys/class/net/eth0/device/aer_dev_correctable +Lines: 9 +RxErr 1 +BadTLP 2 +BadDLLP 3 +Rollover 4 +Timeout 5 +NonFatalErr 6 +CorrIntErr 7 +HeaderOF 8 +TOTAL_ERR_COR 9 +Mode: 444 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/sys/class/net/eth0/device/aer_dev_fatal +Lines: 19 +Undefined 10 +DLP 11 +SDES 12 +TLP 13 +FCP 14 +CmpltTO 15 +CmpltAbrt 16 +UnxCmplt 17 +RxOF 18 +MalfTLP 19 +ECRC 20 +UnsupReq 21 +ACSViol 22 +UncorrIntErr 23 +BlockedTLP 24 +AtomicOpBlocked 25 +TLPBlockedErr 26 +PoisonTLPBlocked 27 +TOTAL_ERR_FATAL 28 +Mode: 444 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/sys/class/net/eth0/device/aer_dev_nonfatal +Lines: 19 +Undefined 30 +DLP 31 +SDES 32 +TLP 33 +FCP 34 +CmpltTO 35 +CmpltAbrt 36 +UnxCmplt 37 +RxOF 38 +MalfTLP 39 +ECRC 40 +UnsupReq 41 +ACSViol 42 +UncorrIntErr 43 +BlockedTLP 44 +AtomicOpBlocked 45 +TLPBlockedErr 46 +PoisonTLPBlocked 47 +TOTAL_ERR_NONFATAL 48 +Mode: 444 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/sys/class/net/eth0/dormant Lines: 1 1