blob: 69e6cfd1255c5b15a6dbb8d9aae80e56c89e0e01 [file] [edit]
// 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 linux
package sysfs
import (
"fmt"
"os"
"path/filepath"
"strconv"
"github.com/prometheus/procfs/internal/util"
)
// Documentation of the sysfs path
// https://docs.nvidia.com/networking/display/mlnxofedv571020/explicit+congestion+notification+(ecn)
// https://enterprise-support.nvidia.com/s/article/dcqcn-parameters
// Ecn contains values from /sys/class/net/<iface>/ecn/roce_np/
// for single interface (iface).
type RoceNpEcn struct {
// A map from a priority to it's enabled status
Ecn map[uint8]bool
// Minimum time between sending CNPs from the port, in microseconds.
// Range: 0-4095, Default: 4
MinTimeBetweenCnps uint64
// The DSCP value for CNPs.
// Range: 0-63, Default: 48
CnpDscp uint64
// The PCP value for CNPs.
// Range: 0-7, Default: 6
Cnp802pPriority uint64
}
// Ecn contains values from /sys/class/net/<iface>/ecn/roce_rp/
// for single interface (iface).
type RoceRpEcn struct {
// A map from a priority to it's enabled status
Ecn map[uint8]bool
// Alpha Update
// Every Alpha Update Period alpha is updated.
// If the CNP is received during this period, alpha is incremented.
// Otherwise, it is decremented.
// Range: 0-1023, Default: 1019
DceTCPG uint64
// The Alpha Update Period used in the formula for DceTCPG. Unit is microseconds.
// Range: 1-131071, Default: 1
DceTCPRtt uint64
// This parameter sets the initial value of alpha that should be used when receiving
// the first CNP for a flow. Fixed point with 10 bits in the fraction part.
// Range: 1-1023, Default: 1023
InitialAlphaValue uint64
// Rate Decrease
// Rates (current, target) on first CNP (0 – 85% of line rate) in Mbps.
// Range: 0, 1-line rate, Default: 0
RateToSetOnFirstCnp uint64
// This parameter defines the maximal ratio of rate decrease in a single event.
// Range: 0-100, Default: 50
RpgMinDecFac uint64
// This parameter defines the minimal rate limit of the QP in Mbps.
// Range: 1-line rate, Default: 1
RpgMinRate uint64
// The coefficient between alpha and the rate reduction factor.
// Range: 10-11, Default: 11
RpgGd uint64
// The time period between rate reductions in microseconds.
// Range: 0-UINT32, Default: 4
RateReduceMonitorPeriod uint64
// Rate Increase
// If set, every rate decreases. The target rate is updated to the current rate.
// Otherwise, the target rate is updated to the current rate only on the first
// decrement after the increment event.
ClampTgtRate bool
// The time period between rate increase events in microseconds.
// Range: 1-131071, Default: 300
RpgTimeReset uint64
// The sent bytes counter between rate increase events.
// Range: 1-32767, Default: 32767
RpgByteReset uint64
// The threshold of rate increase events for moving to next rate increase phase.
// Range: 1-31, Default: 1
RpgThreshold uint64
// The rate increase value in the Additive Increase phase in Mbps.
// Range: 1-line rate, Default: 5
RpgAiRate uint64
// The rate increase value in the Hyper Increase phase in Mbps.
// Range: 1-line rate, Default: 1
RpgHaiRate uint64
}
// EcnIface contains Ecn info from files in /sys/class/net/<iface>/ecn/
// for single interface (iface).
type EcnIface struct {
Name string // Interface name
// protocols
RoceNpEcn RoceNpEcn // Notification point
RoceRpEcn RoceRpEcn // Reaction point
}
// AllEcnIface is collection of Ecn info for every interface (iface) in /sys/class/net.
// The map keys are interface (iface) names.
type AllEcnIface map[string]EcnIface
// EcnByIface returns info for a single net interfaces (iface).
func (fs FS) EcnByIface(devicePath string) (*EcnIface, error) {
_, err := fs.NetClassByIface(devicePath)
if err != nil {
return nil, err
}
path := fs.sys.Path(netclassPath)
ecnPath := filepath.Join(path, devicePath, "ecn")
validPath, err := PathExistsAndIsDir(ecnPath)
if err != nil {
return nil, err
}
if !validPath {
// this device doesn't have ECN values at this path
return nil, fmt.Errorf("does not have ECN values: %q", devicePath)
}
ecnIface, err := ParseEcnIfaceInfo(ecnPath)
if err != nil {
return nil, err
}
ecnIface.Name = devicePath
return ecnIface, nil
}
// EcnDevices returns EcnIface for all net interfaces (iface) read from /sys/class/net/<iface>/ecn.
func (fs FS) EcnDevices() (AllEcnIface, error) {
devices, err := fs.NetClassDevices()
if err != nil {
return nil, err
}
path := fs.sys.Path(netclassPath)
allEcnIface := AllEcnIface{}
for _, devicePath := range devices {
ecnPath := filepath.Join(path, devicePath, "ecn")
validPath, err := PathExistsAndIsDir(ecnPath)
if err != nil {
return nil, err
}
if !validPath {
// this device doesn't have ECN values at this path
continue
}
ecnIface, err := ParseEcnIfaceInfo(ecnPath)
if err != nil {
return nil, err
}
ecnIface.Name = devicePath
allEcnIface[devicePath] = *ecnIface
}
return allEcnIface, nil
}
// ParseEcnIfaceInfo scans predefined files in /sys/class/net/<iface>/ecn
// directory and gets their contents.
func ParseEcnIfaceInfo(ecnPath string) (*EcnIface, error) {
ecnIface := EcnIface{}
err := ParseRoceNpEcnInfo(filepath.Join(ecnPath, "roce_np"), &ecnIface.RoceNpEcn)
if err != nil {
return nil, err
}
err = ParseRoceRpEcnInfo(filepath.Join(ecnPath, "roce_rp"), &ecnIface.RoceRpEcn)
if err != nil {
return nil, err
}
return &ecnIface, nil
}
// ParseEcnIfaceInfo scans predefined files in /sys/class/net/<iface>/ecn/roce_np/
// directory and gets their contents.
func ParseRoceNpEcnInfo(ecnPath string, ecn *RoceNpEcn) error {
value, err := ParseEcnEnable(filepath.Join(ecnPath, "enable"))
if err != nil {
return err
}
ecn.Ecn = value
files, err := os.ReadDir(ecnPath)
if err != nil {
return err
}
for _, f := range files {
if !f.Type().IsRegular() {
continue
}
if err := ParseRoceNpEcnAttribute(ecnPath, f.Name(), ecn); err != nil {
return err
}
}
return nil
}
// Parses all of the attributes in for ROCE NP protocol.
func ParseRoceNpEcnAttribute(ecnPath string, attrName string, ecn *RoceNpEcn) error {
attrPath := filepath.Join(ecnPath, attrName)
value, err := util.SysReadFile(attrPath)
if err != nil {
if canIgnoreError(err) {
return nil
}
return fmt.Errorf("failed to read file %q: %w", attrPath, err)
}
vp := util.NewValueParser(value)
switch attrName {
case "min_time_between_cnps":
ecn.MinTimeBetweenCnps = *vp.PUInt64()
case "cnp_802p_prio":
ecn.Cnp802pPriority = *vp.PUInt64()
case "cnp_dscp":
ecn.CnpDscp = *vp.PUInt64()
default:
return nil
}
return nil
}
// ParseRoceRpEcnInfo scans predefined files in /sys/class/net/<iface>/ecn/roce_rp/
// directory and gets their contents.
func ParseRoceRpEcnInfo(ecnPath string, ecn *RoceRpEcn) error {
value, err := ParseEcnEnable(filepath.Join(ecnPath, "enable"))
if err != nil {
return err
}
ecn.Ecn = value
files, err := os.ReadDir(ecnPath)
if err != nil {
return err
}
for _, f := range files {
if !f.Type().IsRegular() {
continue
}
if err := ParseRoceRpEcnAttribute(ecnPath, f.Name(), ecn); err != nil {
return err
}
}
return nil
}
// Parses all of the attributes in for ROCE RP protocol.
func ParseRoceRpEcnAttribute(ecnPath string, attrName string, ecn *RoceRpEcn) error {
attrPath := filepath.Join(ecnPath, attrName)
value, err := util.SysReadFile(attrPath)
if err != nil {
if canIgnoreError(err) {
return nil
}
return fmt.Errorf("failed to read file %q: %w", attrPath, err)
}
vp := util.NewValueParser(value)
switch attrName {
case "clamp_tgt_rate":
switch *vp.PUInt64() {
case 0:
ecn.ClampTgtRate = false
case 1:
ecn.ClampTgtRate = true
default:
return fmt.Errorf("failed to parse file %q: %w", attrPath, err)
}
case "dce_tcp_g":
ecn.DceTCPG = *vp.PUInt64()
case "dce_tcp_rtt":
ecn.DceTCPRtt = *vp.PUInt64()
case "initial_alpha_value":
ecn.InitialAlphaValue = *vp.PUInt64()
case "rate_reduce_monitor_period":
ecn.RateReduceMonitorPeriod = *vp.PUInt64()
case "rate_to_set_on_first_cnp":
ecn.RateToSetOnFirstCnp = *vp.PUInt64()
case "rpg_ai_rate":
ecn.RpgAiRate = *vp.PUInt64()
case "rpg_byte_reset":
ecn.RpgByteReset = *vp.PUInt64()
case "rpg_gd":
ecn.RpgGd = *vp.PUInt64()
case "rpg_hai_rate":
ecn.RpgHaiRate = *vp.PUInt64()
case "rpg_min_dec_fac":
ecn.RpgMinDecFac = *vp.PUInt64()
case "rpg_min_rate":
ecn.RpgMinRate = *vp.PUInt64()
case "rpg_threshold":
ecn.RpgThreshold = *vp.PUInt64()
case "rpg_time_reset":
ecn.RpgTimeReset = *vp.PUInt64()
default:
return nil
}
return nil
}
// parses the ECN enable directory. It takes a path which should be a directory.
// This directory should have filenames that are uint8 and the content of the file is
// either 0 or 1.
func ParseEcnEnable(path string) (map[uint8]bool, error) {
// Read the files in the directory
files, err := os.ReadDir(path)
if err != nil {
return nil, err
}
ecn := make(map[uint8]bool)
// Iterate through each file in the directory
for _, file := range files {
// Only process files (skip directories)
if file.IsDir() {
continue
}
// Extract the file name (which should be the integer key)
filename := file.Name()
// Attempt to convert the file name to an integer
filenameInt, err := strconv.ParseUint(filename, 10, 8)
if err != nil {
// Skip the file if the name cannot be converted to an integer
continue
}
value, err := util.SysReadFile(filepath.Join(path, filename))
if err != nil {
if canIgnoreError(err) {
return nil, err
}
return nil, fmt.Errorf("failed to read file %q: %w", filename, err)
}
vp := util.NewValueParser(value)
fileValue := *vp.PUInt64()
switch fileValue {
case 0:
ecn[uint8(filenameInt)] = false
case 1:
ecn[uint8(filenameInt)] = true
default:
return nil, fmt.Errorf("failed to parse file %q: %q", filename, value)
}
}
return ecn, nil
}
// Utility function that given a path will return if the path is a dir or not.
func PathExistsAndIsDir(path string) (bool, error) {
info, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return false, nil // Path does not exist
}
return false, err // Some other error occurred
}
return info.IsDir(), nil // Check if the path is a directory
}