Meziantou.Framework.Unix.ControlGroups 2.0.1

Prefix Reserved
There is a newer version of this package available.
See the version list below for details.
dotnet add package Meziantou.Framework.Unix.ControlGroups --version 2.0.1
                    
NuGet\Install-Package Meziantou.Framework.Unix.ControlGroups -Version 2.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Meziantou.Framework.Unix.ControlGroups" Version="2.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Meziantou.Framework.Unix.ControlGroups" Version="2.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Meziantou.Framework.Unix.ControlGroups" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Meziantou.Framework.Unix.ControlGroups --version 2.0.1
                    
#r "nuget: Meziantou.Framework.Unix.ControlGroups, 2.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Meziantou.Framework.Unix.ControlGroups@2.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Meziantou.Framework.Unix.ControlGroups&version=2.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Meziantou.Framework.Unix.ControlGroups&version=2.0.1
                    
Install as a Cake Tool

Meziantou.Framework.Unix.ControlGroups

A .NET library for managing Linux Control Groups v2 (cgroups v2).

Features

  • Create, delete, and manage cgroup hierarchies
  • Add/remove processes and threads to/from cgroups
  • Configure resource limits:
    • CPU: Weight-based scheduling, bandwidth limits
    • Memory: Hard limits, soft limits, protection levels, swap control
    • IO: Weight-based scheduling, bandwidth/IOPS limits per device
    • PIDs: Limit number of processes
    • Cpuset: CPU and memory node affinity
  • Freeze/unfreeze processes in a cgroup
  • Kill all processes in a cgroup
  • Read statistics (CPU usage, memory usage, etc.)
  • Enable/disable controllers per cgroup

Requirements

  • Linux kernel 4.5+ with cgroup v2 enabled. Some APIs need a newer kernel: AssociateThread/GetThreads need 4.14, Freeze/Unfreeze/IsFrozen need 5.2, and Kill needs 5.14. The set of keys reported by GetMemoryStat also varies by version — for example swapcached needs 5.13 and kernel needs 6.0 — and MemoryStat reports null for anything the running kernel does not emit.
  • cgroup v2 filesystem mounted at /sys/fs/cgroup (default on most modern distributions)
  • Appropriate permissions to manage cgroups

Usage

Basic Operations

using Meziantou.Framework.Unix.ControlGroups;

// Get the root cgroup
var root = CGroup2.Root;

// Create a new cgroup
var myGroup = root.CreateOrGetChild("myapp");

// Add the current process to the cgroup
myGroup.AssociateProcess(Environment.ProcessId);

// Create nested cgroups
var subGroup = myGroup.CreateOrGetChild("worker1");

CPU Control

// Enable the CPU controller for the children of the root cgroup, which is where myGroup lives.
// Controllers are always enabled on the PARENT, never on the cgroup being limited.
root.SetControllers("cpu");

// Set CPU weight (relative share, 1-10000, default 100)
myGroup.SetCpuWeight(200); // 2x the default share

// Set CPU bandwidth limit (max 50% of 1 CPU)
myGroup.SetCpuMax(50_000, 100_000); // 50ms per 100ms period

// Remove limit
myGroup.RemoveCpuMax();

// Get CPU statistics
if (myGroup.GetCpuStat().TryGetValue(out var cpuStat))
{
    Console.WriteLine($"CPU Usage: {cpuStat.UsageMicroseconds} μs");
    Console.WriteLine($"User time: {cpuStat.UserMicroseconds} μs");
    Console.WriteLine($"System time: {cpuStat.SystemMicroseconds} μs");
}

Memory Control

// Enable the memory controller on the parent (see the note in "CPU Control")
root.SetControllers("memory");

// Set hard memory limit (1 GB)
myGroup.SetMemoryMax(1024L * 1024 * 1024);

// Set soft limit (throttling starts at 512 MB)
myGroup.SetMemoryHigh(512L * 1024 * 1024);

// Set memory protection (best-effort, prevents reclaim below 256 MB)
myGroup.SetMemoryLow(256L * 1024 * 1024);

// Set swap limit
myGroup.SetSwapMax(512L * 1024 * 1024);

// Get current memory usage
var currentMemory = myGroup.GetMemoryCurrent();
Console.WriteLine($"Current memory usage: {currentMemory.Value} bytes");

