Read rx errors from the driver stats array and add the pcs counters
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -327,6 +328,135 @@ func getCoalesce(fd int, ifname string) (ethtoolCoalesce, error) {
|
||||
return ec, err
|
||||
}
|
||||
|
||||
const (
|
||||
ethSSStats = 1
|
||||
ethGstringLen = 32
|
||||
)
|
||||
|
||||
// Each of these has a __u32 or __u8 tail the kernel fills past the end of the
|
||||
// struct, so they are only ever the header on a larger buffer.
|
||||
type ethtoolSsetInfo struct {
|
||||
cmd uint32
|
||||
reserved uint32
|
||||
ssetMask uint64
|
||||
data [1]uint32
|
||||
_ [4]byte
|
||||
}
|
||||
|
||||
type ethtoolGstrings struct {
|
||||
cmd uint32
|
||||
stringSet uint32
|
||||
len uint32
|
||||
}
|
||||
|
||||
type ethtoolStatsHdr struct {
|
||||
cmd uint32
|
||||
nStats uint32
|
||||
}
|
||||
|
||||
// The stats payload is an array of __u64 immediately after the header, so the
|
||||
// buffer is allocated as uint64 to guarantee it lands on an eight byte
|
||||
// boundary. A []byte carries no such guarantee.
|
||||
func statsBuf(n int) []uint64 { return make([]uint64, n) }
|
||||
|
||||
func statCount(fd int, ifname string) (uint32, error) {
|
||||
req := ethtoolSsetInfo{cmd: unix.ETHTOOL_GSSET_INFO, ssetMask: 1 << ethSSStats}
|
||||
if err := ethtoolCall(fd, ifname, unsafe.Pointer(&req)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// A cleared mask bit means the driver does not have that string set at all,
|
||||
// in which case no count was written into the tail.
|
||||
if req.ssetMask == 0 {
|
||||
return 0, fmt.Errorf("driver has no ETH_SS_STATS string set")
|
||||
}
|
||||
return req.data[0], nil
|
||||
}
|
||||
|
||||
func statNames(fd int, ifname string) ([]string, error) {
|
||||
n, err := statCount(fd, ifname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
return nil, fmt.Errorf("driver reports zero statistics")
|
||||
}
|
||||
|
||||
hdrLen := int(unsafe.Sizeof(ethtoolGstrings{}))
|
||||
buf := statsBuf((hdrLen + int(n)*ethGstringLen + 7) / 8)
|
||||
hdr := (*ethtoolGstrings)(unsafe.Pointer(&buf[0]))
|
||||
hdr.cmd = unix.ETHTOOL_GSTRINGS
|
||||
hdr.stringSet = ethSSStats
|
||||
hdr.len = n
|
||||
if err := ethtoolCall(fd, ifname, unsafe.Pointer(&buf[0])); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw := unsafe.Slice((*byte)(unsafe.Pointer(&buf[0])), len(buf)*8)[hdrLen:]
|
||||
names := make([]string, hdr.len)
|
||||
for i := range names {
|
||||
s := raw[i*ethGstringLen : (i+1)*ethGstringLen]
|
||||
if k := bytes.IndexByte(s, 0); k >= 0 {
|
||||
s = s[:k]
|
||||
}
|
||||
names[i] = string(s)
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
func statValues(fd int, ifname string, n uint32, into []uint64) error {
|
||||
hdr := (*ethtoolStatsHdr)(unsafe.Pointer(&into[0]))
|
||||
hdr.cmd = unix.ETHTOOL_GSTATS
|
||||
hdr.nStats = n
|
||||
return ethtoolCall(fd, ifname, unsafe.Pointer(&into[0]))
|
||||
}
|
||||
|
||||
// The ioctl indexes counters by position, and the ordering is a property of the
|
||||
// driver build, so names are resolved to indices once rather than per poll.
|
||||
type statReader struct {
|
||||
fd int
|
||||
ifname string
|
||||
n uint32
|
||||
want []int
|
||||
buf []uint64
|
||||
}
|
||||
|
||||
func newStatReader(fd int, ifname string, want []string) (*statReader, error) {
|
||||
names, err := statNames(fd, ifname)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s statistics: %w", ifname, err)
|
||||
}
|
||||
idx := make(map[string]int, len(names))
|
||||
for i, s := range names {
|
||||
idx[s] = i
|
||||
}
|
||||
r := &statReader{
|
||||
fd: fd,
|
||||
ifname: ifname,
|
||||
n: uint32(len(names)),
|
||||
buf: statsBuf(1 + len(names)),
|
||||
}
|
||||
for _, w := range want {
|
||||
i, ok := idx[w]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s has no statistic %q", ifname, w)
|
||||
}
|
||||
r.want = append(r.want, i)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *statReader) sum() uint64 {
|
||||
if err := statValues(r.fd, r.ifname, r.n, r.buf); err != nil {
|
||||
panic(fmt.Sprintf("reading %s statistics: %v", r.ifname, err))
|
||||
}
|
||||
vals := r.buf[1:]
|
||||
var total uint64
|
||||
for _, i := range r.want {
|
||||
total += vals[i]
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func checkGovernor(want string) checkResult {
|
||||
res := checkResult{item: "cpu governor"}
|
||||
paths, err := filepath.Glob("/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor")
|
||||
|
||||
Reference in New Issue
Block a user