|
| 1 | +// Copyright 2024 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 | +//go:build linux |
| 15 | +// +build linux |
| 16 | + |
| 17 | +package sysfs |
| 18 | + |
| 19 | +import ( |
| 20 | + "fmt" |
| 21 | + "path/filepath" |
| 22 | + "strconv" |
| 23 | + "strings" |
| 24 | + |
| 25 | + "github.com/prometheus/procfs/internal/util" |
| 26 | +) |
| 27 | + |
| 28 | +// CorrectableAerCounters contains values from /sys/class/net/<iface>/device/aer_dev_correctable |
| 29 | +// for single interface (iface). |
| 30 | +type CorrectableAerCounters struct { |
| 31 | + RxErr uint64 |
| 32 | + BadTLP uint64 |
| 33 | + BadDLLP uint64 |
| 34 | + Rollover uint64 |
| 35 | + Timeout uint64 |
| 36 | + NonFatalErr uint64 |
| 37 | + CorrIntErr uint64 |
| 38 | + HeaderOF uint64 |
| 39 | +} |
| 40 | + |
| 41 | +// UncorrectableAerCounters contains values from /sys/class/net/<iface>/device/aer_dev_[non]fatal |
| 42 | +// for single interface (iface). |
| 43 | +type UncorrectableAerCounters struct { |
| 44 | + Undefined uint64 |
| 45 | + DLP uint64 |
| 46 | + SDES uint64 |
| 47 | + TLP uint64 |
| 48 | + FCP uint64 |
| 49 | + CmpltTO uint64 |
| 50 | + CmpltAbrt uint64 |
| 51 | + UnxCmplt uint64 |
| 52 | + RxOF uint64 |
| 53 | + MalfTLP uint64 |
| 54 | + ECRC uint64 |
| 55 | + UnsupReq uint64 |
| 56 | + ACSViol uint64 |
| 57 | + UncorrIntErr uint64 |
| 58 | + BlockedTLP uint64 |
| 59 | + AtomicOpBlocked uint64 |
| 60 | + TLPBlockedErr uint64 |
| 61 | + PoisonTLPBlocked uint64 |
| 62 | +} |
| 63 | + |
| 64 | +// AerCounters contains AER counters from files in /sys/class/net/<iface>/device |
| 65 | +// for single interface (iface). |
| 66 | +type AerCounters struct { |
| 67 | + Name string // Interface name |
| 68 | + Correctable CorrectableAerCounters |
| 69 | + Fatal UncorrectableAerCounters |
| 70 | + NonFatal UncorrectableAerCounters |
| 71 | +} |
| 72 | + |
| 73 | +// AllAerCounters is collection of AER counters for every interface (iface) in /sys/class/net. |
| 74 | +// The map keys are interface (iface) names. |
| 75 | +type AllAerCounters map[string]AerCounters |
| 76 | + |
| 77 | +// AerCounters returns info for a single net interfaces (iface). |
| 78 | +func (fs FS) AerCountersByIface(devicePath string) (*AerCounters, error) { |
| 79 | + _, err := fs.NetClassByIface(devicePath) |
| 80 | + if err != nil { |
| 81 | + return nil, err |
| 82 | + } |
| 83 | + |
| 84 | + path := fs.sys.Path(netclassPath) |
| 85 | + counters, err := parseAerCounters(filepath.Join(path, devicePath)) |
| 86 | + if err != nil { |
| 87 | + return nil, err |
| 88 | + } |
| 89 | + counters.Name = devicePath |
| 90 | + |
| 91 | + return counters, nil |
| 92 | +} |
| 93 | + |
| 94 | +// AerCounters returns AER counters for all net interfaces (iface) read from /sys/class/net/<iface>/device. |
| 95 | +func (fs FS) AerCounters() (AllAerCounters, error) { |
| 96 | + devices, err := fs.NetClassDevices() |
| 97 | + if err != nil { |
| 98 | + return nil, err |
| 99 | + } |
| 100 | + |
| 101 | + path := fs.sys.Path(netclassPath) |
| 102 | + allAerCounters := AllAerCounters{} |
| 103 | + for _, devicePath := range devices { |
| 104 | + counters, err := parseAerCounters(filepath.Join(path, devicePath)) |
| 105 | + if err != nil { |
| 106 | + return nil, err |
| 107 | + } |
| 108 | + counters.Name = devicePath |
| 109 | + allAerCounters[devicePath] = *counters |
| 110 | + } |
| 111 | + |
| 112 | + return allAerCounters, nil |
| 113 | +} |
| 114 | + |
| 115 | +// parseAerCounters scans predefined files in /sys/class/net/<iface>/device |
| 116 | +// directory and gets their contents. |
| 117 | +func parseAerCounters(devicePath string) (*AerCounters, error) { |
| 118 | + counters := AerCounters{} |
| 119 | + err := parseCorrectableAerCounters(devicePath, &counters.Correctable) |
| 120 | + if err != nil { |
| 121 | + return nil, err |
| 122 | + } |
| 123 | + err = parseUncorrectableAerCounters(devicePath, "fatal", &counters.Fatal) |
| 124 | + if err != nil { |
| 125 | + return nil, err |
| 126 | + } |
| 127 | + err = parseUncorrectableAerCounters(devicePath, "nonfatal", &counters.NonFatal) |
| 128 | + if err != nil { |
| 129 | + return nil, err |
| 130 | + } |
| 131 | + return &counters, nil |
| 132 | +} |
| 133 | + |
| 134 | +// parseCorrectableAerCounters parses correctable error counters in |
| 135 | +// /sys/class/net/<iface>/device/aer_dev_correctable. |
| 136 | +func parseCorrectableAerCounters(devicePath string, counters *CorrectableAerCounters) error { |
| 137 | + path := filepath.Join(devicePath, "device", "aer_dev_correctable") |
| 138 | + value, err := util.SysReadFile(path) |
| 139 | + if err != nil { |
| 140 | + if canIgnoreError(err) { |
| 141 | + return nil |
| 142 | + } |
| 143 | + return fmt.Errorf("failed to read file %q: %w", path, err) |
| 144 | + } |
| 145 | + |
| 146 | + for _, line := range strings.Split(string(value), "\n") { |
| 147 | + if line == "" { |
| 148 | + continue |
| 149 | + } |
| 150 | + fields := strings.Fields(line) |
| 151 | + if len(fields) != 2 { |
| 152 | + return fmt.Errorf("unexpected number of fields: %v", fields) |
| 153 | + } |
| 154 | + counterName := fields[0] |
| 155 | + value, err := strconv.ParseUint(fields[1], 10, 64) |
| 156 | + if err != nil { |
| 157 | + return fmt.Errorf("error parsing value for %s: %v", counterName, err) |
| 158 | + } |
| 159 | + |
| 160 | + switch counterName { |
| 161 | + case "RxErr": |
| 162 | + counters.RxErr = value |
| 163 | + case "BadTLP": |
| 164 | + counters.BadTLP = value |
| 165 | + case "BadDLLP": |
| 166 | + counters.BadDLLP = value |
| 167 | + case "Rollover": |
| 168 | + counters.Rollover = value |
| 169 | + case "Timeout": |
| 170 | + counters.Timeout = value |
| 171 | + case "NonFatalErr": |
| 172 | + counters.NonFatalErr = value |
| 173 | + case "CorrIntErr": |
| 174 | + counters.CorrIntErr = value |
| 175 | + case "HeaderOF": |
| 176 | + counters.HeaderOF = value |
| 177 | + default: |
| 178 | + continue |
| 179 | + } |
| 180 | + } |
| 181 | + |
| 182 | + return nil |
| 183 | +} |
| 184 | + |
| 185 | +// parseUncorrectableAerCounters parses uncorrectable error counters in |
| 186 | +// /sys/class/net/<iface>/device/aer_dev_[non]fatal. |
| 187 | +func parseUncorrectableAerCounters(devicePath string, counterType string, |
| 188 | + counters *UncorrectableAerCounters) error { |
| 189 | + path := filepath.Join(devicePath, "device", "aer_dev_"+counterType) |
| 190 | + value, err := util.ReadFileNoStat(path) |
| 191 | + if err != nil { |
| 192 | + if canIgnoreError(err) { |
| 193 | + return nil |
| 194 | + } |
| 195 | + return fmt.Errorf("failed to read file %q: %w", path, err) |
| 196 | + } |
| 197 | + |
| 198 | + for _, line := range strings.Split(string(value), "\n") { |
| 199 | + if line == "" { |
| 200 | + continue |
| 201 | + } |
| 202 | + fields := strings.Fields(line) |
| 203 | + if len(fields) != 2 { |
| 204 | + return fmt.Errorf("unexpected number of fields: %v", fields) |
| 205 | + } |
| 206 | + counterName := fields[0] |
| 207 | + value, err := strconv.ParseUint(fields[1], 10, 64) |
| 208 | + if err != nil { |
| 209 | + return fmt.Errorf("error parsing value for %s: %v", counterName, err) |
| 210 | + } |
| 211 | + |
| 212 | + switch counterName { |
| 213 | + case "Undefined": |
| 214 | + counters.Undefined = value |
| 215 | + case "DLP": |
| 216 | + counters.DLP = value |
| 217 | + case "SDES": |
| 218 | + counters.SDES = value |
| 219 | + case "TLP": |
| 220 | + counters.TLP = value |
| 221 | + case "FCP": |
| 222 | + counters.FCP = value |
| 223 | + case "CmpltTO": |
| 224 | + counters.CmpltTO = value |
| 225 | + case "CmpltAbrt": |
| 226 | + counters.CmpltAbrt = value |
| 227 | + case "UnxCmplt": |
| 228 | + counters.UnxCmplt = value |
| 229 | + case "RxOF": |
| 230 | + counters.RxOF = value |
| 231 | + case "MalfTLP": |
| 232 | + counters.MalfTLP = value |
| 233 | + case "ECRC": |
| 234 | + counters.ECRC = value |
| 235 | + case "UnsupReq": |
| 236 | + counters.UnsupReq = value |
| 237 | + case "ACSViol": |
| 238 | + counters.ACSViol = value |
| 239 | + case "UncorrIntErr": |
| 240 | + counters.UncorrIntErr = value |
| 241 | + case "BlockedTLP": |
| 242 | + counters.BlockedTLP = value |
| 243 | + case "AtomicOpBlocked": |
| 244 | + counters.AtomicOpBlocked = value |
| 245 | + case "TLPBlockedErr": |
| 246 | + counters.TLPBlockedErr = value |
| 247 | + case "PoisonTLPBlocked": |
| 248 | + counters.PoisonTLPBlocked = value |
| 249 | + default: |
| 250 | + continue |
| 251 | + } |
| 252 | + } |
| 253 | + |
| 254 | + return nil |
| 255 | +} |
0 commit comments