// Get the hard limit. null means "unlimited"; "not enabled" and "unlimited" are different answers
var max = myGroup.GetMemoryMax();
Console.WriteLine(max.State switch
{
    CGroupValueState.Configured => $"Limited to {max.Value} bytes",
    CGroupValueState.NotConfigured => "No memory limit",
    CGroupValueState.Unavailable => "The memory controller is not enabled on the parent cgroup",
    _ => $"Unexpected memory.max content: '{max.RawValue}'",
});

// Get detailed memory statistics
if (myGroup.GetMemoryStat().TryGetValue(out var memoryStat))
{
    Console.WriteLine($"Anonymous memory: {memoryStat.Anon} bytes");
    Console.WriteLine($"File cache: {memoryStat.File} bytes");
    Console.WriteLine($"Page faults: {memoryStat.PageFault}");
}

IO Control

// Enable the IO controller on the parent
root.SetControllers("io");

// Set default IO weight
myGroup.SetDefaultIoWeight(200);

// Set IO weight for specific device (major:minor = 8:0 for /dev/sda)
myGroup.SetIoWeight(8, 0, 300);

// Set IO bandwidth limits (10 MB/s read, 5 MB/s write)
myGroup.SetIoMax(
    major: 8,
    minor: 0,
    readBytesPerSecond: 10 * 1024 * 1024,
    writeBytesPerSecond: 5 * 1024 * 1024
);

// Set IOPS limits
myGroup.SetIoMax(
    major: 8,
    minor: 0,
    readIopsPerSecond: 1000,
    writeIopsPerSecond: 500
);

// Remove limits
myGroup.RemoveIoMax(8, 0);

PID Control

// Enable the PIDs controller on the parent
root.SetControllers("pids");

// Limit to 100 processes
myGroup.SetPidsMax(100);

// Get current number of processes
var currentPids = myGroup.GetPidsCurrent();
Console.WriteLine($"Current processes: {currentPids.Value}");

// Get limit (GetValueOrDefault avoids handling every state when a fallback is good enough)
var maxPids = myGroup.GetPidsMax();
Console.WriteLine($"Max processes: {maxPids.GetValueOrDefault(long.MaxValue)}");

CPU Affinity (Cpuset)

// Enable the cpuset controller on the parent
root.SetControllers("cpuset");

// Restrict to CPUs 0, 1, 2
myGroup.SetCpusetCpus(0, 1, 2);

// Or use range format
myGroup.SetCpusetCpusRaw("0-2,4,6-8");

// Restrict to memory nodes 0 and 1
myGroup.SetCpusetMems(0, 1);

// Get effective CPUs (actually granted)
var effectiveCpus = myGroup.GetCpusetCpusEffective();
Console.WriteLine($"Effective CPUs: {string.Join(", ", effectiveCpus.GetValueOrDefault([]))}");

// Set partition type
myGroup.SetCpusetPartition("isolated");

HugeTLB Control

// Enable hugetlb controller (if available)
// Note: HugeTLB support depends on kernel configuration

// Set HugeTLB limit for 2MB pages
myGroup.SetHugeTlbMax("2MB", 100 * 1024 * 1024); // 100 MB

// Get current HugeTLB usage. Unavailable when the kernel does not provide this page size
var current = myGroup.GetHugeTlbCurrent("2MB");
if (current.IsConfigured)
{
    Console.WriteLine($"Current 2MB HugeTLB usage: {current.Value} bytes");
}

// Get limit hit count
var limitHits = myGroup.GetHugeTlbEventsMax("2MB");
Console.WriteLine($"HugeTLB limit hits: {limitHits.GetValueOrDefault(0)}");

Process Management

// Associate a process
using var process = Process.Start("myapp");
myGroup.AssociateProcess(process);

// Or by PID
myGroup.AssociateProcess(1234);

// Add a thread by TID
myGroup.AssociateThread(5678);

// Get all processes in the cgroup
foreach (var pid in myGroup.GetProcesses())
{
    Console.WriteLine($"Process: {pid}");
}

// Get all threads
foreach (var tid in myGroup.GetThreads())
{
    Console.WriteLine($"Thread: {tid}");
}

Freezer

// Freeze all processes (SIGSTOP)
myGroup.Freeze();

// Check if frozen
if (myGroup.IsFrozen())
{
    Console.WriteLine("Cgroup is frozen");
}

// Unfreeze
myGroup.Unfreeze();

Kill All Processes

// Kill all processes in the cgroup (SIGKILL)
myGroup.Kill();

Controller Management

// Get available controllers
var available = myGroup.GetAvailableControllers();
Console.WriteLine($"Available: {string.Join(", ", available)}");

