blob: 42e7344b27604d6c71bbd4f86cf1adbf2971a4a3 [file]
// 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"
"strings"
"github.com/prometheus/procfs/internal/util"
)
// PciPowerState represents the power state of a PCI device.
type PciPowerState string
const (
PciPowerStateUnknown PciPowerState = "unknown"
PciPowerStateError PciPowerState = "error"
PciPowerStateD0 PciPowerState = "D0"
PciPowerStateD1 PciPowerState = "D1"
PciPowerStateD2 PciPowerState = "D2"
PciPowerStateD3Hot PciPowerState = "D3hot"
PciPowerStateD3Cold PciPowerState = "D3cold"
)
// String returns the string representation of the power state.
func (p PciPowerState) String() string {
return string(p)
}
const pciDevicesPath = "bus/pci/devices"
// PciDeviceLocation represents the location of the device attached.
// "0000:00:00.0" represents Segment:Bus:Device.Function .
type PciDeviceLocation struct {
Segment int
Bus int
Device int
Function int
}
func (pdl PciDeviceLocation) String() string {
return fmt.Sprintf("%04x:%02x:%02x:%x", pdl.Segment, pdl.Bus, pdl.Device, pdl.Function)
}
func (pdl PciDeviceLocation) Strings() []string {
return []string{
fmt.Sprintf("%04x", pdl.Segment),
fmt.Sprintf("%02x", pdl.Bus),
fmt.Sprintf("%02x", pdl.Device),
fmt.Sprintf("%x", pdl.Function),
}
}
// PciDevice contains info from files in /sys/bus/pci/devices for a
// single PCI device.
type PciDevice struct {
Location PciDeviceLocation
ParentLocation *PciDeviceLocation
Class uint32 // /sys/bus/pci/devices/<Location>/class
Vendor uint32 // /sys/bus/pci/devices/<Location>/vendor
Device uint32 // /sys/bus/pci/devices/<Location>/device
SubsystemVendor uint32 // /sys/bus/pci/devices/<Location>/subsystem_vendor
SubsystemDevice uint32 // /sys/bus/pci/devices/<Location>/subsystem_device
Revision uint32 // /sys/bus/pci/devices/<Location>/revision
NumaNode *int32 // /sys/bus/pci/devices/<Location>/numa_node
MaxLinkSpeed *float64 // /sys/bus/pci/devices/<Location>/max_link_speed
MaxLinkWidth *float64 // /sys/bus/pci/devices/<Location>/max_link_width
CurrentLinkSpeed *float64 // /sys/bus/pci/devices/<Location>/current_link_speed
CurrentLinkWidth *float64 // /sys/bus/pci/devices/<Location>/current_link_width
SriovDriversAutoprobe *bool // /sys/bus/pci/devices/<Location>/sriov_drivers_autoprobe
SriovNumvfs *uint32 // /sys/bus/pci/devices/<Location>/sriov_numvfs
SriovOffset *uint32 // /sys/bus/pci/devices/<Location>/sriov_offset
SriovStride *uint32 // /sys/bus/pci/devices/<Location>/sriov_stride
SriovTotalvfs *uint32 // /sys/bus/pci/devices/<Location>/sriov_totalvfs
SriovVfDevice *uint32 // /sys/bus/pci/devices/<Location>/sriov_vf_device
SriovVfTotalMsix *uint64 // /sys/bus/pci/devices/<Location>/sriov_vf_total_msix
D3coldAllowed *bool // /sys/bus/pci/devices/<Location>/d3cold_allowed
PowerState *PciPowerState // /sys/bus/pci/devices/<Location>/power_state
}
func (pd PciDevice) Name() string {
return pd.Location.String()
}
// PciDevices is a collection of every PCI device in
// /sys/bus/pci/devices .
//
// The map keys are the location of PCI devices.
type PciDevices map[string]PciDevice
// PciDevices returns info for all PCI devices read from
// /sys/bus/pci/devices .
func (fs FS) PciDevices() (PciDevices, error) {
path := fs.sys.Path(pciDevicesPath)
dirs, err := os.ReadDir(path)
if err != nil {
return nil, err
}
pciDevs := make(PciDevices, len(dirs))
for _, d := range dirs {
device, err := fs.parsePciDevice(d.Name())
if err != nil {
return nil, err
}
pciDevs[device.Name()] = *device
}
return pciDevs, nil
}
func parsePciDeviceLocation(loc string) (*PciDeviceLocation, error) {
locs := strings.Split(loc, ":")
if len(locs) != 3 {
return nil, fmt.Errorf("invalid location '%s'", loc)
}
locs = append(locs[0:2], strings.Split(locs[2], ".")...)
if len(locs) != 4 {
return nil, fmt.Errorf("invalid location '%s'", loc)
}
seg, err := strconv.ParseInt(locs[0], 16, 32)
if err != nil {
return nil, fmt.Errorf("invalid segment: %w", err)
}
bus, err := strconv.ParseInt(locs[1], 16, 32)
if err != nil {
return nil, fmt.Errorf("invalid bus: %w", err)
}
device, err := strconv.ParseInt(locs[2], 16, 32)
if err != nil {
return nil, fmt.Errorf("invalid device: %w", err)
}
function, err := strconv.ParseInt(locs[3], 16, 32)
if err != nil {
return nil, fmt.Errorf("invalid function: %w", err)
}
return &PciDeviceLocation{
Segment: int(seg),
Bus: int(bus),
Device: int(device),
Function: int(function),
}, nil
}
// Parse one PCI device
// Refer to https://docs.kernel.org/PCI/sysfs-pci.html
func (fs FS) parsePciDevice(name string) (*PciDevice, error) {
path := fs.sys.Path(pciDevicesPath, name)
// the file must be symbolic link.
realPath, err := os.Readlink(path)
if err != nil {
return nil, fmt.Errorf("failed to readlink: %w", err)
}
// parse device location from realpath
// like "../../../devices/pci0000:00/0000:00:02.5/0000:04:00.0"
deviceLocStr := filepath.Base(realPath)
parentDeviceLocStr := filepath.Base(filepath.Dir(realPath))
deviceLoc, err := parsePciDeviceLocation(deviceLocStr)
if err != nil {
return nil, fmt.Errorf("failed to parse device location:%q %w", deviceLoc, err)
}
// the parent device may have "pci" prefix.
// this is not pci device like bridges.
// we ignore such location to avoid confusion.
// TODO: is it really ok?
var parentDeviceLoc *PciDeviceLocation
if !strings.HasPrefix(parentDeviceLocStr, "pci") {
parentDeviceLoc, err = parsePciDeviceLocation(parentDeviceLocStr)
if err != nil {
return nil, fmt.Errorf("failed to parse parent device location %q: %w", parentDeviceLocStr, err)
}
}
device := &PciDevice{
Location: *deviceLoc,
ParentLocation: parentDeviceLoc,
}
// These files must exist in a device directory.
for _, f := range [...]string{"class", "vendor", "device", "subsystem_vendor", "subsystem_device", "revision"} {
name := filepath.Join(path, f)
valueStr, err := util.SysReadFile(name)
if err != nil {
return nil, fmt.Errorf("failed to read file %q: %w", name, err)
}
value, err := strconv.ParseInt(valueStr, 0, 32)
if err != nil {
return nil, fmt.Errorf("failed to parse %s %q %s: %w", f, valueStr, device.Location, err)
}
switch f {
case "class":
device.Class = uint32(value)
case "vendor":
device.Vendor = uint32(value)
case "device":
device.Device = uint32(value)
case "subsystem_vendor":
device.SubsystemVendor = uint32(value)
case "subsystem_device":
device.SubsystemDevice = uint32(value)
case "revision":
device.Revision = uint32(value)
default:
return nil, fmt.Errorf("unknown file %q", f)
}
}
for _, f := range [...]string{"max_link_speed", "max_link_width", "current_link_speed", "current_link_width", "numa_node"} {
name := filepath.Join(path, f)
valueStr, err := util.SysReadFile(name)
if err != nil {
if os.IsNotExist(err) {
continue
}
return nil, fmt.Errorf("failed to read file %q: %w", name, err)
}
// Some devices may be NULL or contain 'Unknown' as a value
// values defined in drivers/pci/probe.c pci_speed_string
if valueStr == "" || strings.HasPrefix(valueStr, "Unknown") {
continue
}
switch f {
case "max_link_speed", "current_link_speed":
// example "8.0 GT/s PCIe"
values := strings.SplitAfterN(valueStr, " ", 2)
if len(values) != 2 {
return nil, fmt.Errorf("invalid value for %s %q %s", f, valueStr, device.Location)
}
if values[1] != "GT/s PCIe" {
return nil, fmt.Errorf("unknown unit for %s %q %s", f, valueStr, device.Location)
}
value, err := strconv.ParseFloat(strings.TrimSpace(values[0]), 64)
if err != nil {
return nil, fmt.Errorf("failed to parse %s %q %s: %w", f, valueStr, device.Location, err)
}
v := float64(value)
switch f {
case "max_link_speed":
device.MaxLinkSpeed = &v
case "current_link_speed":
device.CurrentLinkSpeed = &v
}
case "max_link_width", "current_link_width":
value, err := strconv.ParseInt(valueStr, 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse %s %q %s: %w", f, valueStr, device.Location, err)
}
v := float64(value)
switch f {
case "max_link_width":
device.MaxLinkWidth = &v
case "current_link_width":
device.CurrentLinkWidth = &v
}
case "numa_node":
value, err := strconv.ParseInt(valueStr, 10, 32)
if err != nil {
return nil, fmt.Errorf("failed to parse %s %q %s: %w", f, valueStr, device.Location, err)
}
v := int32(value)
device.NumaNode = &v
}
}
// Parse SR-IOV files (these are optional and may not exist for all devices)
for _, f := range [...]string{"sriov_drivers_autoprobe", "sriov_numvfs", "sriov_offset", "sriov_stride", "sriov_totalvfs", "sriov_vf_device", "sriov_vf_total_msix"} {
name := filepath.Join(path, f)
valueStr, err := util.SysReadFile(name)
if err != nil {
if os.IsNotExist(err) {
continue // SR-IOV files are optional
}
return nil, fmt.Errorf("failed to read SR-IOV file %q %s: %w", name, device.Location, err)
}
valueStr = strings.TrimSpace(valueStr)
if valueStr == "" {
continue
}
switch f {
case "sriov_drivers_autoprobe":
// sriov_drivers_autoprobe is a boolean (0 or 1)
value, err := strconv.ParseInt(valueStr, 10, 32)
if err != nil {
return nil, fmt.Errorf("failed to parse SR-IOV drivers autoprobe %q %s: %w", valueStr, device.Location, err)
}
v := value != 0
device.SriovDriversAutoprobe = &v
case "sriov_numvfs":
value, err := strconv.ParseUint(valueStr, 10, 32)
if err != nil {
return nil, fmt.Errorf("failed to parse SR-IOV numvfs %q %s: %w", valueStr, device.Location, err)
}
v := uint32(value)
device.SriovNumvfs = &v
case "sriov_offset":
value, err := strconv.ParseUint(valueStr, 10, 32)
if err != nil {
return nil, fmt.Errorf("failed to parse SR-IOV offset %q %s: %w", valueStr, device.Location, err)
}
v := uint32(value)
device.SriovOffset = &v
case "sriov_stride":
value, err := strconv.ParseUint(valueStr, 10, 32)
if err != nil {
return nil, fmt.Errorf("failed to parse SR-IOV stride %q %s: %w", valueStr, device.Location, err)
}
v := uint32(value)
device.SriovStride = &v
case "sriov_totalvfs":
value, err := strconv.ParseUint(valueStr, 10, 32)
if err != nil {
return nil, fmt.Errorf("failed to parse SR-IOV totalvfs %q %s: %w", valueStr, device.Location, err)
}
v := uint32(value)
device.SriovTotalvfs = &v
case "sriov_vf_device":
value, err := strconv.ParseUint(valueStr, 16, 32)
if err != nil {
return nil, fmt.Errorf("failed to parse SR-IOV vf device %q %s: %w", valueStr, device.Location, err)
}
v := uint32(value)
device.SriovVfDevice = &v
case "sriov_vf_total_msix":
value, err := strconv.ParseUint(valueStr, 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse SR-IOV vf total msix %q %s: %w", valueStr, device.Location, err)
}
v := uint64(value)
device.SriovVfTotalMsix = &v
}
}
// Parse power management files (these are optional and may not exist for all devices)
for _, f := range [...]string{"d3cold_allowed", "power_state"} {
name := filepath.Join(path, f)
valueStr, err := util.SysReadFile(name)
if err != nil {
if os.IsNotExist(err) {
continue // Power management files are optional
}
return nil, fmt.Errorf("failed to read power management file %q %s: %w", name, device.Location, err)
}
valueStr = strings.TrimSpace(valueStr)
if valueStr == "" {
continue
}
switch f {
case "d3cold_allowed":
// d3cold_allowed is a boolean (0 or 1)
value, err := strconv.ParseInt(valueStr, 10, 32)
if err != nil {
return nil, fmt.Errorf("failed to parse d3cold_allowed boolean %q %s: %w", valueStr, device.Location, err)
}
v := value != 0
device.D3coldAllowed = &v
case "power_state":
// power_state is a string (one of: "unknown", "error", "D0", "D1", "D2", "D3hot", "D3cold")
powerState := PciPowerState(valueStr)
device.PowerState = &powerState
}
}
return device, nil
}