Files
tyto/backend/internal/collectors/gpu/manager_test.go
vikingowl a0a947094d feat: add multi-GPU support and operational modes
Multi-GPU Collection System:
- Add modular GPU collector architecture in collectors/gpu/
- Support AMD (amdgpu), NVIDIA (nvidia-smi), and Intel (i915/xe) GPUs
- GPU Manager auto-detects and aggregates all vendor collectors
- Backward-compatible JSON output for existing frontend

Operational Modes:
- Standalone mode (default): single-host monitoring, no database
- Server mode: multi-device with database, auth, agents (WIP)
- Agent mode: lightweight reporter to central server (WIP)
- Mode selection via TYTO_MODE env var or config.yaml

Configuration Updates:
- Add server config (gRPC port, mTLS settings, registration)
- Add agent config (ID, server URL, TLS certificates)
- Add database config (SQLite/PostgreSQL support)
- Support TYTO_* prefixed environment variables

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 07:21:50 +01:00

154 lines
3.6 KiB
Go

package gpu
import (
"os"
"path/filepath"
"testing"
)
func TestManager_NoGPUs(t *testing.T) {
tmpDir := t.TempDir()
manager := NewManager(tmpDir)
if manager.Available() {
t.Error("Expected no GPUs available")
}
if manager.GPUCount() != 0 {
t.Errorf("Expected 0 GPUs, got %d", manager.GPUCount())
}
stats, err := manager.Collect()
if err != nil {
t.Fatalf("Collect failed: %v", err)
}
if stats.Available {
t.Error("Expected stats.Available to be false")
}
if len(stats.GPUs) != 0 {
t.Errorf("Expected 0 GPUs in stats, got %d", len(stats.GPUs))
}
}
func TestManager_WithAMDGPU(t *testing.T) {
tmpDir := t.TempDir()
sysPath := filepath.Join(tmpDir, "sys")
// Create mock AMD GPU
gpuPath := filepath.Join(sysPath, "class/drm/card0/device")
if err := os.MkdirAll(gpuPath, 0755); err != nil {
t.Fatal(err)
}
driverTarget := filepath.Join(tmpDir, "drivers/amdgpu")
if err := os.MkdirAll(driverTarget, 0755); err != nil {
t.Fatal(err)
}
if err := os.Symlink(driverTarget, filepath.Join(gpuPath, "driver")); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(gpuPath, "gpu_busy_percent"), []byte("42\n"), 0644); err != nil {
t.Fatal(err)
}
manager := NewManager(sysPath)
if !manager.Available() {
t.Error("Expected GPU to be available")
}
if manager.GPUCount() != 1 {
t.Errorf("Expected 1 GPU, got %d", manager.GPUCount())
}
stats, err := manager.Collect()
if err != nil {
t.Fatalf("Collect failed: %v", err)
}
if !stats.Available {
t.Error("Expected stats.Available to be true")
}
if len(stats.GPUs) != 1 {
t.Fatalf("Expected 1 GPU in stats, got %d", len(stats.GPUs))
}
if stats.GPUs[0].Index != 0 {
t.Errorf("Expected index 0, got %d", stats.GPUs[0].Index)
}
if stats.GPUs[0].Utilization != 42 {
t.Errorf("Expected utilization 42, got %d", stats.GPUs[0].Utilization)
}
}
func TestManager_MixedVendors(t *testing.T) {
tmpDir := t.TempDir()
sysPath := filepath.Join(tmpDir, "sys")
// Create AMD GPU (card0)
amdPath := filepath.Join(sysPath, "class/drm/card0/device")
if err := os.MkdirAll(amdPath, 0755); err != nil {
t.Fatal(err)
}
amdDriver := filepath.Join(tmpDir, "drivers/amdgpu")
if err := os.MkdirAll(amdDriver, 0755); err != nil {
t.Fatal(err)
}
if err := os.Symlink(amdDriver, filepath.Join(amdPath, "driver")); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(amdPath, "gpu_busy_percent"), []byte("50\n"), 0644); err != nil {
t.Fatal(err)
}
// Create Intel GPU (card1)
intelPath := filepath.Join(sysPath, "class/drm/card1/device")
if err := os.MkdirAll(intelPath, 0755); err != nil {
t.Fatal(err)
}
intelDriver := filepath.Join(tmpDir, "drivers/i915")
if err := os.MkdirAll(intelDriver, 0755); err != nil {
t.Fatal(err)
}
if err := os.Symlink(intelDriver, filepath.Join(intelPath, "driver")); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(intelPath, "gt_cur_freq_mhz"), []byte("1000\n"), 0644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(intelPath, "gt_max_freq_mhz"), []byte("1500\n"), 0644); err != nil {
t.Fatal(err)
}
manager := NewManager(sysPath)
if !manager.Available() {
t.Error("Expected GPUs to be available")
}
if manager.GPUCount() != 2 {
t.Errorf("Expected 2 GPUs, got %d", manager.GPUCount())
}
stats, err := manager.Collect()
if err != nil {
t.Fatalf("Collect failed: %v", err)
}
if len(stats.GPUs) != 2 {
t.Fatalf("Expected 2 GPUs in stats, got %d", len(stats.GPUs))
}
// Verify indices are sequential
for i, gpu := range stats.GPUs {
if gpu.Index != i {
t.Errorf("GPU %d has index %d, expected %d", i, gpu.Index, i)
}
}
}