From 2560bf63327529421c8b0aefbefeebbf44f91d37 Mon Sep 17 00:00:00 2001 From: Evan Lezar <elezar@nvidia.com> Date: Thu, 4 Apr 2024 16:52:35 +0200 Subject: [PATCH 1/4] Fix check targets Signed-off-by: Evan Lezar <elezar@nvidia.com> --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index c94c191..04edc8a 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ DOCKER ?= docker PCI_IDS_URL ?= https://pci-ids.ucw.cz/v2.2/pci.ids +CHECK_TARGETS := lint TARGETS := binary build all check fmt assert-fmt generate lint vet test coverage DOCKER_TARGETS := $(patsubst %,docker-%, $(TARGETS)) .PHONY: $(TARGETS) $(DOCKER_TARGETS) vendor check-vendor @@ -28,7 +29,7 @@ build: GOOS=$(GOOS) go build ./... all: check build binary -check: assert-fmt lint vet +check: $(CHECK_TARGETS) vendor: go mod tidy @@ -59,8 +60,7 @@ generate: go generate $(MODULE)/... lint: - # We use `go list -f '{{.Dir}}' $(MODULE)/...` to skip the `vendor` folder. - go list -f '{{.Dir}}' $(MODULE)/... | grep -v pkg/nvml | xargs golint -set_exit_status + golangci-lint run ./... ## goimports: Apply goimports -local to the codebase goimports: From 791d093c62f8cdd48e1e18502371a800491112ed Mon Sep 17 00:00:00 2001 From: Evan Lezar <elezar@nvidia.com> Date: Tue, 26 Mar 2024 14:55:37 +0200 Subject: [PATCH 2/4] Refactor info API This change adds a PropertyExtractor interface to encapsulate functions that query a system for certain capabilities. The IsTegraSystem has been renamed to HasTegraFiles function and marked as Deprecated. Signed-off-by: Evan Lezar <elezar@nvidia.com> --- pkg/nvlib/info/api.go | 34 ++++ pkg/nvlib/info/builder.go | 35 ++++ pkg/nvlib/info/options.go | 20 +- .../info/{info.go => property-extractor.go} | 50 ++--- pkg/nvlib/info/property-extractor_mock.go | 178 ++++++++++++++++++ pkg/nvlib/info/root.go | 86 +++++++++ 6 files changed, 353 insertions(+), 50 deletions(-) create mode 100644 pkg/nvlib/info/api.go create mode 100644 pkg/nvlib/info/builder.go rename pkg/nvlib/info/{info.go => property-extractor.go} (64%) create mode 100644 pkg/nvlib/info/property-extractor_mock.go create mode 100644 pkg/nvlib/info/root.go diff --git a/pkg/nvlib/info/api.go b/pkg/nvlib/info/api.go new file mode 100644 index 0000000..41b8e22 --- /dev/null +++ b/pkg/nvlib/info/api.go @@ -0,0 +1,34 @@ +/** +# Copyright 2024 NVIDIA CORPORATION +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +**/ + +package info + +// Interface provides the API to the info package. +type Interface interface { + PropertyExtractor +} + +// PropertyExtractor provides a set of functions to query capabilities of the +// system. +// +//go:generate moq -rm -out property-extractor_mock.go . PropertyExtractor +type PropertyExtractor interface { + HasDXCore() (bool, string) + HasNvml() (bool, string) + HasTegraFiles() (bool, string) + // Deprecated: Use HasTegraFiles instead. + IsTegraSystem() (bool, string) +} diff --git a/pkg/nvlib/info/builder.go b/pkg/nvlib/info/builder.go new file mode 100644 index 0000000..d9275ca --- /dev/null +++ b/pkg/nvlib/info/builder.go @@ -0,0 +1,35 @@ +/** +# Copyright 2024 NVIDIA CORPORATION +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +**/ + +package info + +type options struct { + root root +} + +// New creates a new instance of the 'info' Interface. +func New(opts ...Option) Interface { + o := &options{} + for _, opt := range opts { + opt(o) + } + if o.root == "" { + o.root = "/" + } + return &propertyExtractor{ + root: o.root, + } +} diff --git a/pkg/nvlib/info/options.go b/pkg/nvlib/info/options.go index ce72150..c4265d1 100644 --- a/pkg/nvlib/info/options.go +++ b/pkg/nvlib/info/options.go @@ -17,23 +17,11 @@ package info // Option defines a function for passing options to the New() call. -type Option func(*infolib) - -// New creates a new instance of the 'info' interface. -func New(opts ...Option) Interface { - i := &infolib{} - for _, opt := range opts { - opt(i) - } - if i.root == "" { - i.root = "/" - } - return i -} +type Option func(*options) // WithRoot provides a Option to set the root of the 'info' interface. -func WithRoot(root string) Option { - return func(i *infolib) { - i.root = root +func WithRoot(r string) Option { + return func(i *options) { + i.root = root(r) } } diff --git a/pkg/nvlib/info/info.go b/pkg/nvlib/info/property-extractor.go similarity index 64% rename from pkg/nvlib/info/info.go rename to pkg/nvlib/info/property-extractor.go index 677270c..9e41a54 100644 --- a/pkg/nvlib/info/info.go +++ b/pkg/nvlib/info/property-extractor.go @@ -19,31 +19,21 @@ package info import ( "fmt" "os" - "path/filepath" "strings" - - "github.com/NVIDIA/go-nvml/pkg/dl" ) -// Interface provides the API to the info package. -type Interface interface { - HasDXCore() (bool, string) - HasNvml() (bool, string) - IsTegraSystem() (bool, string) -} - -type infolib struct { - root string +type propertyExtractor struct { + root root } -var _ Interface = &infolib{} +var _ Interface = &propertyExtractor{} // HasDXCore returns true if DXCore is detected on the system. -func (i *infolib) HasDXCore() (bool, string) { +func (i *propertyExtractor) HasDXCore() (bool, string) { const ( libraryName = "libdxcore.so" ) - if err := assertHasLibrary(libraryName); err != nil { + if err := i.root.assertHasLibrary(libraryName); err != nil { return false, fmt.Sprintf("could not load DXCore library: %v", err) } @@ -51,11 +41,11 @@ func (i *infolib) HasDXCore() (bool, string) { } // HasNvml returns true if NVML is detected on the system. -func (i *infolib) HasNvml() (bool, string) { +func (i *propertyExtractor) HasNvml() (bool, string) { const ( libraryName = "libnvidia-ml.so.1" ) - if err := assertHasLibrary(libraryName); err != nil { + if err := i.root.assertHasLibrary(libraryName); err != nil { return false, fmt.Sprintf("could not load NVML library: %v", err) } @@ -63,9 +53,15 @@ func (i *infolib) HasNvml() (bool, string) { } // IsTegraSystem returns true if the system is detected as a Tegra-based system. -func (i *infolib) IsTegraSystem() (bool, string) { - tegraReleaseFile := filepath.Join(i.root, "/etc/nv_tegra_release") - tegraFamilyFile := filepath.Join(i.root, "/sys/devices/soc0/family") +// Deprecated: Use HasTegraFiles instead. +func (i *propertyExtractor) IsTegraSystem() (bool, string) { + return i.HasTegraFiles() +} + +// HasTegraFiles returns true if tegra-based files are detected on the system. +func (i *propertyExtractor) HasTegraFiles() (bool, string) { + tegraReleaseFile := i.root.join("/etc/nv_tegra_release") + tegraFamilyFile := i.root.join("/sys/devices/soc0/family") if info, err := os.Stat(tegraReleaseFile); err == nil && !info.IsDir() { return true, fmt.Sprintf("%v found", tegraReleaseFile) @@ -86,17 +82,3 @@ func (i *infolib) IsTegraSystem() (bool, string) { return false, fmt.Sprintf("%v has no 'tegra' prefix", tegraFamilyFile) } - -// assertHasLibrary returns an error if the specified library cannot be loaded. -func assertHasLibrary(libraryName string) error { - const ( - libraryLoadFlags = dl.RTLD_LAZY - ) - lib := dl.New(libraryName, libraryLoadFlags) - if err := lib.Open(); err != nil { - return err - } - defer lib.Close() - - return nil -} diff --git a/pkg/nvlib/info/property-extractor_mock.go b/pkg/nvlib/info/property-extractor_mock.go new file mode 100644 index 0000000..570dfe3 --- /dev/null +++ b/pkg/nvlib/info/property-extractor_mock.go @@ -0,0 +1,178 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package info + +import ( + "sync" +) + +// Ensure, that PropertyExtractorMock does implement PropertyExtractor. +// If this is not the case, regenerate this file with moq. +var _ PropertyExtractor = &PropertyExtractorMock{} + +// PropertyExtractorMock is a mock implementation of PropertyExtractor. +// +// func TestSomethingThatUsesPropertyExtractor(t *testing.T) { +// +// // make and configure a mocked PropertyExtractor +// mockedPropertyExtractor := &PropertyExtractorMock{ +// HasDXCoreFunc: func() (bool, string) { +// panic("mock out the HasDXCore method") +// }, +// HasNvmlFunc: func() (bool, string) { +// panic("mock out the HasNvml method") +// }, +// HasTegraFilesFunc: func() (bool, string) { +// panic("mock out the HasTegraFiles method") +// }, +// IsTegraSystemFunc: func() (bool, string) { +// panic("mock out the IsTegraSystem method") +// }, +// } +// +// // use mockedPropertyExtractor in code that requires PropertyExtractor +// // and then make assertions. +// +// } +type PropertyExtractorMock struct { + // HasDXCoreFunc mocks the HasDXCore method. + HasDXCoreFunc func() (bool, string) + + // HasNvmlFunc mocks the HasNvml method. + HasNvmlFunc func() (bool, string) + + // HasTegraFilesFunc mocks the HasTegraFiles method. + HasTegraFilesFunc func() (bool, string) + + // IsTegraSystemFunc mocks the IsTegraSystem method. + IsTegraSystemFunc func() (bool, string) + + // calls tracks calls to the methods. + calls struct { + // HasDXCore holds details about calls to the HasDXCore method. + HasDXCore []struct { + } + // HasNvml holds details about calls to the HasNvml method. + HasNvml []struct { + } + // HasTegraFiles holds details about calls to the HasTegraFiles method. + HasTegraFiles []struct { + } + // IsTegraSystem holds details about calls to the IsTegraSystem method. + IsTegraSystem []struct { + } + } + lockHasDXCore sync.RWMutex + lockHasNvml sync.RWMutex + lockHasTegraFiles sync.RWMutex + lockIsTegraSystem sync.RWMutex +} + +// HasDXCore calls HasDXCoreFunc. +func (mock *PropertyExtractorMock) HasDXCore() (bool, string) { + if mock.HasDXCoreFunc == nil { + panic("PropertyExtractorMock.HasDXCoreFunc: method is nil but PropertyExtractor.HasDXCore was just called") + } + callInfo := struct { + }{} + mock.lockHasDXCore.Lock() + mock.calls.HasDXCore = append(mock.calls.HasDXCore, callInfo) + mock.lockHasDXCore.Unlock() + return mock.HasDXCoreFunc() +} + +// HasDXCoreCalls gets all the calls that were made to HasDXCore. +// Check the length with: +// +// len(mockedPropertyExtractor.HasDXCoreCalls()) +func (mock *PropertyExtractorMock) HasDXCoreCalls() []struct { +} { + var calls []struct { + } + mock.lockHasDXCore.RLock() + calls = mock.calls.HasDXCore + mock.lockHasDXCore.RUnlock() + return calls +} + +// HasNvml calls HasNvmlFunc. +func (mock *PropertyExtractorMock) HasNvml() (bool, string) { + if mock.HasNvmlFunc == nil { + panic("PropertyExtractorMock.HasNvmlFunc: method is nil but PropertyExtractor.HasNvml was just called") + } + callInfo := struct { + }{} + mock.lockHasNvml.Lock() + mock.calls.HasNvml = append(mock.calls.HasNvml, callInfo) + mock.lockHasNvml.Unlock() + return mock.HasNvmlFunc() +} + +// HasNvmlCalls gets all the calls that were made to HasNvml. +// Check the length with: +// +// len(mockedPropertyExtractor.HasNvmlCalls()) +func (mock *PropertyExtractorMock) HasNvmlCalls() []struct { +} { + var calls []struct { + } + mock.lockHasNvml.RLock() + calls = mock.calls.HasNvml + mock.lockHasNvml.RUnlock() + return calls +} + +// HasTegraFiles calls HasTegraFilesFunc. +func (mock *PropertyExtractorMock) HasTegraFiles() (bool, string) { + if mock.HasTegraFilesFunc == nil { + panic("PropertyExtractorMock.HasTegraFilesFunc: method is nil but PropertyExtractor.HasTegraFiles was just called") + } + callInfo := struct { + }{} + mock.lockHasTegraFiles.Lock() + mock.calls.HasTegraFiles = append(mock.calls.HasTegraFiles, callInfo) + mock.lockHasTegraFiles.Unlock() + return mock.HasTegraFilesFunc() +} + +// HasTegraFilesCalls gets all the calls that were made to HasTegraFiles. +// Check the length with: +// +// len(mockedPropertyExtractor.HasTegraFilesCalls()) +func (mock *PropertyExtractorMock) HasTegraFilesCalls() []struct { +} { + var calls []struct { + } + mock.lockHasTegraFiles.RLock() + calls = mock.calls.HasTegraFiles + mock.lockHasTegraFiles.RUnlock() + return calls +} + +// IsTegraSystem calls IsTegraSystemFunc. +func (mock *PropertyExtractorMock) IsTegraSystem() (bool, string) { + if mock.IsTegraSystemFunc == nil { + panic("PropertyExtractorMock.IsTegraSystemFunc: method is nil but PropertyExtractor.IsTegraSystem was just called") + } + callInfo := struct { + }{} + mock.lockIsTegraSystem.Lock() + mock.calls.IsTegraSystem = append(mock.calls.IsTegraSystem, callInfo) + mock.lockIsTegraSystem.Unlock() + return mock.IsTegraSystemFunc() +} + +// IsTegraSystemCalls gets all the calls that were made to IsTegraSystem. +// Check the length with: +// +// len(mockedPropertyExtractor.IsTegraSystemCalls()) +func (mock *PropertyExtractorMock) IsTegraSystemCalls() []struct { +} { + var calls []struct { + } + mock.lockIsTegraSystem.RLock() + calls = mock.calls.IsTegraSystem + mock.lockIsTegraSystem.RUnlock() + return calls +} diff --git a/pkg/nvlib/info/root.go b/pkg/nvlib/info/root.go new file mode 100644 index 0000000..d38dc73 --- /dev/null +++ b/pkg/nvlib/info/root.go @@ -0,0 +1,86 @@ +/** +# Copyright 2024 NVIDIA CORPORATION +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +**/ + +package info + +import ( + "fmt" + "path/filepath" + + "github.com/NVIDIA/go-nvml/pkg/dl" +) + +// root represents a directory on the filesystem relative to which libraries +// such as the NVIDIA driver libraries can be found. +type root string + +func (r root) join(parts ...string) string { + return filepath.Join(append([]string{string(r)}, parts...)...) +} + +// assertHasLibrary returns an error if the specified library cannot be loaded. +func (r root) assertHasLibrary(libraryName string) error { + const ( + libraryLoadFlags = dl.RTLD_LAZY + ) + lib := dl.New(r.tryResolveLibrary(libraryName), libraryLoadFlags) + if err := lib.Open(); err != nil { + return err + } + defer lib.Close() + + return nil +} + +// tryResolveLibrary attempts to locate the specified library in the root. +// If the root is not specified, is "/", or the library cannot be found in the +// set of predefined paths, the input is returned as is. +func (r root) tryResolveLibrary(libraryName string) string { + if r == "" || r == "/" { + return libraryName + } + + librarySearchPaths := []string{ + "/usr/lib64", + "/usr/lib/x86_64-linux-gnu", + "/usr/lib/aarch64-linux-gnu", + "/lib64", + "/lib/x86_64-linux-gnu", + "/lib/aarch64-linux-gnu", + } + + for _, d := range librarySearchPaths { + l := r.join(d, libraryName) + resolved, err := resolveLink(l) + if err != nil { + continue + } + return resolved + } + + return libraryName +} + +// resolveLink finds the target of a symlink or the file itself in the +// case of a regular file. +// This is equivalent to running `readlink -f ${l}`. +func resolveLink(l string) (string, error) { + resolved, err := filepath.EvalSymlinks(l) + if err != nil { + return "", fmt.Errorf("error resolving link '%v': %w", l, err) + } + return resolved, nil +} From d1e08f17ea893eead136008184e0e94392918cc0 Mon Sep 17 00:00:00 2001 From: Evan Lezar <elezar@nvidia.com> Date: Tue, 26 Mar 2024 10:46:42 +0200 Subject: [PATCH 3/4] Add UsesOnlyNVGPUModule check to PropertyExtractor interface Signed-off-by: Evan Lezar <elezar@nvidia.com> --- pkg/nvlib/info/api.go | 1 + pkg/nvlib/info/builder.go | 22 +++++++- pkg/nvlib/info/options.go | 20 ++++++++ pkg/nvlib/info/property-extractor.go | 61 ++++++++++++++++++++++- pkg/nvlib/info/property-extractor_mock.go | 45 +++++++++++++++-- 5 files changed, 142 insertions(+), 7 deletions(-) diff --git a/pkg/nvlib/info/api.go b/pkg/nvlib/info/api.go index 41b8e22..b466bcb 100644 --- a/pkg/nvlib/info/api.go +++ b/pkg/nvlib/info/api.go @@ -31,4 +31,5 @@ type PropertyExtractor interface { HasTegraFiles() (bool, string) // Deprecated: Use HasTegraFiles instead. IsTegraSystem() (bool, string) + UsesOnlyNVGPUModule() (bool, string) } diff --git a/pkg/nvlib/info/builder.go b/pkg/nvlib/info/builder.go index d9275ca..bf2dd89 100644 --- a/pkg/nvlib/info/builder.go +++ b/pkg/nvlib/info/builder.go @@ -16,8 +16,16 @@ package info +import ( + "github.com/NVIDIA/go-nvml/pkg/nvml" + + "github.com/NVIDIA/go-nvlib/pkg/nvlib/device" +) + type options struct { - root root + root root + nvmllib nvml.Interface + devicelib device.Interface } // New creates a new instance of the 'info' Interface. @@ -29,7 +37,17 @@ func New(opts ...Option) Interface { if o.root == "" { o.root = "/" } + if o.nvmllib == nil { + o.nvmllib = nvml.New( + nvml.WithLibraryPath(o.root.tryResolveLibrary("libnvidia-ml.so.1")), + ) + } + if o.devicelib == nil { + o.devicelib = device.New(device.WithNvml(o.nvmllib)) + } return &propertyExtractor{ - root: o.root, + root: o.root, + nvmllib: o.nvmllib, + devicelib: o.devicelib, } } diff --git a/pkg/nvlib/info/options.go b/pkg/nvlib/info/options.go index c4265d1..f8b47aa 100644 --- a/pkg/nvlib/info/options.go +++ b/pkg/nvlib/info/options.go @@ -16,9 +16,29 @@ package info +import ( + "github.com/NVIDIA/go-nvml/pkg/nvml" + + "github.com/NVIDIA/go-nvlib/pkg/nvlib/device" +) + // Option defines a function for passing options to the New() call. type Option func(*options) +// WithDeviceLib sets the device library for the library. +func WithDeviceLib(devicelib device.Interface) Option { + return func(l *options) { + l.devicelib = devicelib + } +} + +// WithNvmlLib sets the nvml library for the library. +func WithNvmlLib(nvmllib nvml.Interface) Option { + return func(l *options) { + l.nvmllib = nvmllib + } +} + // WithRoot provides a Option to set the root of the 'info' interface. func WithRoot(r string) Option { return func(i *options) { diff --git a/pkg/nvlib/info/property-extractor.go b/pkg/nvlib/info/property-extractor.go index 9e41a54..43ec3b8 100644 --- a/pkg/nvlib/info/property-extractor.go +++ b/pkg/nvlib/info/property-extractor.go @@ -20,10 +20,16 @@ import ( "fmt" "os" "strings" + + "github.com/NVIDIA/go-nvml/pkg/nvml" + + "github.com/NVIDIA/go-nvlib/pkg/nvlib/device" ) type propertyExtractor struct { - root root + root root + nvmllib nvml.Interface + devicelib device.Interface } var _ Interface = &propertyExtractor{} @@ -82,3 +88,56 @@ func (i *propertyExtractor) HasTegraFiles() (bool, string) { return false, fmt.Sprintf("%v has no 'tegra' prefix", tegraFamilyFile) } + +// UsesOnlyNVGPUModule checks whether the only the nvgpu module is used. +// This kernel module is used on Tegra-based systems when using the iGPU. +// Since some of these systems also support NVML, we use the device name +// reported by NVML to determine whether the system is an iGPU system. +// +// Devices that use the nvgpu module have their device names as: +// +// GPU 0: Orin (nvgpu) (UUID: 54d0709b-558d-5a59-9c65-0c5fc14a21a4) +// +// This function returns true if ALL devices use the nvgpu module. +func (i *propertyExtractor) UsesOnlyNVGPUModule() (uses bool, reason string) { + // We ensure that this function never panics + defer func() { + if err := recover(); err != nil { + uses = false + reason = fmt.Sprintf("panic: %v", err) + } + }() + + ret := i.nvmllib.Init() + if ret != nvml.SUCCESS { + return false, fmt.Sprintf("failed to initialize nvml: %v", ret) + } + defer func() { + _ = i.nvmllib.Shutdown() + }() + + var names []string + + err := i.devicelib.VisitDevices(func(i int, d device.Device) error { + name, ret := d.GetName() + if ret != nvml.SUCCESS { + return fmt.Errorf("device %v: %v", i, ret) + } + names = append(names, name) + return nil + }) + if err != nil { + return false, fmt.Sprintf("failed to get device names: %v", err) + } + + if len(names) == 0 { + return false, "no devices found" + } + + for _, name := range names { + if !strings.Contains(name, "(nvgpu)") { + return false, fmt.Sprintf("device %q does not use nvgpu module", name) + } + } + return true, "all devices use nvgpu module" +} diff --git a/pkg/nvlib/info/property-extractor_mock.go b/pkg/nvlib/info/property-extractor_mock.go index 570dfe3..f2b057e 100644 --- a/pkg/nvlib/info/property-extractor_mock.go +++ b/pkg/nvlib/info/property-extractor_mock.go @@ -29,6 +29,9 @@ var _ PropertyExtractor = &PropertyExtractorMock{} // IsTegraSystemFunc: func() (bool, string) { // panic("mock out the IsTegraSystem method") // }, +// UsesOnlyNVGPUModuleFunc: func() (bool, string) { +// panic("mock out the UsesOnlyNVGPUModule method") +// }, // } // // // use mockedPropertyExtractor in code that requires PropertyExtractor @@ -48,6 +51,9 @@ type PropertyExtractorMock struct { // IsTegraSystemFunc mocks the IsTegraSystem method. IsTegraSystemFunc func() (bool, string) + // UsesOnlyNVGPUModuleFunc mocks the UsesOnlyNVGPUModule method. + UsesOnlyNVGPUModuleFunc func() (bool, string) + // calls tracks calls to the methods. calls struct { // HasDXCore holds details about calls to the HasDXCore method. @@ -62,11 +68,15 @@ type PropertyExtractorMock struct { // IsTegraSystem holds details about calls to the IsTegraSystem method. IsTegraSystem []struct { } + // UsesOnlyNVGPUModule holds details about calls to the UsesOnlyNVGPUModule method. + UsesOnlyNVGPUModule []struct { + } } - lockHasDXCore sync.RWMutex - lockHasNvml sync.RWMutex - lockHasTegraFiles sync.RWMutex - lockIsTegraSystem sync.RWMutex + lockHasDXCore sync.RWMutex + lockHasNvml sync.RWMutex + lockHasTegraFiles sync.RWMutex + lockIsTegraSystem sync.RWMutex + lockUsesOnlyNVGPUModule sync.RWMutex } // HasDXCore calls HasDXCoreFunc. @@ -176,3 +186,30 @@ func (mock *PropertyExtractorMock) IsTegraSystemCalls() []struct { mock.lockIsTegraSystem.RUnlock() return calls } + +// UsesOnlyNVGPUModule calls UsesOnlyNVGPUModuleFunc. +func (mock *PropertyExtractorMock) UsesOnlyNVGPUModule() (bool, string) { + if mock.UsesOnlyNVGPUModuleFunc == nil { + panic("PropertyExtractorMock.UsesOnlyNVGPUModuleFunc: method is nil but PropertyExtractor.UsesOnlyNVGPUModule was just called") + } + callInfo := struct { + }{} + mock.lockUsesOnlyNVGPUModule.Lock() + mock.calls.UsesOnlyNVGPUModule = append(mock.calls.UsesOnlyNVGPUModule, callInfo) + mock.lockUsesOnlyNVGPUModule.Unlock() + return mock.UsesOnlyNVGPUModuleFunc() +} + +// UsesOnlyNVGPUModuleCalls gets all the calls that were made to UsesOnlyNVGPUModule. +// Check the length with: +// +// len(mockedPropertyExtractor.UsesOnlyNVGPUModuleCalls()) +func (mock *PropertyExtractorMock) UsesOnlyNVGPUModuleCalls() []struct { +} { + var calls []struct { + } + mock.lockUsesOnlyNVGPUModule.RLock() + calls = mock.calls.UsesOnlyNVGPUModule + mock.lockUsesOnlyNVGPUModule.RUnlock() + return calls +} From 21c8f035ca66b29da378c3bb51e5c94dbcac4e73 Mon Sep 17 00:00:00 2001 From: Evan Lezar <elezar@nvidia.com> Date: Tue, 26 Mar 2024 10:55:59 +0200 Subject: [PATCH 4/4] Add ResolvePlatform function to info package Signed-off-by: Evan Lezar <elezar@nvidia.com> --- pkg/nvlib/info/api.go | 6 ++ pkg/nvlib/info/builder.go | 35 +++++++-- pkg/nvlib/info/logger.go | 28 +++++++ pkg/nvlib/info/options.go | 31 +++++++- pkg/nvlib/info/property-extractor.go | 2 +- pkg/nvlib/info/resolver.go | 64 ++++++++++++++++ pkg/nvlib/info/resolver_test.go | 110 +++++++++++++++++++++++++++ 7 files changed, 266 insertions(+), 10 deletions(-) create mode 100644 pkg/nvlib/info/logger.go create mode 100644 pkg/nvlib/info/resolver.go create mode 100644 pkg/nvlib/info/resolver_test.go diff --git a/pkg/nvlib/info/api.go b/pkg/nvlib/info/api.go index b466bcb..1c62d63 100644 --- a/pkg/nvlib/info/api.go +++ b/pkg/nvlib/info/api.go @@ -18,9 +18,15 @@ package info // Interface provides the API to the info package. type Interface interface { + PlatformResolver PropertyExtractor } +// PlatformResolver defines a function to resolve the current platform. +type PlatformResolver interface { + ResolvePlatform() Platform +} + // PropertyExtractor provides a set of functions to query capabilities of the // system. // diff --git a/pkg/nvlib/info/builder.go b/pkg/nvlib/info/builder.go index bf2dd89..87f20f0 100644 --- a/pkg/nvlib/info/builder.go +++ b/pkg/nvlib/info/builder.go @@ -22,18 +22,30 @@ import ( "github.com/NVIDIA/go-nvlib/pkg/nvlib/device" ) +type infolib struct { + PropertyExtractor + PlatformResolver +} + type options struct { + logger basicLogger root root nvmllib nvml.Interface devicelib device.Interface + + platform Platform + propertyExtractor PropertyExtractor } -// New creates a new instance of the 'info' Interface. +// New creates a new instance of the 'info' interface. func New(opts ...Option) Interface { o := &options{} for _, opt := range opts { opt(o) } + if o.logger == nil { + o.logger = &nullLogger{} + } if o.root == "" { o.root = "/" } @@ -45,9 +57,22 @@ func New(opts ...Option) Interface { if o.devicelib == nil { o.devicelib = device.New(device.WithNvml(o.nvmllib)) } - return &propertyExtractor{ - root: o.root, - nvmllib: o.nvmllib, - devicelib: o.devicelib, + if o.platform == "" { + o.platform = PlatformAuto + } + if o.propertyExtractor == nil { + o.propertyExtractor = &propertyExtractor{ + root: o.root, + nvmllib: o.nvmllib, + devicelib: o.devicelib, + } + } + return &infolib{ + PlatformResolver: &platformResolver{ + logger: o.logger, + platform: o.platform, + propertyExtractor: o.propertyExtractor, + }, + PropertyExtractor: o.propertyExtractor, } } diff --git a/pkg/nvlib/info/logger.go b/pkg/nvlib/info/logger.go new file mode 100644 index 0000000..6a6f74e --- /dev/null +++ b/pkg/nvlib/info/logger.go @@ -0,0 +1,28 @@ +/** +# Copyright 2024 NVIDIA CORPORATION +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +**/ + +package info + +type basicLogger interface { + Debugf(string, ...interface{}) + Infof(string, ...interface{}) +} + +type nullLogger struct{} + +func (n *nullLogger) Debugf(string, ...interface{}) {} + +func (n *nullLogger) Infof(string, ...interface{}) {} diff --git a/pkg/nvlib/info/options.go b/pkg/nvlib/info/options.go index f8b47aa..e05c2bf 100644 --- a/pkg/nvlib/info/options.go +++ b/pkg/nvlib/info/options.go @@ -27,15 +27,22 @@ type Option func(*options) // WithDeviceLib sets the device library for the library. func WithDeviceLib(devicelib device.Interface) Option { - return func(l *options) { - l.devicelib = devicelib + return func(i *options) { + i.devicelib = devicelib + } +} + +// WithLogger sets the logger for the library. +func WithLogger(logger basicLogger) Option { + return func(i *options) { + i.logger = logger } } // WithNvmlLib sets the nvml library for the library. func WithNvmlLib(nvmllib nvml.Interface) Option { - return func(l *options) { - l.nvmllib = nvmllib + return func(i *options) { + i.nvmllib = nvmllib } } @@ -45,3 +52,19 @@ func WithRoot(r string) Option { i.root = root(r) } } + +// WithPropertyExtractor provides an Option to set the PropertyExtractor +// interface implementation. +// This is predominantly used for testing. +func WithPropertyExtractor(propertyExtractor PropertyExtractor) Option { + return func(i *options) { + i.propertyExtractor = propertyExtractor + } +} + +// WithPlatform provides an option to set the platform explicitly. +func WithPlatform(platform Platform) Option { + return func(i *options) { + i.platform = platform + } +} diff --git a/pkg/nvlib/info/property-extractor.go b/pkg/nvlib/info/property-extractor.go index 43ec3b8..5d5d97c 100644 --- a/pkg/nvlib/info/property-extractor.go +++ b/pkg/nvlib/info/property-extractor.go @@ -32,7 +32,7 @@ type propertyExtractor struct { devicelib device.Interface } -var _ Interface = &propertyExtractor{} +var _ PropertyExtractor = &propertyExtractor{} // HasDXCore returns true if DXCore is detected on the system. func (i *propertyExtractor) HasDXCore() (bool, string) { diff --git a/pkg/nvlib/info/resolver.go b/pkg/nvlib/info/resolver.go new file mode 100644 index 0000000..1aeb04c --- /dev/null +++ b/pkg/nvlib/info/resolver.go @@ -0,0 +1,64 @@ +/** +# Copyright 2024 NVIDIA CORPORATION +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +**/ + +package info + +// Platform represents a supported plaform. +type Platform string + +const ( + PlatformAuto = Platform("auto") + PlatformNVML = Platform("nvml") + PlatformTegra = Platform("tegra") + PlatformWSL = Platform("wsl") + PlatformUnknown = Platform("unknown") +) + +type platformResolver struct { + logger basicLogger + platform Platform + propertyExtractor PropertyExtractor +} + +func (p platformResolver) ResolvePlatform() Platform { + if p.platform != PlatformAuto { + p.logger.Infof("Using requested platform '%s'", p.platform) + return p.platform + } + + hasDXCore, reason := p.propertyExtractor.HasDXCore() + p.logger.Debugf("Is WSL-based system? %v: %v", hasDXCore, reason) + + hasTegraFiles, reason := p.propertyExtractor.HasTegraFiles() + p.logger.Debugf("Is Tegra-based system? %v: %v", hasTegraFiles, reason) + + hasNVML, reason := p.propertyExtractor.HasNvml() + p.logger.Debugf("Is NVML-based system? %v: %v", hasNVML, reason) + + usesOnlyNVGPUModule, reason := p.propertyExtractor.UsesOnlyNVGPUModule() + p.logger.Debugf("Uses nvgpu kernel module? %v: %v", usesOnlyNVGPUModule, reason) + + switch { + case hasDXCore: + return PlatformWSL + case (hasTegraFiles && !hasNVML), usesOnlyNVGPUModule: + return PlatformTegra + case hasNVML: + return PlatformNVML + default: + return PlatformUnknown + } +} diff --git a/pkg/nvlib/info/resolver_test.go b/pkg/nvlib/info/resolver_test.go new file mode 100644 index 0000000..611ec54 --- /dev/null +++ b/pkg/nvlib/info/resolver_test.go @@ -0,0 +1,110 @@ +/** +# Copyright (c) NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +**/ + +package info + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestResolvePlatform(t *testing.T) { + testCases := []struct { + platform string + hasTegraFiles bool + hasDXCore bool + hasNVML bool + usesOnlyNVGPUModule bool + expected string + }{ + { + platform: "auto", + hasDXCore: true, + expected: "wsl", + }, + { + platform: "auto", + hasDXCore: false, + hasTegraFiles: true, + hasNVML: false, + expected: "tegra", + }, + { + platform: "auto", + hasDXCore: false, + hasTegraFiles: false, + hasNVML: false, + expected: "unknown", + }, + { + platform: "auto", + hasDXCore: false, + hasTegraFiles: true, + hasNVML: true, + expected: "nvml", + }, + { + platform: "auto", + hasDXCore: false, + hasTegraFiles: true, + hasNVML: true, + usesOnlyNVGPUModule: true, + expected: "tegra", + }, + { + platform: "nvml", + hasDXCore: true, + hasTegraFiles: true, + expected: "nvml", + }, + { + platform: "wsl", + hasDXCore: false, + expected: "wsl", + }, + { + platform: "not-auto", + hasDXCore: true, + expected: "not-auto", + }, + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("test case %d", i), func(t *testing.T) { + l := New( + WithPropertyExtractor(&PropertyExtractorMock{ + HasDXCoreFunc: func() (bool, string) { + return tc.hasDXCore, "" + }, + HasNvmlFunc: func() (bool, string) { + return tc.hasNVML, "" + }, + HasTegraFilesFunc: func() (bool, string) { + return tc.hasTegraFiles, "" + }, + UsesOnlyNVGPUModuleFunc: func() (bool, string) { + return tc.usesOnlyNVGPUModule, "" + }, + }), + WithPlatform(Platform(tc.platform)), + ) + + require.Equal(t, Platform(tc.expected), l.ResolvePlatform()) + }) + } +}