CL.SystemStats
Cross-platform CPU, memory, uptime, and process statistics — one API over Windows performance counters and Linux
/proc.
CL.SystemStats gives a CodeLogic 4 application a single surface for reading live system metrics. It samples CPU usage, memory pressure, uptime, and per-process resource use through platform-specific providers, returns every reading as a Result<T>, caches common reads for a short window, and raises events when CPU or memory usage crosses a configured threshold. The library exposes a SystemStatsLibrary that forwards every stats method directly and also surfaces the underlying service through its Stats property.
| Package | CodeLogic.SystemStats |
| Library class | CL.SystemStats.SystemStatsLibrary |
| Config file | config.systemstats.json (section systemstats) |
| Dependencies | System.Diagnostics.PerformanceCounter 9.x · System.Management 9.x |
Install & load
dotnet add package CodeLogic.SystemStats
using CL.SystemStats;
await Libraries.LoadAsync<SystemStatsLibrary>(); // register before ConfigureAsync()
await CodeLogic.ConfigureAsync();
await CodeLogic.StartAsync();
var sys = Libraries.Get<SystemStatsLibrary>();
The library forwards all stats methods, so sys.GetCpuStatsAsync() and sys.Stats.GetCpuStatsAsync() are equivalent. The Stats property (a SystemStatsService) additionally exposes ClearCache(), GetPlatformInfo(), and IsInitialized.
How readings work across platforms
There is one API but two providers, selected at runtime. Unknown platforms fall back to the Linux provider.
| Metric | Windows | Linux |
|---|---|---|
| CPU info | Registry | /proc/cpuinfo |
| CPU usage | PerformanceCounter |
/proc/stat (dual-sample delta) |
| Memory | P/Invoke GlobalMemoryStatusEx |
/proc/meminfo |
| Uptime | Environment.TickCount64 |
/proc/uptime |
Because the providers read different sources, a few fields differ by platform; these are called out alongside the models below. Every data method returns a Task<Result<T>> — check IsSuccess / IsFailure, read .Value on success, and .Error?.Message on failure.
Result<CpuStats> r = await sys.GetCpuStatsAsync();
if (r.IsFailure)
logger.Warn(r.Error?.Message);
else
Use(r.Value!);
CPU
GetCpuInfoAsync() returns static hardware facts; GetCpuStatsAsync() returns a live usage reading sampled per the configuration.
Result<CpuInfo> info = await sys.GetCpuInfoAsync();
Result<CpuStats> stats = await sys.GetCpuStatsAsync();
if (info.IsSuccess && stats.IsSuccess)
{
Console.WriteLine($"{info.Value!.ModelName} ({info.Value.LogicalCoreCount} threads)");
Console.WriteLine($"Overall: {stats.Value!.OverallUsagePercent:F1}%");
for (int core = 0; core < stats.Value.PerCoreUsagePercent.Count; core++)
Console.WriteLine($" core {core}: {stats.Value.PerCoreUsagePercent[core]:F1}%");
}
GetCpuStatsAsync() publishes HighCpuUsageEvent when OverallUsagePercent reaches HighCpuThresholdPercent.
public sealed record CpuInfo(
string ModelName,
int PhysicalCoreCount,
int LogicalCoreCount,
string Vendor,
double MaxSpeedMHz,
string Architecture);
public sealed record CpuStats(
double OverallUsagePercent,
IReadOnlyList<double> PerCoreUsagePercent,
DateTime SampledAt);
Memory
GetMemoryInfoAsync() returns total installed RAM; GetMemoryStatsAsync() returns a live breakdown. Both records carry MiB/GiB helper properties so you don't have to divide by 1024 yourself.
Result<MemoryInfo> info = await sys.GetMemoryInfoAsync();
Result<MemoryStats> mem = await sys.GetMemoryStatsAsync();
if (info.IsSuccess) Console.WriteLine($"Installed: {info.Value!.TotalGiB:F1} GiB");
if (mem.IsSuccess) Console.WriteLine($"Used: {mem.Value!.UsedMiB} MiB ({mem.Value.UsagePercent:F1}%)");
GetMemoryStatsAsync() publishes HighMemoryUsageEvent when UsagePercent reaches HighMemoryThresholdPercent.
public sealed record MemoryInfo(long TotalBytes)
{
public long TotalMiB { get; } // helper
public double TotalGiB { get; } // helper
}
public sealed record MemoryStats(
long TotalBytes,
long AvailableBytes,
long UsedBytes,
long CachedBytes,
long BuffersBytes,
double UsagePercent,
DateTime SampledAt)
{
public long UsedMiB { get; } // helper
public double TotalGiB { get; } // helper
public double AvailableGiB { get; } // helper
}
CachedBytesandBuffersBytesare0on Windows and populated on Linux (from/proc/meminfo).
Uptime
Result<TimeSpan> up = await sys.GetSystemUptimeAsync();
if (up.IsSuccess)
Console.WriteLine($"Up {up.Value.Days}d {up.Value.Hours}h {up.Value.Minutes}m");
Processes
Enumerate every process, look one up by PID, or rank the heaviest by CPU or memory. Process monitoring honours EnableProcessMonitoring; ranking methods default to MaxTopProcesses but take an explicit count.
Result<IReadOnlyList<ProcessStats>> all = await sys.GetAllProcessesAsync();
Result<IReadOnlyList<ProcessStats>> topCpu = await sys.GetTopProcessesByCpuAsync(5);
Result<IReadOnlyList<ProcessStats>> topMem = await sys.GetTopProcessesByMemoryAsync(5);
Result<ProcessStats> one = await sys.GetProcessStatsAsync(processId: 1234);
if (one.IsFailure)
Console.WriteLine(one.Error?.Message); // NotFound if the PID no longer exists
if (topMem.IsSuccess)
foreach (var p in topMem.Value!)
Console.WriteLine($"{p.Name} (pid {p.ProcessId}) — {p.WorkingSetMiB} MiB");
public sealed record ProcessStats(
int ProcessId,
string Name,
long WorkingSetBytes,
double CpuUsagePercent,
int ThreadCount,
int HandleCount,
DateTime? StartTime,
TimeSpan? TotalCpuTime,
string PriorityClass)
{
public long WorkingSetMiB { get; } // helper
}
Per-process CPU is sampled, so these calls take time to return.
GetTopProcessesByCpuAsyncmeasures each process'sCpuUsagePercentby sampling its processor time twice overCpuSamplingIntervalMs(default 100 ms), so the call blocks for roughly that interval;GetSystemSnapshotAsyncdoes the same when it ranks top processes. UseTotalCpuTimefor cumulative CPU consumption, and rank byWorkingSetBytesfor memory.CpuSamplesForAverageis currently reserved and not yet applied to per-process sampling. On Linux,HandleCountis the open file-descriptor count;StartTimeandTotalCpuTimearenullwhen unavailable.
GetProcessStatsAsync, GetTopProcessesByCpuAsync, and GetTopProcessesByMemoryAsync bypass the cache so they always return fresh data; GetAllProcessesAsync is cached.
System snapshot
GetSystemSnapshotAsync() collects everything in one call — useful for a dashboard tick or a periodic health log.
Result<SystemSnapshot> snap = await sys.GetSystemSnapshotAsync();
if (snap.IsSuccess)
{
var s = snap.Value!;
Console.WriteLine($"CPU {s.CpuStats.OverallUsagePercent:F0}% " +
$"RAM {s.MemoryStats.UsagePercent:F0}% " +
$"up {s.Uptime.TotalHours:F0}h");
}
public sealed record SystemSnapshot(
CpuStats CpuStats,
MemoryStats MemoryStats,
TimeSpan Uptime,
IReadOnlyList<ProcessStats> TopProcessesByCpu,
IReadOnlyList<ProcessStats> TopProcessesByMemory,
DateTime TakenAt);
Taking a snapshot publishes SystemSnapshotTakenEvent, and because it reads CPU and memory stats it also runs the CPU and memory threshold checks (so it can publish HighCpuUsageEvent / HighMemoryUsageEvent).
Caching
When EnableCaching is on, these reads are cached for CacheDurationSeconds: cpu_info, cpu_stats, mem_info, mem_stats, uptime, and all_processes. Per-process lookups and the top-N rankings always bypass the cache. Clear everything manually through the service:
sys.Stats.ClearCache();
Events
All three implement IEvent (namespace CL.SystemStats.Events) and are published to the CodeLogic event bus when one is available.
| Event | Carries | Published when |
|---|---|---|
SystemSnapshotTakenEvent |
CpuUsagePercent, MemoryUsagePercent, Uptime, TakenAt |
After a successful GetSystemSnapshotAsync(). |
HighCpuUsageEvent |
UsagePercent, ThresholdPercent, DetectedAt |
Overall CPU usage reaches HighCpuThresholdPercent (from GetCpuStatsAsync / GetSystemSnapshotAsync). |
HighMemoryUsageEvent |
UsagePercent, ThresholdPercent, DetectedAt |
Memory usage reaches HighMemoryThresholdPercent (from GetMemoryStatsAsync / GetSystemSnapshotAsync). |
Configuration
The library writes config.systemstats.json (section systemstats) with defaults on first run.
{
"EnableCaching": true,
"CacheDurationSeconds": 5,
"CpuSamplingIntervalMs": 100,
"CpuSamplesForAverage": 3,
"EnableTemperatureMonitoring": true,
"EnableProcessMonitoring": true,
"MaxTopProcesses": 10,
"HighCpuThresholdPercent": 90.0,
"HighMemoryThresholdPercent": 90.0
}
| Setting | Type | Default | Description |
|---|---|---|---|
EnableCaching |
bool |
true |
Cache common reads for CacheDurationSeconds. When off, every call samples live. |
CacheDurationSeconds |
int |
5 |
Cache lifetime in seconds (1–600). |
CpuSamplingIntervalMs |
int |
100 |
Delay between consecutive CPU samples in ms (1–10000). |
CpuSamplesForAverage |
int |
3 |
Samples averaged per CPU reading (1–20). Currently reserved — not yet applied. |
EnableTemperatureMonitoring |
bool |
true |
Reserved switch for temperature sampling support. |
EnableProcessMonitoring |
bool |
true |
Enable process enumeration and ranking. |
MaxTopProcesses |
int |
10 |
Default count for the top-process queries (1–200). |
HighCpuThresholdPercent |
double |
90.0 |
Overall CPU usage that triggers HighCpuUsageEvent (1–100). |
HighMemoryThresholdPercent |
double |
90.0 |
Memory usage that triggers HighMemoryUsageEvent (1–100). |
Health check
HealthCheckAsync() confirms the active provider can read CPU stats.
var status = await sys.HealthCheckAsync();
// status.Status : Healthy | Degraded | Unhealthy
- Healthy — CPU stats are accessible.
- Degraded — CPU stats could not be read.
- Unhealthy — the service is not initialized.
See also
- Getting Started — load, configure, and use any
CL.*library. - API Reference — generated type/member documentation.
- Package on NuGet