// Get enabled controllers
var enabled = myGroup.GetEnabledControllers();
Console.WriteLine($"Enabled: {string.Join(", ", enabled)}");

// Enable multiple controllers at once for myGroup's children
myGroup.SetControllers("cpu", "memory", "io");

// Disable a controller
// Prefixing a controller name with '-' disables that controller. For example, "-io" disables the IO controller.
myGroup.SetControllers("-io");

Cleanup

// Delete a cgroup (must be empty - no processes, no child cgroups)
subGroup.Delete();
myGroup.Delete();

Important Notes

  1. Permissions: Managing cgroups typically requires root privileges or appropriate capabilities.

  2. Controllers are enabled on the parent: SetControllers writes to cgroup.subtree_control, which enables controllers for a cgroup's children. To limit myGroup, enable the controller on myGroup.Parent. Calling myGroup.SetControllers("cpu") makes cpu.* files appear in myGroup's children, not in myGroup itself.

  3. No Internal Process Constraint: Non-root cgroups can only enable controllers if they don't contain any processes. Move processes to leaf cgroups first.

  4. Hierarchical: Resource limits are hierarchical. A child cgroup can't use more resources than its parent allows.

  5. Culture Invariant: All number parsing and formatting use CultureInfo.InvariantCulture for consistency.

  6. cgroup v2 Only: This library only supports cgroup v2. For systems still using cgroup v1, consider upgrading or use v1-specific tools.

  7. Error Handling: File operations may throw IOException, UnauthorizedAccessException, or DirectoryNotFoundException. Handle these appropriately. Reading a cgroup that no longer exists throws DirectoryNotFoundException rather than reporting an absent value, so a stale CGroup2 instance does not look like a healthy cgroup without controllers.

  8. Reading values: getters that read a single interface file return CGroupValue<T> instead of a nullable value, because "the controller is not enabled" and "there is no limit" are different answers and a caller deciding whether to apply its own limit must not confuse them:

    State meaning
    Configured the file holds a value, available through Value
    NotConfigured the file holds no limit (max)
    Unavailable the file does not exist: the controller is not enabled on the parent cgroup, or the kernel does not support the feature
    Invalid the file holds content this library does not understand; RawValue keeps it for diagnostics

    Use switch over State when the cases differ, TryGetValue when only a value matters, and GetValueOrDefault(fallback) when a fallback is good enough. Value throws InvalidOperationException when there is no value.

Example: Complete Application Resource Limiting

using Meziantou.Framework.Unix.ControlGroups;
using System.Diagnostics;

// Create a cgroup for the application
var appGroup = CGroup2.Root.CreateOrGetChild("myapp");

// Enable controllers on the parent so they apply to appGroup
CGroup2.Root.SetControllers("cpu", "memory", "io", "pids");

// Configure limits
appGroup.SetCpuWeight(100);       // Normal priority
appGroup.SetCpuMax(200_000, 100_000);    // Max 2 CPUs
appGroup.SetMemoryMax(2L * 1024 * 1024 * 1024); // 2 GB
appGroup.SetMemoryHigh(1536L * 1024 * 1024); // Start throttling at 1.5 GB
appGroup.SetPidsMax(200);       // Max 200 processes

// Start your application
var process = Process.Start("myapp");
appGroup.AssociateProcess(process);

// Monitor
while (!process.HasExited)
{
    var cpuStat = appGroup.GetCpuStat();
    var memCurrent = appGroup.GetMemoryCurrent();
    var pidsCurrent = appGroup.GetPidsCurrent();
    
    Console.WriteLine($"CPU: {cpuStat.Value.UsageMicroseconds} μs");
    Console.WriteLine($"Memory: {memCurrent.Value / (1024.0 * 1024):F2} MB");
    Console.WriteLine($"Processes: {pidsCurrent.Value}");
    
    await Task.Delay(1000);
}

// Cleanup
appGroup.Delete();

References

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed.  net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net11.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Meziantou.Framework.Unix.ControlGroups:

Package Downloads
Meziantou.Framework.ProcessWrapper

Fluent API for wrapping System.Diagnostics.Process

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.3 841 10/21/2026
2.0.2 13,548 9/6/2026
2.0.1 1,188 8/29/2026
2.0.0 4,153 7/5/2026
1.0.4 1,486 6/13/2026
1.0.3 419 6/7/2026
1.0.2 209 5/31/2026
1.0.1 1,843 11/16/2025
1.0.0 307 10/31/2025