Skip to content

Commit 3b1c8c9

Browse files
feat: replace Linux FFI with pure Dart cgroup implementation (#15)
* refactor: remove unused dylib.dart and fix incorrect comment - Delete lib/src/dylib.dart: unused code that duplicated library loading logic from base.dart - Fix comment in base.dart that incorrectly claimed File.existsSync() fails in distroless containers (testing proved this is false) - Update comment to reflect actual reason for direct DynamicLibrary.open(): single operation and TOCTOU race condition avoidance * fix: improve native library compatibility with older glibc - Build with gcc:10 to require only glibc 2.7 (was 2.38) - Add glibc version detection with concise error diagnostics - Test compatibility on dart:3.8, 3.9, 3.10, stable - Run compat tests after binary rebuild to use fresh binaries - Document glibc 2.7+ requirement (Ubuntu 14.04+, CentOS 7+) Tested on glibc 2.17 (CentOS 7) through 2.41 (dart:stable). * feat: add pure Dart implementation for Linux using cgroups Implement system resource monitoring without FFI on Linux: - cgroup_detector.dart: Detect cgroup v1/v2 and container environment - cgroup_cpu.dart: CPU monitoring via cgroup accounting + /proc/loadavg fallback - cgroup_memory.dart: Memory monitoring via cgroups + /proc/meminfo fallback - system_resources.dart: Public API with Serverpod-compatible methods This approach reads cgroup files directly, eliminating the need for native libraries on Linux and enabling gVisor compatibility. * refactor: hybrid implementation - pure Dart for Linux, FFI for macOS - Add macos_ffi.dart with FFI bindings for macOS only - Remove base.dart (replaced by hybrid system_resources.dart) - Remove Linux native libraries (.so files) - no longer needed - Keep macOS dylibs for FFI support Linux now uses pure Dart file I/O with no glibc dependencies. * test: update tests for hybrid implementation - Add setUpAll() to call init() for macOS FFI - Update expectations to handle both Linux and macOS - Update example to show cgroup version and new API methods * docs(ci): simplify CI for hybrid implementation - Remove Linux native builds from build-binaries.yml (macOS only) - Simplify ci.yml: Linux tests no longer need 'make' - Update Dockerfile.test with multi-stage build and compiled executable - Update README with new architecture and platform support - Update GVISOR.md to reflect cgroup-based CPU monitoring * chore: rebuild macOS native binaries * refactor: flatten OS/cgroup dispatch to single DetectedPlatform enum - Add DetectedPlatform enum (macOS, linuxCgroupV2, linuxCgroupV1, linuxHost, unsupported) and cached detectPlatform() to CgroupDetector - Make cgroup v1/v2/proc reader methods public in CgroupCpu and CgroupMemory, removing their internal version dispatch - CgroupCpu delta methods now accept reader callbacks instead of calling detectVersion() internally - All SystemResources methods use a single switch expression for dispatch - Public API surface unchanged; all tests pass * chore: rebuild macOS native binaries * ci: merge binary rebuild into CI workflow * chore: rebuild macOS native binaries * fix: resolve process's actual cgroup path for memory metrics on native Linux On native Linux hosts with systemd cgroup v2, memory.max and memory.current don't exist at the root cgroup (/sys/fs/cgroup/) — they live in the process's child cgroup (e.g. /sys/fs/cgroup/user.slice/ .../session.scope/). CPU files worked because cpu.stat exists at root. This caused memoryLimitBytes and memoryUsedBytes to return 0, failing CI on test-linux (amd64) and test-linux (arm64) while container tests passed (containers have their own cgroup namespace at root). Changes: - Parse /proc/self/cgroup to resolve the process's v2 cgroup directory, matching how JDK, .NET, and Go runtimes discover cgroup paths - Convert hardcoded v2 path constants to dynamic getters using the resolved directory - Fall back to /proc/meminfo when cgroup memory files are unreadable - Add docs/cgroup-path-resolution.md with research and rationale * chore: rebuild macOS native binaries * fix: unify CPU load normalization to respect SYSRES_CPU_CORES env var getLoad() had its own fallback logic that skipped the SYSRES_CPU_CORES environment variable when the cgroup CPU limit was unavailable, falling back directly to Platform.numberOfProcessors. This caused cpuLoadAvg() to report ~half the actual CPU utilization in gVisor environments. Delegate limit resolution in getLoad() to getLimitCores(), which correctly handles the full fallback chain: cgroup limit, SYSRES_CPU_CORES env var, then Platform.numberOfProcessors. * chore: rebuild macOS native binaries * refactor: rename files and classes from implementation to domain names Rename source files and classes to reflect domain rather than mechanism: - cgroup_cpu -> cpu_monitor (CgroupCpu -> CpuMonitor) - cgroup_memory -> memory_monitor (CgroupMemory -> MemoryMonitor) - cgroup_detector -> platform_detector (CgroupDetector -> PlatformDetector) - macos_ffi -> macos_native (MacOsFfi -> MacOsNative) Extract CpuMonitor unit tests into dedicated cpu_monitor_test.dart. * chore: rebuild macOS native binaries * fix: upsert macOS binaries PR comment instead of creating duplicates * chore: bump version to 2.2.0 and update changelog/readme Add changelog entry for 2.2.0 covering pure Dart Linux support, cgroup v1 compatibility, new APIs, and bug fixes. Update README with gVisor CPU limit guidance using SYSRES_CPU_CORES env var. * fix(ci): filter artifact download to exclude Docker cache blobs * chore: rebuild macOS native binaries --------- Co-authored-by: thiagoalmeidasa <1412716+thiagoalmeidasa@users.noreply.github.com>
1 parent b37a668 commit 3b1c8c9

30 files changed

Lines changed: 1796 additions & 977 deletions