Skip to content
Closed
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
1 change: 1 addition & 0 deletions alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var (
WithDisableWarnings = option.WithNullAlerter
WithDisableTools = option.WithDisableTools
WithPathOverrides = option.WithPathOverrides
WithDisableTopology = option.WithDisableTopology
)

type PathOverrides = option.PathOverrides
Expand Down
12 changes: 9 additions & 3 deletions pkg/gpu/gpu_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,21 @@ func gpuFillPCIDevice(pci *pci.Info, cards []*GraphicsCard) {
// affined to, setting the GraphicsCard.Node field accordingly. If the host
// system is not a NUMA system, the Node field will be set to nil.
func gpuFillNUMANodes(opts *option.Options, cards []*GraphicsCard) {
// Skip topology detection if requested to reduce memory consumption
if opts.DisableTopology {
for _, card := range cards {
card.Node = nil
}
return
}

paths := linuxpath.New(opts)
topo, err := topology.New()
if err != nil {
// Problem getting topology information so just set the graphics card's
// node to nil
for _, card := range cards {
if topo.Architecture != topology.ArchitectureNUMA {
card.Node = nil
}
card.Node = nil
}
return
}
Expand Down
18 changes: 18 additions & 0 deletions pkg/option/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ type Options struct {
// custom pcidb.WithOption settings, instead of letting ghw load the PCI
// database automatically.
PCIDB *pcidb.PCIDB

// DisableTopology skips system topology detection when calling PCI() or GPU().
// This can significantly reduce memory consumption when you only need basic
// hardware information and don't care about NUMA topology or node affinity.
// When enabled, the system architecture will be assumed to be SMP and device
// Node fields will be nil.
DisableTopology bool
}

func (o *Options) Warn(msg string, args ...interface{}) {
Expand Down Expand Up @@ -156,6 +163,17 @@ func WithPCIDB(pcidb *pcidb.PCIDB) Option {
}
}

// WithDisableTopology disables system topology detection to reduce memory consumption.
// When using this option, ghw will skip scanning NUMA topology, CPU cores, memory
// caches, and node distances. This is useful when you only need basic PCI or GPU
// information and want to minimize memory overhead. The system architecture will be
// assumed to be SMP, and device Node fields will be nil.
func WithDisableTopology() Option {
return func(opts *Options) {
opts.DisableTopology = true
}
}

// PathOverrides is a map, keyed by the string name of a mount path, of override paths
type PathOverrides map[string]string

Expand Down
28 changes: 19 additions & 9 deletions pkg/pci/pci.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,22 +137,32 @@ func (i *Info) String() string {
// New returns a pointer to an Info struct that contains information about the
// PCI devices on the host system
func New(opt ...option.Option) (*Info, error) {
topo, err := topology.New(opt...)
if err != nil {
return nil, fmt.Errorf(
"failed to initialize PCI info dur to failure to initialize "+
"Topology info: %w",
err,
)
}
opts := &option.Options{}
for _, o := range opt {
o(opts)
}

// by default we don't report NUMA information;
// we will only if are sure we are running on NUMA architecture
info := &Info{
arch: topo.Architecture,
arch: topology.ArchitectureSMP, // default to SMP
}
// Skip topology detection if requested to reduce memory consumption
if opts.DisableTopology {
opts.Warn("topology detection disabled, assuming SMP architecture")
} else {
topo, err := topology.New(opt...)
if err != nil {
return nil, fmt.Errorf(
"failed to initialize PCI info due to failure to initialize "+
"Topology info: %w",
err,
)
}
info.arch = topo.Architecture
}
if opts.PCIDB != nil {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I found options.PCIDB no longer used, i'm not sure if this is expected.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Author

@halfcrazy yes, we've totally changed the way that options and context are handled. I will rework this PR soon, I promise. sorry for the delay.

info.db = opts.PCIDB
}
if err := info.load(opts); err != nil {
return nil, err
Expand Down
65 changes: 65 additions & 0 deletions pkg/pci/pci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"os"
"testing"

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

Expand Down Expand Up @@ -58,3 +59,67 @@ func TestPCI(t *testing.T) {
}
}
}

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

// Test with DisableTopology option enabled
info, err := pci.New(ghw.WithDisableTopology())
if err != nil {
t.Fatalf("Expected no error creating PciInfo with DisableTopology, but got %v", err)
}

devs := info.Devices
if len(devs) == 0 {
t.Fatalf("Expected to find >0 PCI devices in PCIInfo.Devices but got 0.")
}

// When DisableTopology is enabled, all devices should have nil Node field
for _, dev := range devs {
if dev.Node != nil {
t.Errorf("Expected device %s to have nil Node when DisableTopology is enabled, but got %v", dev.Address, dev.Node)
}
// Verify other fields are still populated correctly
if dev.Class == nil {
t.Fatalf("Expected device class for %s to be non-nil", dev.Address)
}
if dev.Product == nil {
t.Fatalf("Expected device product for %s to be non-nil", dev.Address)
}
if dev.Vendor == nil {
t.Fatalf("Expected device vendor for %s to be non-nil", dev.Address)
}
}
}

func BenchmarkPCIMemoryComparison(b *testing.B) {
if _, ok := os.LookupEnv("GHW_TESTING_SKIP_PCI"); ok {
b.Skip("Skipping PCI benchmarks.")
}

b.Run("WithTopologyDetection", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
_, err := pci.New()
if err != nil {
b.Fatalf("Error getting PCI info: %v", err)
}
}
})

b.Run("WithDisableTopology", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
_, err := pci.New(ghw.WithDisableTopology())
if err != nil {
b.Fatalf("Error getting PCI info with DisableTopology: %v", err)
}
}
})
}