Bozza aggiunta gestione salvataggio DbMetriche (da verificare compilazione)

This commit is contained in:
Samuele E. Locatelli (W11-AI)
2026-04-07 07:14:21 +02:00
parent fb12ccc028
commit 9c0dc1ef19
2 changed files with 145 additions and 0 deletions
+13
View File
@@ -1,5 +1,7 @@
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.OpenApi.Models;
using MP.Data.Repository.Utils;
using MP.Data.Services.Utils;
using MP.IOC.Data;
using MP.IOC.Services;
using NLog;
@@ -58,6 +60,17 @@ logger.Info("YARP reverse proxy configured");
builder.Services.AddSingleton<PreserveBodyTransformer>();
builder.Services.AddSingleton<RouteStatsManager>();
builder.Services.AddHostedService<MetricsFlushService>();
builder.Services.AddHostedService<MetricsDbFlushService>();
// MP.Data DbContext for Stats repositories
builder.Services.AddDbContextFactory<DataLayerContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("MP.Utils")));
// MP.Data Services Utils - Statistiche DB
builder.Services.AddScoped<IStatsAggrRepository, StatsAggrRepository>();
builder.Services.AddScoped<IStatsDetailRepository, StatsDetailRepository>();
builder.Services.AddScoped<IStatsAggrService, StatsAggrService>();
builder.Services.AddScoped<IStatsDetailService, StatsDetailService>();
// generic controller
builder.Services.AddControllers();
+132
View File
@@ -0,0 +1,132 @@
using MP.Data.DbModels.Utils;
using MP.Data.Services.Utils;
using NLog;
using System.Globalization;
namespace MP.IOC.Services
{
public class MetricsDbFlushService : BackgroundService
{
private const int FlushIntervalSeconds = 30;
private readonly RouteStatsManager _stats;
private readonly IStatsAggrService _aggrService;
private readonly IStatsDetailService _detailService;
private readonly IConfiguration _config;
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public MetricsDbFlushService(
RouteStatsManager stats,
IStatsAggrService aggrService,
IStatsDetailService detailService,
IConfiguration config)
{
_stats = stats;
_aggrService = aggrService;
_detailService = detailService;
_config = config;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var interval = _config.GetValue<int>("RouteMan:FlushIntervalSeconds", FlushIntervalSeconds);
while (!stoppingToken.IsCancellationRequested)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(interval), stoppingToken);
await FlushMetricsAsync();
}
catch (TaskCanceledException)
{
break;
}
catch (Exception ex)
{
Log.Error(ex, "Error flushing metrics to database");
}
}
}
public async Task FlushMetricsAsync()
{
var snapshot = _stats.Snapshot();
if (snapshot.Count == 0)
{
return;
}
try
{
var utcNow = DateTime.Now;
var hourStart = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, utcNow.Hour, 0, 0);
var aggrRecords = new List<StatsAggregatedModel>();
var detailRecords = new List<StatsDetailModel>();
foreach (var kv in snapshot)
{
var method = kv.Key;
var stat = kv.Value;
var count = Interlocked.Read(ref stat.Count);
var totalMs = stat.TotalDuration.TotalMilliseconds;
if (count == 0) continue;
var avgDuration = totalMs / count;
var aggrRecord = new StatsAggregatedModel
{
Hour = hourStart,
RequestCount = count,
AvgDuration = avgDuration,
MaxDuration = totalMs,
Perc05Duration = 0,
Perc95Duration = 0,
NoReply = 0
};
aggrRecords.Add(aggrRecord);
foreach (var dest in stat.Destinations)
{
var detailRecord = new StatsDetailModel
{
Environment = dest.Key,
Type = method,
Hour = hourStart,
RequestCount = dest.Value,
AvgDuration = totalMs / dest.Value,
MaxDuration = totalMs,
Perc05Duration = 0,
Perc95Duration = 0,
NoReply = 0
};
detailRecords.Add(detailRecord);
}
}
if (aggrRecords.Count > 0)
{
await _aggrService.UpsertManyAsync(aggrRecords, true);
Log.Info("Flushed {count} aggregated stats records", aggrRecords.Count);
}
if (detailRecords.Count > 0)
{
await _detailService.UpsertManyAsync(detailRecords, true);
Log.Info("Flushed {count} detail stats records", detailRecords.Count);
}
_stats.Clear();
}
catch (Exception ex)
{
Log.Error(ex, "Error processing metrics for database flush");
throw;
}
}
}
}