Skip to content

Commit ce8252b

Browse files
committed
review feedback
1 parent 74d6200 commit ce8252b

8 files changed

Lines changed: 182 additions & 33 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ full-smoke-test: ci fast-build
213213
build-linux-amd:
214214
$(call print-target)
215215
echo ${VERSION}
216-
GOOS=linux GOARCH=amd64 go build -o brev -ldflags "-X github.com/brevdev/brev-cli/pkg/cmd/version.Version=${VERSION}"
216+
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o brev -ldflags "-X github.com/brevdev/brev-cli/pkg/cmd/version.Version=${VERSION}"
217217

218218
.PHONY: build-darwin-amd
219219
build-darwin-amd:

pkg/cmd/register/gpu_nvml.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ func probeGPUsNVML() ([]GPU, []Interconnect) {
8585
arch := ""
8686
major, minor, ret := device.GetCudaComputeCapability()
8787
if ret == nvml.SUCCESS {
88-
if name := archName(major, minor); name != "" {
89-
arch = name
88+
if archStr := archName(major, minor); archStr != "" {
89+
arch = archStr
9090
} else {
9191
arch = fmt.Sprintf("sm_%d%d", major, minor)
9292
}
@@ -124,14 +124,17 @@ func probeGPUsNVML() ([]GPU, []Interconnect) {
124124
return gpus, interconnects
125125
}
126126

127+
// maxNVLinks is the maximum number of NVLink links to probe per device.
128+
// Blackwell supports up to 18.
129+
const maxNVLinks = 18
130+
127131
// probeNVLink checks NVLink connections for a device.
128132
func probeNVLink(device nvml.Device, deviceIdx int) []Interconnect {
129133
var ics []Interconnect
130134
activeLinks := 0
131135

132-
// NVLink link count varies by architecture; try up to 18 links.
133136
var nvlinkVersion uint32
134-
for link := 0; link < 18; link++ {
137+
for link := 0; link < maxNVLinks; link++ {
135138
state, ret := device.GetNvLinkState(link)
136139
if ret != nvml.SUCCESS {
137140
break

pkg/cmd/register/hardware.go

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ type HardwareProfile struct {
5353

5454
// FormatHardwareProfile returns a human-readable summary of the hardware profile.
5555
func FormatHardwareProfile(s *HardwareProfile) string {
56+
if s == nil {
57+
return ""
58+
}
5659
var b strings.Builder
5760
if s.ProductName != "" {
5861
_, _ = fmt.Fprintf(&b, " Product: %s\n", s.ProductName)
@@ -63,6 +66,26 @@ func FormatHardwareProfile(s *HardwareProfile) string {
6366
if s.RAMBytes != nil {
6467
_, _ = fmt.Fprintf(&b, " RAM: %.1f GB\n", float64(*s.RAMBytes)/(1024*1024*1024))
6568
}
69+
parseGPUs(s, b)
70+
_, _ = fmt.Fprintf(&b, " Arch: %s\n", s.Architecture)
71+
if s.OS != "" || s.OSVersion != "" {
72+
_, _ = fmt.Fprintf(&b, " OS: %s %s\n", s.OS, s.OSVersion)
73+
}
74+
parseInterconnects(s, b)
75+
for _, st := range s.Storage {
76+
_, _ = fmt.Fprintf(&b, " Storage: %.1f GB", float64(st.StorageBytes)/(1024*1024*1024))
77+
if st.StorageType != "" {
78+
_, _ = fmt.Fprintf(&b, " (%s)", st.StorageType)
79+
}
80+
if st.Name != "" {
81+
_, _ = fmt.Fprintf(&b, " [%s]", st.Name)
82+
}
83+
b.WriteString("\n")
84+
}
85+
return b.String()
86+
}
87+
88+
func parseGPUs(s *HardwareProfile, b strings.Builder) {
6689
for _, gpu := range s.GPUs {
6790
if gpu.MemoryBytes != nil {
6891
memGB := float64(*gpu.MemoryBytes) / (1024 * 1024 * 1024)
@@ -75,12 +98,19 @@ func FormatHardwareProfile(s *HardwareProfile) string {
7598
}
7699
b.WriteString("\n")
77100
}
78-
_, _ = fmt.Fprintf(&b, " Arch: %s\n", s.Architecture)
79-
if s.OS != "" || s.OSVersion != "" {
80-
_, _ = fmt.Fprintf(&b, " OS: %s %s\n", s.OS, s.OSVersion)
81-
}
101+
}
102+
103+
func parseInterconnects(s *HardwareProfile, b strings.Builder) {
82104
for _, ic := range s.Interconnects {
83105
_, _ = fmt.Fprintf(&b, " Link: %s", ic.Type)
106+
if ic.Generation > 0 || ic.Width > 0 {
107+
if ic.Generation > 0 {
108+
_, _ = fmt.Fprintf(&b, " Gen%d", ic.Generation)
109+
}
110+
if ic.Width > 0 {
111+
_, _ = fmt.Fprintf(&b, " x%d", ic.Width)
112+
}
113+
}
84114
if ic.Device != "" {
85115
_, _ = fmt.Fprintf(&b, " (%s)", ic.Device)
86116
}
@@ -89,17 +119,6 @@ func FormatHardwareProfile(s *HardwareProfile) string {
89119
}
90120
b.WriteString("\n")
91121
}
92-
for _, st := range s.Storage {
93-
_, _ = fmt.Fprintf(&b, " Storage: %.1f GB", float64(st.StorageBytes)/(1024*1024*1024))
94-
if st.StorageType != "" {
95-
_, _ = fmt.Fprintf(&b, " (%s)", st.StorageType)
96-
}
97-
if st.Name != "" {
98-
_, _ = fmt.Fprintf(&b, " [%s]", st.Name)
99-
}
100-
b.WriteString("\n")
101-
}
102-
return b.String()
103122
}
104123

105124
// --- Content-parsing helpers (pure functions, used by Linux adapter and tests) ---
@@ -120,6 +139,8 @@ func parseCPUCountContent(content string) (int, error) {
120139
}
121140

122141
// parseMemInfoContent parses the content of /proc/meminfo.
142+
// Not used by the current Linux profiler (which uses syscall.Sysinfo), but
143+
// retained as a tested pure-function fallback for environments without sysinfo.
123144
func parseMemInfoContent(content string) (int64, error) {
124145
scanner := bufio.NewScanner(strings.NewReader(content))
125146
for scanner.Scan() {
@@ -163,6 +184,8 @@ func unquote(s string) string {
163184

164185
// parseStorageOutput parses lsblk output (NAME,SIZE,TYPE,ROTA columns),
165186
// returning one StorageDevice entry per disk device. ROTA=0 → SSD, ROTA=1 → HDD.
187+
// Not used by the current Linux profiler (which uses probeStorageSysfs), but
188+
// retained as a tested pure-function fallback for environments without sysfs.
166189
func parseStorageOutput(output string) []StorageDevice {
167190
var devices []StorageDevice
168191
scanner := bufio.NewScanner(strings.NewReader(output))

pkg/cmd/register/hardware_darwin.go

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package register
44

55
import (
6+
"log"
67
"os/exec"
78
"regexp"
89
"runtime"
@@ -28,6 +29,8 @@ func (p *SystemHardwareProfiler) Profile() (*HardwareProfile, error) {
2829
if memSize, err := unix.SysctlUint64("hw.memsize"); err == nil {
2930
ramBytes := int64(memSize)
3031
hw.RAMBytes = &ramBytes
32+
} else {
33+
log.Printf("hardware profiler: failed to read memory size: %v", err)
3134
}
3235

3336
hw.OS = readDarwinCommand("sw_vers", "-productName")
@@ -137,21 +140,9 @@ func parseDiskutilSize(val string, dev *StorageDevice) {
137140
cleaned := strings.ReplaceAll(m[1], ",", "")
138141
if size, err := strconv.ParseInt(cleaned, 10, 64); err == nil {
139142
dev.StorageBytes = size
140-
return
141-
}
142-
}
143-
// Fallback: extract digits from first token.
144-
parts := strings.Fields(val)
145-
if len(parts) < 1 {
146-
return
147-
}
148-
var size int64
149-
for _, c := range parts[0] {
150-
if c >= '0' && c <= '9' {
151-
size = size*10 + int64(c-'0')
152143
}
153144
}
154-
dev.StorageBytes = size
145+
// If the regex doesn't match, leave StorageBytes as 0 (unknown).
155146
}
156147

157148
func parseDiskutilSolidState(val string, dev *StorageDevice) {

pkg/cmd/register/hardware_linux.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package register
44

55
import (
66
"fmt"
7+
"log"
78
"os"
89
"path/filepath"
910
"runtime"
@@ -26,10 +27,14 @@ func (p *SystemHardwareProfiler) Profile() (*HardwareProfile, error) {
2627
if cpuCount, err := readCPUCount(); err == nil {
2728
count32 := int32(cpuCount)
2829
hw.CPUCount = &count32
30+
} else {
31+
log.Printf("hardware profiler: failed to read CPU count: %v", err)
2932
}
3033

3134
if ramBytes, err := readRAMBytes(); err == nil {
3235
hw.RAMBytes = &ramBytes
36+
} else {
37+
log.Printf("hardware profiler: failed to read RAM: %v", err)
3338
}
3439

3540
hw.OS, hw.OSVersion = readOSRelease()

pkg/cmd/register/hardware_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,29 @@ func Test_FormatHardwareProfile(t *testing.T) {
196196
}
197197
}
198198

199+
func Test_FormatHardwareProfile_Nil(t *testing.T) {
200+
output := FormatHardwareProfile(nil)
201+
if output != "" {
202+
t.Errorf("expected empty string for nil input, got: %q", output)
203+
}
204+
}
205+
206+
func Test_FormatHardwareProfile_PCIeInterconnect(t *testing.T) {
207+
s := &HardwareProfile{
208+
Architecture: "amd64",
209+
Interconnects: []Interconnect{
210+
{Type: "PCIe", Device: "GPU 0", Generation: 4, Width: 16},
211+
},
212+
}
213+
output := FormatHardwareProfile(s)
214+
if !strings.Contains(output, "PCIe Gen4 x16") {
215+
t.Errorf("expected 'PCIe Gen4 x16' in output, got: %s", output)
216+
}
217+
if !strings.Contains(output, "(GPU 0)") {
218+
t.Errorf("expected '(GPU 0)' in output, got: %s", output)
219+
}
220+
}
221+
199222
func Test_FormatHardwareProfile_MinimalFields(t *testing.T) {
200223
s := &HardwareProfile{
201224
GPUs: []GPU{

pkg/cmd/register/register_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,81 @@ func Test_runRegister_GrantSSH_no_retry_on_permanent_error(t *testing.T) {
617617
}
618618
}
619619

620+
func Test_runRegister_PlatformIncompatible(t *testing.T) {
621+
regStore := &mockRegistrationStore{}
622+
623+
store := &mockRegisterStore{
624+
user: &entity.User{ID: "user_1"},
625+
org: &entity.Organization{ID: "org_123", Name: "TestOrg"},
626+
token: "tok",
627+
}
628+
629+
svc := &fakeNodeService{}
630+
deps, server := testRegisterDeps(t, svc, regStore)
631+
defer server.Close()
632+
633+
deps.platform = mockPlatform{compatible: false}
634+
635+
term := terminal.New()
636+
err := runRegister(context.Background(), term, store, "My Spark", deps)
637+
if err == nil {
638+
t.Fatal("expected error when platform is incompatible")
639+
}
640+
if !strings.Contains(err.Error(), "only supported on Linux") {
641+
t.Errorf("expected platform incompatibility error, got: %v", err)
642+
}
643+
}
644+
645+
func Test_runRegister_HardwareProfilerFailure(t *testing.T) {
646+
regStore := &mockRegistrationStore{}
647+
648+
store := &mockRegisterStore{
649+
user: &entity.User{ID: "user_1"},
650+
org: &entity.Organization{ID: "org_123", Name: "TestOrg"},
651+
token: "tok",
652+
}
653+
654+
svc := &fakeNodeService{}
655+
deps, server := testRegisterDeps(t, svc, regStore)
656+
defer server.Close()
657+
658+
deps.hardwareProfiler = &mockHardwareProfiler{err: fmt.Errorf("nvml init failed")}
659+
660+
term := terminal.New()
661+
err := runRegister(context.Background(), term, store, "My Spark", deps)
662+
if err == nil {
663+
t.Fatal("expected error when hardware profiler fails")
664+
}
665+
if !strings.Contains(err.Error(), "hardware profile") {
666+
t.Errorf("expected hardware profile error, got: %v", err)
667+
}
668+
}
669+
670+
func Test_runRegister_NetBirdInstallFailure(t *testing.T) {
671+
regStore := &mockRegistrationStore{}
672+
673+
store := &mockRegisterStore{
674+
user: &entity.User{ID: "user_1"},
675+
org: &entity.Organization{ID: "org_123", Name: "TestOrg"},
676+
token: "tok",
677+
}
678+
679+
svc := &fakeNodeService{}
680+
deps, server := testRegisterDeps(t, svc, regStore)
681+
defer server.Close()
682+
683+
deps.netbird = mockNetBirdManager{err: fmt.Errorf("install failed")}
684+
685+
term := terminal.New()
686+
err := runRegister(context.Background(), term, store, "My Spark", deps)
687+
if err == nil {
688+
t.Fatal("expected error when NetBird install fails")
689+
}
690+
if !strings.Contains(err.Error(), "tunnel setup failed") {
691+
t.Errorf("expected tunnel setup error, got: %v", err)
692+
}
693+
}
694+
620695
func Test_runRegister_NoNameNotRegistered(t *testing.T) {
621696
regStore := &mockRegistrationStore{}
622697

pkg/cmd/register/rpcclient_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,35 @@ func Test_toProtoNodeSpec(t *testing.T) {
119119
}
120120
}
121121

122+
func Test_toProtoNodeSpec_PCIeInterconnect(t *testing.T) {
123+
local := &HardwareProfile{
124+
Architecture: "amd64",
125+
Interconnects: []Interconnect{
126+
{Type: "PCIe", Device: "GPU 0", Generation: 4, Width: 16},
127+
},
128+
}
129+
130+
proto := toProtoNodeSpec(local)
131+
132+
if len(proto.GetInterconnects()) != 1 {
133+
t.Fatalf("expected 1 interconnect, got %d", len(proto.GetInterconnects()))
134+
}
135+
ic := proto.GetInterconnects()[0]
136+
if ic.GetDevice() != "GPU 0" {
137+
t.Errorf("expected device 'GPU 0', got %s", ic.GetDevice())
138+
}
139+
pcie := ic.GetPcie()
140+
if pcie == nil {
141+
t.Fatal("expected PCIe details, got nil")
142+
}
143+
if pcie.GetGeneration() != 4 {
144+
t.Errorf("expected PCIe Gen 4, got %d", pcie.GetGeneration())
145+
}
146+
if pcie.GetWidth() != 16 {
147+
t.Errorf("expected PCIe Width 16, got %d", pcie.GetWidth())
148+
}
149+
}
150+
122151
func Test_toProtoNodeSpec_Nil(t *testing.T) {
123152
if toProtoNodeSpec(nil) != nil {
124153
t.Error("expected nil for nil input")

0 commit comments

Comments
 (0)