Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pci: report NUMA node locality for the PCI devices #225

Merged
merged 1 commit into from
Feb 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions pkg/pci/pci.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/jaypipes/ghw/pkg/context"
"github.com/jaypipes/ghw/pkg/marshal"
"github.com/jaypipes/ghw/pkg/option"
"github.com/jaypipes/ghw/pkg/topology"
"github.com/jaypipes/ghw/pkg/util"
)

Expand All @@ -39,6 +40,9 @@ type Device struct {
Subclass *pcidb.Subclass `json:"subclass"`
// optional programming interface
ProgrammingInterface *pcidb.ProgrammingInterface `json:"programming_interface"`
// Topology node that the PCI device is affined to. Will be nil if the
// architecture is not NUMA.
Node *topology.Node `json:"node,omitempty"`
}

type devIdent struct {
Expand Down Expand Up @@ -116,7 +120,8 @@ func (d *Device) String() string {
}

type Info struct {
ctx *context.Context
arch topology.Architecture
ctx *context.Context
// All PCI devices on the host system
Devices []*Device
// hash of class ID -> class information
Expand Down Expand Up @@ -178,7 +183,19 @@ func AddressFromString(address string) *Address {
// PCI devices on the host system
func New(opts ...*option.Option) (*Info, error) {
ctx := context.New(opts...)
info := &Info{ctx: ctx}
// by default we don't report NUMA information;
// we will only if are sure we are running on NUMA architecture
arch := topology.ARCHITECTURE_SMP
topo, err := topology.NewWithContext(ctx)
if err == nil {
arch = topo.Architecture
} else {
ctx.Warn("error detecting system topology: %v", err)
}
info := &Info{
arch: arch,
ctx: ctx,
}
if err := ctx.Do(info.load); err != nil {
return nil, err
}
Expand Down
26 changes: 26 additions & 0 deletions pkg/pci/pci_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

"github.com/jaypipes/ghw/pkg/context"
"github.com/jaypipes/ghw/pkg/linuxpath"
"github.com/jaypipes/ghw/pkg/topology"
"github.com/jaypipes/ghw/pkg/util"
)

Expand Down Expand Up @@ -66,6 +67,28 @@ func getDeviceRevision(ctx *context.Context, address string) string {
return string(revision)
}

func getDeviceNUMANode(ctx *context.Context, address string) *topology.Node {
paths := linuxpath.New(ctx)
pciAddr := AddressFromString(address)
if pciAddr == nil {
return nil
}
numaNodePath := filepath.Join(paths.SysBusPciDevices, pciAddr.String(), "numa_node")

if _, err := os.Stat(numaNodePath); err != nil {
return nil
}

nodeIdx := util.SafeIntFromFile(ctx, numaNodePath)
if nodeIdx == -1 {
return nil
}

return &topology.Node{
ID: nodeIdx,
}
}

type deviceModaliasInfo struct {
vendorID string
productID string
Expand Down Expand Up @@ -263,6 +286,9 @@ func (info *Info) GetDevice(address string) *Device {

device := info.getDeviceFromModaliasInfo(address, modaliasInfo)
device.Revision = getDeviceRevision(info.ctx, address)
if info.arch == topology.ARCHITECTURE_NUMA {
device.Node = getDeviceNUMANode(info.ctx, address)
}
return device
}

Expand Down
87 changes: 87 additions & 0 deletions pkg/pci/pci_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package pci_test

import (
"fmt"
"os"
"path/filepath"
"testing"

"github.com/jaypipes/ghw/pkg/option"
"github.com/jaypipes/ghw/pkg/pci"

"github.com/jaypipes/ghw/testdata"
)

type pciNumaTestCase struct {
addr string
node int
}

// nolint: gocyclo
func TestPCINUMANode(t *testing.T) {
if _, ok := os.LookupEnv("GHW_TESTING_SKIP_PCI"); ok {
t.Skip("Skipping PCI tests.")
}

testdataPath, err := testdata.SnapshotsDirectory()
if err != nil {
t.Fatalf("Expected nil err, but got %v", err)
}

multiNumaSnapshot := filepath.Join(testdataPath, "linux-amd64-intel-xeon-L5640.tar.gz")
// from now on we use constants reflecting the content of the snapshot we requested,
// which we reviewed beforehand. IOW, you need to know the content of the
// snapshot to fully understand this test. Inspect it using
// GHW_SNAPSHOT_PATH="/path/to/linux-amd64-intel-xeon-L5640.tar.gz" ghwc topology

info, err := pci.New(option.WithSnapshot(option.SnapshotOptions{
Path: multiNumaSnapshot,
}))

if err != nil {
t.Fatalf("Expected nil err, but got %v", err)
}
if info == nil {
t.Fatalf("Expected non-nil PCIInfo, but got nil")
}

tCases := []pciNumaTestCase{
{
addr: "0000:07:03.0",
// -1 is actually what we get out of the box on the snapshotted box
node: -1,
},
{
addr: "0000:05:11.0",
node: 0,
},
{
addr: "0000:05:00.1",
node: 1,
},
}
for _, tCase := range tCases {
t.Run(fmt.Sprintf("%s (%d)", tCase.addr, tCase.node), func(t *testing.T) {
dev := info.GetDevice(tCase.addr)
if dev == nil {
t.Fatalf("got nil device for address %q", tCase.addr)
}
if dev.Node == nil {
if tCase.node != -1 {
t.Fatalf("got nil numa NODE for address %q", tCase.addr)
}
} else {
if dev.Node.ID != tCase.node {
t.Errorf("got NUMA node info %#v, expected on node %d", dev.Node, tCase.node)
}
}
})
}

}
8 changes: 7 additions & 1 deletion pkg/topology/topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,13 @@ type Info struct {
// New returns a pointer to an Info struct that contains information about the
// NUMA topology on the host system
func New(opts ...*option.Option) (*Info, error) {
ctx := context.New(opts...)
return NewWithContext(context.New(opts...))
}

// NewWithContext returns a pointer to an Info struct that contains information about
// the NUMA topology on the host system. Use this function when you want to consume
// the topology package from another package (e.g. pci, gpu)
func NewWithContext(ctx *context.Context) (*Info, error) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

info := &Info{ctx: ctx}
if err := ctx.Do(info.load); err != nil {
return nil, err
Expand Down
Binary file not shown.