From c4ead0d8de74becc69cda7f4629fedc421d5d6ed Mon Sep 17 00:00:00 2001 From: ryzij Date: Sun, 28 Jun 2026 21:34:58 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=20=D0=B4=D0=BB=D1=8F=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BD=D0=B0?= =?UTF-8?q?=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B8=20=D0=BD=D0=B0=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=86=D0=B5=D1=81=D1=81=D0=BE=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contracts/CpuUsageResponse.cs | 7 ++ .../Controllers/MonitorController.cs | 17 ++++- .../MonitorAgent/MonitorAgent.API/Program.cs | 6 ++ .../MonitorAgent.API/appsettings.json | 5 +- .../MonitorAgent.Application.csproj | 4 ++ .../Services/CpuMonitorBackgroundService.cs | 64 +++++++++++++++++++ .../Services/RawCpuMonitorService.cs | 19 ++++++ .../Settings/CpuMonitorSettings.cs | 6 ++ .../Abstraction/ICpuMonitorService.cs | 8 +++ .../MonitorAgent.Core/Models/CpuUsage.cs | 9 +++ .../MonitorAgent.Core/Models/RawCpuStats.cs | 46 +++++++++++++ .../Models/RawCpuStatsCollection.cs | 10 +++ 12 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 Backend/MonitorAgent/MonitorAgent.API/Contracts/CpuUsageResponse.cs create mode 100644 Backend/MonitorAgent/MonitorAgent.Application/Services/CpuMonitorBackgroundService.cs create mode 100644 Backend/MonitorAgent/MonitorAgent.Application/Services/RawCpuMonitorService.cs create mode 100644 Backend/MonitorAgent/MonitorAgent.Application/Settings/CpuMonitorSettings.cs create mode 100644 Backend/MonitorAgent/MonitorAgent.Core/Abstraction/ICpuMonitorService.cs create mode 100644 Backend/MonitorAgent/MonitorAgent.Core/Models/CpuUsage.cs create mode 100644 Backend/MonitorAgent/MonitorAgent.Core/Models/RawCpuStats.cs create mode 100644 Backend/MonitorAgent/MonitorAgent.Core/Models/RawCpuStatsCollection.cs diff --git a/Backend/MonitorAgent/MonitorAgent.API/Contracts/CpuUsageResponse.cs b/Backend/MonitorAgent/MonitorAgent.API/Contracts/CpuUsageResponse.cs new file mode 100644 index 0000000..fc86f3a --- /dev/null +++ b/Backend/MonitorAgent/MonitorAgent.API/Contracts/CpuUsageResponse.cs @@ -0,0 +1,7 @@ +namespace MonitorAgent.API.Contracts; + +public class CpuUsageResponse(decimal total, IEnumerable cores) +{ + public decimal Total { get; set; } = total; + public List Cores { get; set; } = new([.. cores]); +} \ No newline at end of file diff --git a/Backend/MonitorAgent/MonitorAgent.API/Controllers/MonitorController.cs b/Backend/MonitorAgent/MonitorAgent.API/Controllers/MonitorController.cs index 744c224..5987ab8 100644 --- a/Backend/MonitorAgent/MonitorAgent.API/Controllers/MonitorController.cs +++ b/Backend/MonitorAgent/MonitorAgent.API/Controllers/MonitorController.cs @@ -1,11 +1,15 @@ using Microsoft.AspNetCore.Mvc; using MonitorAgent.API.Contracts; +using MonitorAgent.Application.Services; using MonitorAgent.Core.Abstraction; namespace MonitorAgent.API.Controllers; [ApiController] [Route("[controller]")] -public class MonitorController(IMonitorService monitorService) : ControllerBase +public class MonitorController( + IMonitorService monitorService, + CpuMonitorBackgroundService cpuMonitorService) + : ControllerBase { [HttpGet("uptime")] public async Task> GetUptimeAsync(CancellationToken cancellationToken = default) @@ -30,9 +34,18 @@ public class MonitorController(IMonitorService monitorService) : ControllerBase } [HttpGet("loadavg")] - public async Task> GetActionResultAsync(CancellationToken cancellationToken = default) + public async Task> GetLoadAverageAsync(CancellationToken cancellationToken = default) { var response = new LoadAverageResponse(await monitorService.GetLoadAverageAsync(cancellationToken)); return Ok(response); } + + [HttpGet("cpuUsage")] + public async Task> GetCpuUsageAsync(CancellationToken cancellationToken = default) + { + var usage = await cpuMonitorService.GetCpuUsageAsync(cancellationToken); + var response = new CpuUsageResponse(usage.TotalUsagePercent, usage.CoresUsagePercent); + + return Ok(response); + } } \ No newline at end of file diff --git a/Backend/MonitorAgent/MonitorAgent.API/Program.cs b/Backend/MonitorAgent/MonitorAgent.API/Program.cs index 65ae649..30b6d68 100644 --- a/Backend/MonitorAgent/MonitorAgent.API/Program.cs +++ b/Backend/MonitorAgent/MonitorAgent.API/Program.cs @@ -1,5 +1,6 @@ using MonitorAgent.Core.Abstraction; using MonitorAgent.Application.Services; +using MonitorAgent.Application.Settings; var builder = WebApplication.CreateBuilder(args); @@ -12,7 +13,12 @@ builder.Services.AddOpenApi(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +builder.Services.Configure(builder.Configuration.GetSection("CpuMonitorSettings")); + builder.Services.AddScoped(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddHostedService(provider => provider.GetRequiredService()); var app = builder.Build(); diff --git a/Backend/MonitorAgent/MonitorAgent.API/appsettings.json b/Backend/MonitorAgent/MonitorAgent.API/appsettings.json index 10f68b8..e91cabf 100644 --- a/Backend/MonitorAgent/MonitorAgent.API/appsettings.json +++ b/Backend/MonitorAgent/MonitorAgent.API/appsettings.json @@ -5,5 +5,8 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "CpuMonitorSettings": { + "DelayTime": 2000 + } } diff --git a/Backend/MonitorAgent/MonitorAgent.Application/MonitorAgent.Application.csproj b/Backend/MonitorAgent/MonitorAgent.Application/MonitorAgent.Application.csproj index 3930b21..a4c3b91 100644 --- a/Backend/MonitorAgent/MonitorAgent.Application/MonitorAgent.Application.csproj +++ b/Backend/MonitorAgent/MonitorAgent.Application/MonitorAgent.Application.csproj @@ -4,6 +4,10 @@ + + + + net10.0 enable diff --git a/Backend/MonitorAgent/MonitorAgent.Application/Services/CpuMonitorBackgroundService.cs b/Backend/MonitorAgent/MonitorAgent.Application/Services/CpuMonitorBackgroundService.cs new file mode 100644 index 0000000..c01c92f --- /dev/null +++ b/Backend/MonitorAgent/MonitorAgent.Application/Services/CpuMonitorBackgroundService.cs @@ -0,0 +1,64 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using MonitorAgent.Application.Settings; +using MonitorAgent.Core.Models; + +namespace MonitorAgent.Application.Services; + +public class CpuMonitorBackgroundService( + RawCpuMonitorService cpuMonitorService, + IOptions options, + ILogger logger) : BackgroundService +{ + private RawCpuStatsCollection? _prevStats; + private RawCpuStatsCollection? _nextStats; + + public async Task GetCpuUsageAsync(CancellationToken cancellationToken = default) + { + RawCpuStatsCollection prev, next; + if (_prevStats != null) + prev = _prevStats; + else + prev = await cpuMonitorService.ParseRawCpuStatsAsync(cancellationToken); + if (_nextStats != null) + next = _nextStats; + else + next = await cpuMonitorService.ParseRawCpuStatsAsync(cancellationToken); + + var total = CalcUsage(prev.Total, next.Total); + var cores = new List(prev.Cores.Count); + for (int i = 0; i < prev.Cores.Count; i++) + cores.Add(CalcUsage(prev.Cores[i], next.Cores[i])); + + return new CpuUsage(total, cores); + } + + private decimal CalcUsage(RawCpuStats prev, RawCpuStats next) + { + decimal deltaTotal = next.Total - prev.Total; + decimal deltaIdle = next.Idle - prev.Idle; + + return (1 - deltaIdle / deltaTotal) * 100; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + try + { + _prevStats = _nextStats; + _nextStats = await cpuMonitorService.ParseRawCpuStatsAsync(stoppingToken); + } + catch (Exception e) + { + logger.LogError(e.Message); + } + finally + { + await Task.Delay(options.Value.DelayTime, stoppingToken); + } + } + } +} \ No newline at end of file diff --git a/Backend/MonitorAgent/MonitorAgent.Application/Services/RawCpuMonitorService.cs b/Backend/MonitorAgent/MonitorAgent.Application/Services/RawCpuMonitorService.cs new file mode 100644 index 0000000..fc1ba00 --- /dev/null +++ b/Backend/MonitorAgent/MonitorAgent.Application/Services/RawCpuMonitorService.cs @@ -0,0 +1,19 @@ +using MonitorAgent.Core.Abstraction; +using MonitorAgent.Core.Models; + +namespace MonitorAgent.Application.Services; + +public class RawCpuMonitorService : ICpuMonitorService +{ + public async Task ParseRawCpuStatsAsync(CancellationToken cancellationToken = default) + { + var raw = await File.ReadAllLinesAsync("/proc/stat", cancellationToken); + + var total = RawCpuStats.Parse(raw[0]); + var cores = new List(); + for (int i = 1; i < raw.Length && raw[i].StartsWith("cpu"); i++) + cores.Add(RawCpuStats.Parse(raw[i])); + + return new RawCpuStatsCollection(total, cores); + } +} \ No newline at end of file diff --git a/Backend/MonitorAgent/MonitorAgent.Application/Settings/CpuMonitorSettings.cs b/Backend/MonitorAgent/MonitorAgent.Application/Settings/CpuMonitorSettings.cs new file mode 100644 index 0000000..a9cd2e7 --- /dev/null +++ b/Backend/MonitorAgent/MonitorAgent.Application/Settings/CpuMonitorSettings.cs @@ -0,0 +1,6 @@ +namespace MonitorAgent.Application.Settings; + +public class CpuMonitorSettings +{ + public int DelayTime { get; set; } = 1000; +} \ No newline at end of file diff --git a/Backend/MonitorAgent/MonitorAgent.Core/Abstraction/ICpuMonitorService.cs b/Backend/MonitorAgent/MonitorAgent.Core/Abstraction/ICpuMonitorService.cs new file mode 100644 index 0000000..fac3704 --- /dev/null +++ b/Backend/MonitorAgent/MonitorAgent.Core/Abstraction/ICpuMonitorService.cs @@ -0,0 +1,8 @@ +using MonitorAgent.Core.Models; + +namespace MonitorAgent.Core.Abstraction; + +public interface ICpuMonitorService +{ + public Task ParseRawCpuStatsAsync(CancellationToken cancellationToken = default); +} diff --git a/Backend/MonitorAgent/MonitorAgent.Core/Models/CpuUsage.cs b/Backend/MonitorAgent/MonitorAgent.Core/Models/CpuUsage.cs new file mode 100644 index 0000000..be3da13 --- /dev/null +++ b/Backend/MonitorAgent/MonitorAgent.Core/Models/CpuUsage.cs @@ -0,0 +1,9 @@ +using System.Collections.ObjectModel; + +namespace MonitorAgent.Core.Models; + +public class CpuUsage(decimal totalUsagePercent, IEnumerable coresUsagePercent) +{ + public readonly decimal TotalUsagePercent = totalUsagePercent; + public readonly ReadOnlyCollection CoresUsagePercent = new([.. coresUsagePercent]); +} \ No newline at end of file diff --git a/Backend/MonitorAgent/MonitorAgent.Core/Models/RawCpuStats.cs b/Backend/MonitorAgent/MonitorAgent.Core/Models/RawCpuStats.cs new file mode 100644 index 0000000..746b191 --- /dev/null +++ b/Backend/MonitorAgent/MonitorAgent.Core/Models/RawCpuStats.cs @@ -0,0 +1,46 @@ +namespace MonitorAgent.Core.Models; + +public struct RawCpuStats +{ + public long User { get; set; } + public long Nice { get; set; } + public long System { get; set; } + public long Idle { get; set; } + public long Iowait { get; set; } + public long Irq { get; set; } + public long Softirq { get; set; } + public long Steal { get; set; } + public long Guest { get; set; } + public long GuestNice { get; set; } + + public readonly long Total => User + Nice + System + Idle + Irq + Softirq + Steal; + public readonly long TotalIdle => Idle + Iowait; + + public static RawCpuStats Parse(string s) + { + var stats = s.Split(); + if (!stats[0].StartsWith("cpu")) + throw new FormatException($"The input string '{s}' was not in a correct format."); + + var longStats = new List(); + for (int i = 1; i < stats.Length; i++) + { + if (!string.IsNullOrEmpty(stats[i])) + longStats.Add(long.Parse(stats[i])); + } + + return new RawCpuStats + { + User = longStats[0], + Nice = longStats[1], + System = longStats[2], + Idle = longStats[3], + Iowait = longStats[4], + Irq = longStats[5], + Softirq = longStats[6], + Steal = longStats[7], + Guest = longStats[8], + GuestNice = longStats[9] + }; + } +} \ No newline at end of file diff --git a/Backend/MonitorAgent/MonitorAgent.Core/Models/RawCpuStatsCollection.cs b/Backend/MonitorAgent/MonitorAgent.Core/Models/RawCpuStatsCollection.cs new file mode 100644 index 0000000..8656259 --- /dev/null +++ b/Backend/MonitorAgent/MonitorAgent.Core/Models/RawCpuStatsCollection.cs @@ -0,0 +1,10 @@ +using System.Collections; +using System.Collections.ObjectModel; + +namespace MonitorAgent.Core.Models; + +public class RawCpuStatsCollection(RawCpuStats total, IEnumerable cores) +{ + public readonly RawCpuStats Total = total; + public readonly ReadOnlyCollection Cores = new([.. cores]); +} \ No newline at end of file