github.com/jaypipes/ghw@v0.21.1/pkg/gpu/gpu_linux.go (about)

     1  // Use and distribution licensed under the Apache license version 2.
     2  //
     3  // See the COPYING file in the root project directory for full text.
     4  //
     5  
     6  package gpu
     7  
     8  import (
     9  	"os"
    10  	"path/filepath"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/jaypipes/ghw/pkg/context"
    16  	"github.com/jaypipes/ghw/pkg/linuxpath"
    17  	"github.com/jaypipes/ghw/pkg/pci"
    18  	"github.com/jaypipes/ghw/pkg/topology"
    19  	"github.com/jaypipes/ghw/pkg/util"
    20  )
    21  
    22  const (
    23  	validPCIAddress = `\b(0{0,4}:[[:xdigit:]]{2}:[[:xdigit:]]{2}\.[[:xdigit:]]:?\w*)`
    24  )
    25  
    26  var reValidPCIAddress = regexp.MustCompile(validPCIAddress)
    27  
    28  const (
    29  	_WARN_NO_SYS_CLASS_DRM = `
    30  /sys/class/drm does not exist on this system (likely the host system is a
    31  virtual machine or container with no graphics). Therefore,
    32  GPUInfo.GraphicsCards will be an empty array.
    33  `
    34  )
    35  
    36  func (i *Info) load() error {
    37  	// In Linux, each graphics card is listed under the /sys/class/drm
    38  	// directory as a symbolic link named "cardN", where N is a zero-based
    39  	// index of the card in the system. "DRM" stands for Direct Rendering
    40  	// Manager and is the Linux subsystem that is responsible for graphics I/O
    41  	//
    42  	// Each card may have multiple symbolic
    43  	// links in this directory representing the interfaces from the graphics
    44  	// card over a particular wire protocol (HDMI, DisplayPort, etc). These
    45  	// symbolic links are named cardN-<INTERFACE_TYPE>-<DISPLAY_ID>. For
    46  	// instance, on one of my local workstations with an NVIDIA GTX 1050ti
    47  	// graphics card with one HDMI, one DisplayPort, and one DVI interface to
    48  	// the card, I see the following in /sys/class/drm:
    49  	//
    50  	// $ ll /sys/class/drm/
    51  	// total 0
    52  	// drwxr-xr-x  2 root root    0 Jul 16 11:50 ./
    53  	// drwxr-xr-x 75 root root    0 Jul 16 11:50 ../
    54  	// lrwxrwxrwx  1 root root    0 Jul 16 11:50 card0 -> ../../devices/pci0000:00/0000:00:03.0/0000:03:00.0/drm/card0/
    55  	// lrwxrwxrwx  1 root root    0 Jul 16 11:50 card0-DP-1 -> ../../devices/pci0000:00/0000:00:03.0/0000:03:00.0/drm/card0/card0-DP-1/
    56  	// lrwxrwxrwx  1 root root    0 Jul 16 11:50 card0-DVI-D-1 -> ../../devices/pci0000:00/0000:00:03.0/0000:03:00.0/drm/card0/card0-DVI-D-1/
    57  	// lrwxrwxrwx  1 root root    0 Jul 16 11:50 card0-HDMI-A-1 -> ../../devices/pci0000:00/0000:00:03.0/0000:03:00.0/drm/card0/card0-HDMI-A-1/
    58  	//
    59  	// In this routine, we are only interested in the first link (card0), which
    60  	// we follow to gather information about the actual device from the PCI
    61  	// subsystem (we query the modalias file of the PCI device's sysfs
    62  	// directory using the `ghw.PCIInfo.GetDevice()` function.
    63  	paths := linuxpath.New(i.ctx)
    64  	links, err := os.ReadDir(paths.SysClassDRM)
    65  	if err != nil {
    66  		i.ctx.Warn(_WARN_NO_SYS_CLASS_DRM)
    67  		return nil
    68  	}
    69  	cards := make([]*GraphicsCard, 0)
    70  	for _, link := range links {
    71  		lname := link.Name()
    72  		if !strings.HasPrefix(lname, "card") {
    73  			continue
    74  		}
    75  		if strings.ContainsRune(lname, '-') {
    76  			continue
    77  		}
    78  		// Grab the card's zero-based integer index
    79  		lnameBytes := []byte(lname)
    80  		cardIdx, err := strconv.Atoi(string(lnameBytes[4:]))
    81  		if err != nil {
    82  			cardIdx = -1
    83  		}
    84  
    85  		// Calculate the card's PCI address by looking at the symbolic link's
    86  		// target
    87  		lpath := filepath.Join(paths.SysClassDRM, lname)
    88  		dest, err := os.Readlink(lpath)
    89  		if err != nil {
    90  			continue
    91  		}
    92  		pathParts := strings.Split(dest, "/")
    93  		// The PCI address of the graphics card is the *last* PCI address in
    94  		// the filepath...
    95  		pciAddress := ""
    96  		for x := len(pathParts) - 1; x >= 0; x-- {
    97  			part := pathParts[x]
    98  			if reValidPCIAddress.MatchString(part) {
    99  				pciAddress = part
   100  				break
   101  			}
   102  		}
   103  		if pciAddress == "" {
   104  			continue
   105  		}
   106  		card := &GraphicsCard{
   107  			Address: pciAddress,
   108  			Index:   cardIdx,
   109  		}
   110  		cards = append(cards, card)
   111  	}
   112  	gpuFillNUMANodes(i.ctx, cards)
   113  	gpuFillPCIDevice(i.ctx, cards)
   114  	i.GraphicsCards = cards
   115  	return nil
   116  }
   117  
   118  // Loops through each GraphicsCard struct and attempts to fill the DeviceInfo
   119  // attribute with PCI device information
   120  func gpuFillPCIDevice(ctx *context.Context, cards []*GraphicsCard) {
   121  	pci, err := pci.New(context.WithContext(ctx))
   122  	if err != nil {
   123  		ctx.Warn("failed to PCI device database: %s", err)
   124  		return
   125  	}
   126  	for _, card := range cards {
   127  		if card.DeviceInfo == nil {
   128  			card.DeviceInfo = pci.GetDevice(card.Address)
   129  		}
   130  	}
   131  }
   132  
   133  // Loops through each GraphicsCard struct and find which NUMA node the card is
   134  // affined to, setting the GraphicsCard.Node field accordingly. If the host
   135  // system is not a NUMA system, the Node field will be set to nil.
   136  func gpuFillNUMANodes(ctx *context.Context, cards []*GraphicsCard) {
   137  	paths := linuxpath.New(ctx)
   138  	topo, err := topology.New(context.WithContext(ctx))
   139  	if err != nil {
   140  		// Problem getting topology information so just set the graphics card's
   141  		// node to nil
   142  		for _, card := range cards {
   143  			if topo.Architecture != topology.ArchitectureNUMA {
   144  				card.Node = nil
   145  			}
   146  		}
   147  		return
   148  	}
   149  	for _, card := range cards {
   150  		// Each graphics card on a NUMA system will have a pseudo-file
   151  		// called /sys/class/drm/card$CARD_INDEX/device/numa_node which
   152  		// contains the NUMA node that the card is affined to
   153  		cardIndexStr := strconv.Itoa(card.Index)
   154  		fpath := filepath.Join(
   155  			paths.SysClassDRM,
   156  			"card"+cardIndexStr,
   157  			"device",
   158  			"numa_node",
   159  		)
   160  		nodeIdx := util.SafeIntFromFile(ctx, fpath)
   161  		if nodeIdx == -1 {
   162  			continue
   163  		}
   164  		for _, node := range topo.Nodes {
   165  			if nodeIdx == int(node.ID) {
   166  				card.Node = node
   167  			}
   168  		}
   169  	}
   170  }