Skip to content

Comments

feat: replace Linux FFI with pure Dart cgroup implementation#15

Merged
thiagoalmeidasa merged 21 commits intomainfrom
pure-dart-system-resources
Feb 6, 2026
Merged

feat: replace Linux FFI with pure Dart cgroup implementation#15
thiagoalmeidasa merged 21 commits intomainfrom
pure-dart-system-resources

Conversation

@thiagoalmeidasa
Copy link
Collaborator

@thiagoalmeidasa thiagoalmeidasa commented Feb 3, 2026

Summary

  • Replace Linux native FFI bindings with a pure Dart implementation that reads cgroup v1/v2 files directly, eliminating glibc dependencies and enabling gVisor compatibility
  • Keep macOS FFI with pre-built dylibs (macOS has no cgroup equivalent)
  • Simplify CI by removing Linux native binary builds; only macOS binaries are rebuilt automatically

What's included

  • Pure Dart cgroup readers: CpuMonitor (CPU accounting via cpu.stat / cpuacct.usage), MemoryMonitor (memory via memory.current / memory.usage_in_bytes), with /proc/loadavg and /proc/meminfo fallbacks for non-container Linux hosts
  • Platform detection: PlatformDetector with DetectedPlatform enum (macOS, linuxCgroupV2, linuxCgroupV1, linuxHost, unsupported) for flat single-switch dispatch across all SystemResources methods
  • Cgroup path resolution: Parses /proc/self/cgroup to resolve the process's actual cgroup v2 directory, fixing memory metrics on native Linux hosts where memory.max/memory.current don't exist at root
  • macOS FFI wrapper: MacOsNative class isolating all FFI bindings, loaded only when running on macOS
  • CPU normalization fix: getLoad() now delegates to getLimitCores() for the full fallback chain (cgroup limit -> SYSRES_CPU_CORES env var -> Platform.numberOfProcessors), fixing ~50% underreporting in gVisor
  • Domain-oriented naming: Renamed files and classes from implementation names to domain names (cgroup_cpu -> cpu_monitor, cgroup_memory -> memory_monitor, cgroup_detector -> platform_detector, macos_ffi -> macos_native)
  • CI simplification: Merged binary rebuild into main CI workflow; Linux tests no longer need make or native compilation

Usage

import 'package:system_resources_2/system_resources_2.dart';

void main() async {
  // Initialize (required for macOS FFI, no-op on Linux)
  await SystemResources.init();

  // Container detection
  print('Container: ${SystemResources.isContainerEnv()}');
  print('Cgroup version: ${SystemResources.cgroupVersion()}');

  // CPU metrics (auto-detects container limits)
  print('CPU Load: ${(SystemResources.cpuLoadAvg() * 100).toStringAsFixed(1)}%');
  print('CPU Limit: ${SystemResources.cpuLimitCores()} cores');

  // Memory metrics (auto-detects container limits)
  print('Memory Usage: ${(SystemResources.memUsage() * 100).toStringAsFixed(1)}%');
  print('Memory Limit: ${SystemResources.memoryLimitBytes() ~/ 1024 ~/ 1024} MB');
  print('Memory Used: ${SystemResources.memoryUsedBytes() ~/ 1024 ~/ 1024} MB');
}

Test plan

  • dart test passes on macOS (arm64 and Intel)
  • dart test passes on Linux (amd64 and arm64)
  • Container tests pass with --memory and --cpus limits (cgroup v2)
  • CpuMonitor unit tests verify limit resolution fallback chain
  • CI green on all matrix targets (native + container)

thiagoalmeidasa and others added 17 commits February 2, 2026 17:55
- 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
- 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).
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.
- 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.
- 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
- 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
- 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
…e 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
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.
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.
@thiagoalmeidasa thiagoalmeidasa changed the title Pure dart system resources feat: replace Linux FFI with pure Dart cgroup implementation Feb 6, 2026
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.
@serverpod serverpod deleted a comment from github-actions bot Feb 6, 2026
@serverpod serverpod deleted a comment from github-actions bot Feb 6, 2026
@serverpod serverpod deleted a comment from github-actions bot Feb 6, 2026
@serverpod serverpod deleted a comment from github-actions bot Feb 6, 2026
@serverpod serverpod deleted a comment from github-actions bot Feb 6, 2026
@serverpod serverpod deleted a comment from github-actions bot Feb 6, 2026
@github-actions
Copy link

github-actions bot commented Feb 6, 2026

macOS Binaries Rebuilt

The macOS native binaries have been automatically rebuilt from source.
Linux uses pure Dart implementation (no native binaries needed).

MD5 Checksums

6a52408a4a06a0639d7a124318696260  libsysres-darwin-arm64.dylib
a3e7bfe372642d42c02cfe43b832019d  libsysres-darwin-x86_64.dylib

These checksums can be used to verify the provenance of the binaries.

@thiagoalmeidasa thiagoalmeidasa marked this pull request as ready for review February 6, 2026 20:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant