diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7803dd0aff..936fed4b50 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -123,7 +123,10 @@ jobs: defaults: run: working-directory: runner - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] steps: - uses: actions/checkout@v4 - name: Set up Go diff --git a/runner/internal/executor/executor.go b/runner/internal/executor/executor.go index e14ce540db..c29913dcb1 100644 --- a/runner/internal/executor/executor.go +++ b/runner/internal/executor/executor.go @@ -11,6 +11,7 @@ import ( "os/exec" osuser "os/user" "path/filepath" + "runtime" "strconv" "strings" "sync" @@ -27,6 +28,12 @@ import ( "github.com/prometheus/procfs" ) +type ConnectionTracker interface { + GetNoConnectionsSecs() int64 + Track(ticker <-chan time.Time) + Stop() +} + type RunExecutor struct { tempDir string homeDir string @@ -51,9 +58,16 @@ type RunExecutor struct { timestamp *MonotonicTimestamp killDelay time.Duration - connectionTracker *connections.ConnectionTracker + connectionTracker ConnectionTracker } +// stubConnectionTracker is a no-op implementation for when procfs is not available (only required for tests on darwin) +type stubConnectionTracker struct{} + +func (s *stubConnectionTracker) GetNoConnectionsSecs() int64 { return 0 } +func (s *stubConnectionTracker) Track(ticker <-chan time.Time) {} +func (s *stubConnectionTracker) Stop() {} + func NewRunExecutor(tempDir string, homeDir string, workingDir string, sshPort int) (*RunExecutor, error) { mu := &sync.RWMutex{} timestamp := NewMonotonicTimestamp() @@ -65,15 +79,25 @@ func NewRunExecutor(tempDir string, homeDir string, workingDir string, sshPort i if err != nil { return nil, fmt.Errorf("failed to parse current user uid: %w", err) } - proc, err := procfs.NewDefaultFS() - if err != nil { - return nil, fmt.Errorf("failed to initialize procfs: %w", err) + + // Try to initialize procfs, but don't fail if it's not available (e.g., on macOS) + var connectionTracker ConnectionTracker + + if runtime.GOOS == "linux" { + proc, err := procfs.NewDefaultFS() + if err != nil { + return nil, fmt.Errorf("failed to initialize procfs: %w", err) + } + connectionTracker = connections.NewConnectionTracker(connections.ConnectionTrackerConfig{ + Port: uint64(sshPort), + MinConnDuration: 10 * time.Second, // shorter connections are likely from dstack-server + Procfs: proc, + }) + } else { + // Use stub connection tracker (only required for tests on darwin) + connectionTracker = &stubConnectionTracker{} } - connectionTracker := connections.NewConnectionTracker(connections.ConnectionTrackerConfig{ - Port: uint64(sshPort), - MinConnDuration: 10 * time.Second, // shorter connections are likely from dstack-server - Procfs: proc, - }) + return &RunExecutor{ tempDir: tempDir, homeDir: homeDir, diff --git a/runner/internal/metrics/metrics_test.go b/runner/internal/metrics/metrics_test.go index 7f280da25f..d547e2e330 100644 --- a/runner/internal/metrics/metrics_test.go +++ b/runner/internal/metrics/metrics_test.go @@ -1,6 +1,7 @@ package metrics import ( + "runtime" "testing" "github.com/dstackai/dstack/runner/internal/schemas" @@ -8,6 +9,9 @@ import ( ) func TestGetAMDGPUMetrics_OK(t *testing.T) { + if runtime.GOOS == "darwin" { + t.Skip("Skipping on macOS") + } collector, err := NewMetricsCollector() assert.NoError(t, err) @@ -39,6 +43,9 @@ func TestGetAMDGPUMetrics_OK(t *testing.T) { } func TestGetAMDGPUMetrics_ErrorGPUUtilNA(t *testing.T) { + if runtime.GOOS == "darwin" { + t.Skip("Skipping on macOS") + } collector, err := NewMetricsCollector() assert.NoError(t, err) metrics, err := collector.getAMDGPUMetrics("gpu,gfx,gfx_clock,vram_used,vram_total\n0,N/A,N/A,283,196300\n")