Skip to content

Commit c569fcb

Browse files
committed
Add WithDisableTopology option to reduce memory consumption
This commit adds a new WithDisableTopology() option that allows users to skip system topology detection when calling ghw.PCI() or ghw.GPU(), significantly reducing memory consumption for use cases that don't require NUMA topology information. Signed-off-by: Yan Zhu <[email protected]>
1 parent 5595b94 commit c569fcb

File tree

6 files changed

+136
-4
lines changed

6 files changed

+136
-4
lines changed

pkg/context/context.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type Context struct {
2222
SnapshotRoot string
2323
SnapshotExclusive bool
2424
PathOverrides option.PathOverrides
25+
DisableTopology bool
2526
snapshotUnpackedPath string
2627
alert option.Alerter
2728
err error
@@ -83,6 +84,10 @@ func New(opts ...*option.Option) *Context {
8384
ctx.PathOverrides = merged.PathOverrides
8485
}
8586

87+
if merged.DisableTopology != nil {
88+
ctx.DisableTopology = *merged.DisableTopology
89+
}
90+
8691
// New is not allowed to return error - it would break the established API.
8792
// so the only way out is to actually do the checks here and record the error,
8893
// and return it later, at the earliest possible occasion, in Setup()

pkg/gpu/gpu_linux.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,14 @@ func gpuFillPCIDevice(ctx *context.Context, cards []*GraphicsCard) {
134134
// affined to, setting the GraphicsCard.Node field accordingly. If the host
135135
// system is not a NUMA system, the Node field will be set to nil.
136136
func gpuFillNUMANodes(ctx *context.Context, cards []*GraphicsCard) {
137+
// Skip topology detection if requested to reduce memory consumption
138+
if ctx.DisableTopology {
139+
for _, card := range cards {
140+
card.Node = nil
141+
}
142+
return
143+
}
144+
137145
paths := linuxpath.New(ctx)
138146
topo, err := topology.New(context.WithContext(ctx))
139147
if err != nil {

pkg/option/option.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,13 @@ type Option struct {
148148
// or providing an instance created with custom pcidb.WithOption settings, instead of
149149
// letting ghw load the PCI database automatically.
150150
PCIDB *pcidb.PCIDB
151+
152+
// DisableTopology skips system topology detection when calling PCI() or GPU().
153+
// This can significantly reduce memory consumption when you only need basic
154+
// hardware information and don't care about NUMA topology or node affinity.
155+
// When enabled, the system architecture will be assumed to be SMP and device
156+
// Node fields will be nil.
157+
DisableTopology *bool
151158
}
152159

153160
// SnapshotOptions contains options for handling of ghw snapshots
@@ -211,6 +218,16 @@ func WithPCIDB(pcidb *pcidb.PCIDB) *Option {
211218
return &Option{PCIDB: pcidb}
212219
}
213220

221+
// WithDisableTopology disables system topology detection to reduce memory consumption.
222+
// When using this option, ghw will skip scanning NUMA topology, CPU cores, memory
223+
// caches, and node distances. This is useful when you only need basic PCI or GPU
224+
// information and want to minimize memory overhead. The system architecture will be
225+
// assumed to be SMP, and device Node fields will be nil.
226+
func WithDisableTopology() *Option {
227+
disabled := true
228+
return &Option{DisableTopology: &disabled}
229+
}
230+
214231
// PathOverrides is a map, keyed by the string name of a mount path, of override paths
215232
type PathOverrides map[string]string
216233

@@ -252,6 +269,9 @@ func Merge(opts ...*Option) *Option {
252269
if opt.PCIDB != nil {
253270
merged.PCIDB = opt.PCIDB
254271
}
272+
if opt.DisableTopology != nil {
273+
merged.DisableTopology = opt.DisableTopology
274+
}
255275
}
256276
// Set the default value if missing from mergeOpts
257277
if merged.Chroot == nil {

pkg/option/option_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,27 @@ func TestOption(t *testing.T) {
172172
},
173173
},
174174
},
175+
{
176+
name: "disable topology",
177+
opts: []*option.Option{
178+
option.WithDisableTopology(),
179+
},
180+
merged: &option.Option{
181+
DisableTopology: boolPtr(true),
182+
},
183+
},
184+
{
185+
name: "chroot and disable topology",
186+
opts: []*option.Option{
187+
option.WithChroot("/my/chroot/dir"),
188+
option.WithDisableTopology(),
189+
},
190+
merged: &option.Option{
191+
Chroot: stringPtr("/my/chroot/dir"),
192+
DisableTopology: boolPtr(true),
193+
EnableTools: boolPtr(true),
194+
},
195+
},
175196
}
176197
if pciTest != nil {
177198
optTCases = append(optTCases, *pciTest)
@@ -220,6 +241,14 @@ func optionEqual(a, b *option.Option) (string, bool) {
220241
return "enabletools value", false
221242
}
222243
}
244+
if a.DisableTopology != nil {
245+
if b.DisableTopology == nil {
246+
return "disabletopology ptr", false
247+
}
248+
if *a.DisableTopology != *b.DisableTopology {
249+
return "disabletopology value", false
250+
}
251+
}
223252
return "", true
224253
}
225254

pkg/pci/pci.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,16 @@ func New(opts ...*option.Option) (*Info, error) {
146146
// a chance to run before any subordinate package is created reusing
147147
// our context.
148148
loadDetectingTopology := func() error {
149-
topo, err := topology.New(context.WithContext(ctx))
150-
if err == nil {
151-
info.arch = topo.Architecture
149+
// Skip topology detection if requested to reduce memory consumption
150+
if merged.DisableTopology != nil && *merged.DisableTopology {
151+
ctx.Warn("topology detection disabled, assuming SMP architecture")
152152
} else {
153-
ctx.Warn("error detecting system topology: %v", err)
153+
topo, err := topology.New(context.WithContext(ctx))
154+
if err == nil {
155+
info.arch = topo.Architecture
156+
} else {
157+
ctx.Warn("error detecting system topology: %v", err)
158+
}
154159
}
155160
if merged.PCIDB != nil {
156161
info.db = merged.PCIDB

pkg/pci/pci_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"os"
1111
"testing"
1212

13+
"github.com/jaypipes/ghw/pkg/option"
1314
"github.com/jaypipes/ghw/pkg/pci"
1415
)
1516

@@ -58,3 +59,67 @@ func TestPCI(t *testing.T) {
5859
}
5960
}
6061
}
62+
63+
func TestPCIWithDisableTopology(t *testing.T) {
64+
if _, ok := os.LookupEnv("GHW_TESTING_SKIP_PCI"); ok {
65+
t.Skip("Skipping PCI tests.")
66+
}
67+
68+
// Test with DisableTopology option enabled
69+
info, err := pci.New(option.WithDisableTopology())
70+
if err != nil {
71+
t.Fatalf("Expected no error creating PciInfo with DisableTopology, but got %v", err)
72+
}
73+
74+
devs := info.Devices
75+
if len(devs) == 0 {
76+
t.Fatalf("Expected to find >0 PCI devices in PCIInfo.Devices but got 0.")
77+
}
78+
79+
// When DisableTopology is enabled, all devices should have nil Node field
80+
for _, dev := range devs {
81+
if dev.Node != nil {
82+
t.Errorf("Expected device %s to have nil Node when DisableTopology is enabled, but got %v", dev.Address, dev.Node)
83+
}
84+
// Verify other fields are still populated correctly
85+
if dev.Class == nil {
86+
t.Fatalf("Expected device class for %s to be non-nil", dev.Address)
87+
}
88+
if dev.Product == nil {
89+
t.Fatalf("Expected device product for %s to be non-nil", dev.Address)
90+
}
91+
if dev.Vendor == nil {
92+
t.Fatalf("Expected device vendor for %s to be non-nil", dev.Address)
93+
}
94+
}
95+
}
96+
97+
func BenchmarkPCIMemoryComparison(b *testing.B) {
98+
if _, ok := os.LookupEnv("GHW_TESTING_SKIP_PCI"); ok {
99+
b.Skip("Skipping PCI benchmarks.")
100+
}
101+
102+
b.Run("WithTopologyDetection", func(b *testing.B) {
103+
b.ReportAllocs()
104+
b.ResetTimer()
105+
106+
for i := 0; i < b.N; i++ {
107+
_, err := pci.New()
108+
if err != nil {
109+
b.Fatalf("Error getting PCI info: %v", err)
110+
}
111+
}
112+
})
113+
114+
b.Run("WithDisableTopology", func(b *testing.B) {
115+
b.ReportAllocs()
116+
b.ResetTimer()
117+
118+
for i := 0; i < b.N; i++ {
119+
_, err := pci.New(option.WithDisableTopology())
120+
if err != nil {
121+
b.Fatalf("Error getting PCI info with DisableTopology: %v", err)
122+
}
123+
}
124+
})
125+
}

0 commit comments

Comments
 (0)