forked from jaypipes/ghw
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgpu_linux.go
More file actions
164 lines (157 loc) · 5.27 KB
/
gpu_linux.go
File metadata and controls
164 lines (157 loc) · 5.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//
package ghw
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
)
const (
PATH_SYSFS_CLASS_DRM = "/sys/class/drm"
)
func gpuFillInfo(info *GPUInfo) error {
// In Linux, each graphics card is listed under the /sys/class/drm
// directory as a symbolic link named "cardN", where N is a zero-based
// index of the card in the system. "DRM" stands for Direct Rendering
// Manager and is the Linux subsystem that is responsible for graphics I/O
//
// Each card may have multiple symbolic
// links in this directory representing the interfaces from the graphics
// card over a particular wire protocol (HDMI, DisplayPort, etc). These
// symbolic links are named cardN-<INTERFACE_TYPE>-<DISPLAY_ID>. For
// instance, on one of my local workstations with an NVIDIA GTX 1050ti
// graphics card with one HDMI, one DisplayPort, and one DVI interface to
// the card, I see the following in /sys/class/drm:
//
// $ ll /sys/class/drm/
// total 0
// drwxr-xr-x 2 root root 0 Jul 16 11:50 ./
// drwxr-xr-x 75 root root 0 Jul 16 11:50 ../
// lrwxrwxrwx 1 root root 0 Jul 16 11:50 card0 -> ../../devices/pci0000:00/0000:00:03.0/0000:03:00.0/drm/card0/
// 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/
// 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/
// 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/
//
// In this routine, we are only interested in the first link (card0), which
// we follow to gather information about the actual device from the PCI
// subsystem (we query the modalias file of the PCI device's sysfs
// directory using the `ghw.PCIInfo.GetDevice()` function.
links, err := ioutil.ReadDir(PATH_SYSFS_CLASS_DRM)
if err != nil {
fmt.Fprintf(os.Stderr, `************************ WARNING ***********************************
/sys/class/drm does not exist on this system (likely the host system is a
virtual machine or container with no graphics). Therefore,
GPUInfo.GraphicsCards will be an empty array.
********************************************************************
`,
)
return nil
}
cards := make([]*GraphicsCard, 0)
for _, link := range links {
lname := link.Name()
if !strings.HasPrefix(lname, "card") {
continue
}
if strings.ContainsRune(lname, '-') {
continue
}
// Grab the card's zero-based integer index
lnameBytes := []byte(lname)
cardIdx, err := strconv.Atoi(string(lnameBytes[4:]))
if err != nil {
cardIdx = -1
}
// Calculate the card's PCI address by looking at the symbolic link's
// target
lpath := filepath.Join(PATH_SYSFS_CLASS_DRM, lname)
dest, err := os.Readlink(lpath)
if err != nil {
continue
}
pathParts := strings.Split(dest, "/")
numParts := len(pathParts)
pciAddress := pathParts[numParts-3]
card := &GraphicsCard{
Address: pciAddress,
Index: cardIdx,
}
cards = append(cards, card)
}
gpuFillNUMANodes(cards)
gpuFillPCIDevice(cards)
info.GraphicsCards = cards
return nil
}
// Loops through each GraphicsCard struct and attempts to fill the DeviceInfo
// attribute with PCI device information
func gpuFillPCIDevice(cards []*GraphicsCard) {
pci, err := PCI()
if err != nil {
return
}
for _, card := range cards {
if card.DeviceInfo == nil {
card.DeviceInfo = pci.GetDevice(card.Address)
}
}
}
// Loops through each GraphicsCard struct and find which NUMA nodes the card is
// affined to, setting the GraphicsCard.Nodes field accordingly. If the host
// system is not a NUMA system, the Nodes field will be set to an empty array
// of Node pointers.
func gpuFillNUMANodes(cards []*GraphicsCard) {
topo, err := Topology()
if err != nil {
for _, card := range cards {
if topo.Architecture != NUMA {
card.Nodes = make([]*TopologyNode, 0)
}
}
return
}
for _, card := range cards {
if topo.Architecture != NUMA {
card.Nodes = make([]*TopologyNode, 0)
continue
}
// Each graphics card on a NUMA system will have a pseudo-file
// called /sys/class/drm/card$CARD_INDEX/device/numa_node which
// contains a comma-separated list of NUMA nodes that the card is
// affined to
cardIndexStr := strconv.Itoa(card.Index)
fpath := filepath.Join(
PATH_SYSFS_CLASS_DRM,
"card"+cardIndexStr,
"device",
"numa_node",
)
numaContents, err := ioutil.ReadFile(fpath)
if err != nil {
fmt.Fprintf(os.Stderr, `************************ WARNING ***********************************
Unable to read numa_nodes descriptor file on this system.
Setting graphics card's Nodes attribute to empty array.
********************************************************************
`,
)
card.Nodes = make([]*TopologyNode, 0)
continue
}
cardNodes := make([]*TopologyNode, 0)
nodeIndexes := strings.Split(string(numaContents), ",")
for _, nodeIndex := range nodeIndexes {
for _, node := range topo.Nodes {
nodeIndexInt, _ := strconv.Atoi(nodeIndex)
if nodeIndexInt == int(node.Id) {
cardNodes = append(cardNodes, node)
}
}
}
card.Nodes = cardNodes
}
}