Update display statistiche chiamata
This commit is contained in:
@@ -6,7 +6,6 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
@* <base href="/" /> *@
|
||||
<link rel="stylesheet" href="lib/bootstrap/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="lib/font-awesome/css/all.min.css" />
|
||||
<link rel="stylesheet" href="app.css" />
|
||||
<link rel="stylesheet" href="MP.IOC.styles.css" />
|
||||
@@ -20,9 +19,10 @@
|
||||
<Routes @rendermode="@(new InteractiveServerRenderMode(prerender: false))" />
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
<script type="text/javascript" src="lib/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="lib/Chart.js/chart.js"></script>
|
||||
<script src="lib/luxon/luxon.js"></script>
|
||||
<script src="lib/chartjs-adapter-luxon/chartjs-adapter-luxon.js"></script>
|
||||
@* <script src="lib/Chart.js/chart.js"></script> *@
|
||||
<script src="lib/Chart.js/chart.umd.js"></script>
|
||||
@* <script src="lib/luxon/luxon.js"></script>
|
||||
<script src="lib/chartjs-adapter-luxon/chartjs-adapter-luxon.umd.js"></script> *@
|
||||
<script src="lib/chartBoot.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -2,48 +2,58 @@
|
||||
|
||||
<div class="card shadow">
|
||||
<div class="card-header">
|
||||
<h3>Current Call Stats </h3>(24h)
|
||||
<h3>Current Call Stats (24h)</h3>
|
||||
</div>
|
||||
<div class="card-body p-1">
|
||||
<div class="row mb-2">
|
||||
<div class="col-2">
|
||||
<div class="col-3">
|
||||
<ul class="list-group shadow">
|
||||
<li class="list-group-item active d-flex justify-content-between align-items-start">
|
||||
<b>Avail Data</b>
|
||||
<button class="btn btn-sm btn-info" @onclick="() => DoReset()">
|
||||
<i class="fa-solid fa-arrow-rotate-right"></i>
|
||||
</button>
|
||||
</li>
|
||||
@foreach (var item in ParetoDay)
|
||||
{
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start small py-1">
|
||||
<span class="align-items-center">@item.Key</span>
|
||||
<button class="btn btn-sm btn-info">
|
||||
<button class="btn btn-sm btn-info" @onclick="() => DoSelect(item.Key)">
|
||||
<i class="fa-solid fa-magnifying-glass"></i>
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
Pareto Chart
|
||||
<div class="col-6">
|
||||
@if (string.IsNullOrEmpty(@currSelect))
|
||||
{
|
||||
<div class="alert alert-info">selezionare la metrica</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<BarPlot Id="@currSelect" AspRatio="2" Data="@DatiPareto" Labels="@LabelPareto" Legenda="@currTitle" lineColor="@lineColors" backColor="@bgColors"></BarPlot>
|
||||
}
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Pie chart
|
||||
<PieChart Id="PieSelected" AspRatio="1" LegendPos="none" Data="@DatiPareto" Labels="@LabelPareto" lineColor="@lineColors" backColor="@bgColors" Title="@currTitle"></PieChart>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-2">
|
||||
lista dettaglio (5/10 pag)
|
||||
<div class="col-3">
|
||||
<ParetoDetail Title="@currSelect" ParetoList="@currData"></ParetoDetail>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<div class="col-9">
|
||||
timeserie selezionata x num call/tempo
|
||||
</div>
|
||||
</div>
|
||||
<div class="row small">
|
||||
@* <div class="row small">
|
||||
@foreach (var item in ParetoDay)
|
||||
{
|
||||
<div class="col-4 mb-2">
|
||||
<ParetoDetail Title="@item.Key" ParetoList="@item.Value"></ParetoDetail>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div> *@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,76 @@ namespace MP.IOC.Components.Pages
|
||||
|
||||
private Dictionary<string, List<StatDataDTO>> ParetoDay = new();
|
||||
|
||||
private void DoReset()
|
||||
{
|
||||
currSelect = "";
|
||||
currData = new();
|
||||
}
|
||||
private void DoSelect(string reqKey)
|
||||
{
|
||||
if (ParetoDay.ContainsKey(reqKey))
|
||||
{
|
||||
currSelect = reqKey;
|
||||
currTitle = $"Pareto | {reqKey}";
|
||||
currData = ParetoDay[reqKey];
|
||||
}
|
||||
}
|
||||
private string currSelect = "";
|
||||
private string currTitle = "";
|
||||
private List<StatDataDTO> currData = new();
|
||||
|
||||
private List<string> LabelPareto
|
||||
{
|
||||
get => currData.Select(x => x.Label).ToList();
|
||||
}
|
||||
|
||||
private List<double> DatiPareto
|
||||
{
|
||||
get => currData.Select(x => x.Value).ToList();
|
||||
}
|
||||
/// <summary>
|
||||
/// Genera colori sfondo 33% rosso / arancione / giallo
|
||||
/// </summary>
|
||||
/// <param name="numRecords"></param>
|
||||
/// <param name="alpha"></param>
|
||||
/// <returns></returns>
|
||||
private List<string> semaphColors(int numRecords, string alpha)
|
||||
{
|
||||
List<string> answ = new List<string>();
|
||||
// verde...
|
||||
for (int i = 0; i < numRecords / 3; i++)
|
||||
{
|
||||
answ.Add($"rgba(54, 235, 82, {alpha})");
|
||||
}
|
||||
// arancione
|
||||
for (int i = 0; i < numRecords / 3; i++)
|
||||
{
|
||||
answ.Add($"rgba(255, 206, 86, {alpha})");
|
||||
}
|
||||
while (answ.Count < numRecords)
|
||||
{
|
||||
answ.Add($"rgba(255, 99, 132, {alpha}");
|
||||
}
|
||||
|
||||
return answ;
|
||||
}
|
||||
/// <summary>
|
||||
/// Genera colori sfondo 33% rosso / arancione / giallo
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected List<string> bgColors
|
||||
{
|
||||
get => semaphColors(currData.Count, "0.3");
|
||||
}
|
||||
/// <summary>
|
||||
/// Genera colori sfondo 33% rosso / arancione / giallo
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private List<string> lineColors
|
||||
{
|
||||
get => semaphColors(currData.Count, "1");
|
||||
}
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Private Properties
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Version>6.16.2604.1008</Version>
|
||||
<Version>6.16.2604.1009</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<body>
|
||||
<i>Modulo MP-IOC </i>
|
||||
<h4>Versione: 6.16.2604.1008</h4>
|
||||
<h4>Versione: 6.16.2604.1009</h4>
|
||||
<br /> Note di rilascio:
|
||||
<ul>
|
||||
<li>
|
||||
|
||||
@@ -1 +1 @@
|
||||
6.16.2604.1008
|
||||
6.16.2604.1009
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<item>
|
||||
<version>6.16.2604.1008</version>
|
||||
<version>6.16.2604.1009</version>
|
||||
<url>https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/MP.IOC.zip</url>
|
||||
<changelog>https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/ChangeLog.html</changelog>
|
||||
<mandatory>false</mandatory>
|
||||
|
||||
@@ -191,100 +191,6 @@ namespace MP.IOC.Services
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if false
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
var interval = _config.GetValue<int>("RouteMan:MetricCalcIntervalSeconds", 20);
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(interval), stoppingToken);
|
||||
var snapshot = _stats.Snapshot();
|
||||
|
||||
if (snapshot.Count == 0) continue;
|
||||
|
||||
var adesso = DateTime.Now;
|
||||
var hourStart = new DateTime(adesso.Year, adesso.Month, adesso.Day, adesso.Hour, 0, 0);
|
||||
var dayStart = new DateTime(adesso.Year, adesso.Month, adesso.Day, 0, 0, 0);
|
||||
|
||||
foreach (var kv in snapshot)
|
||||
{
|
||||
string dest = "IO";
|
||||
string method = "NA";
|
||||
// verifico se ho chiave completo (dest+method) o parziale
|
||||
string rawKey = kv.Key;
|
||||
if (rawKey.Contains("|"))
|
||||
{
|
||||
var splitVal = rawKey.Split("|");
|
||||
dest = splitVal[0];
|
||||
method = splitVal[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
method = rawKey;
|
||||
}
|
||||
var stat = kv.Value;
|
||||
var count = Interlocked.Read(ref stat.Count);
|
||||
var totalMs = stat.TotalDuration.TotalMilliseconds;
|
||||
var maxMs = stat.MaxDuration.TotalMilliseconds;
|
||||
var minMs = (stat.MinDuration == TimeSpan.MaxValue) ? 0 : stat.MinDuration.TotalMilliseconds;
|
||||
|
||||
Log.Info($"Dest {dest} | Method {method} | Count {count} | TotalDurationMs {totalMs} | MaxDurationMs {maxMs} | MinDurationMs {minMs}");
|
||||
|
||||
if (_db == null) continue;
|
||||
|
||||
// Keys
|
||||
var hourKey = HourBucketKey(dest, method, hourStart);
|
||||
var hoursIndex = HoursIndexKey(dest, method);
|
||||
var dayKey = DayBucketKey(dest, dayStart);
|
||||
var daysIndex = DaysIndexKey(dest);
|
||||
|
||||
// Use batch to reduce roundtrips
|
||||
var batch = _db.CreateBatch();
|
||||
|
||||
// Increment hash fields (count and totalMs)
|
||||
var taskHourCount = batch.HashIncrementAsync(hourKey, "count", count);
|
||||
var taskHourTotal = batch.HashIncrementAsync(hourKey, "totalMs", totalMs);
|
||||
var taskHourMax = batch.HashSetAsync(hourKey, "maxMs", maxMs.ToString());
|
||||
var taskHourMin = batch.HashSetAsync(hourKey, "minMs", minMs.ToString());
|
||||
|
||||
var taskDayCount = batch.HashIncrementAsync(dayKey, "count", count);
|
||||
var taskDayTotal = batch.HashIncrementAsync(dayKey, "totalMs", totalMs);
|
||||
|
||||
// For day bucket min/max: save as separate fields to be aggregated later
|
||||
// Using HashSet with comparison logic handled during flush
|
||||
var taskDayMax = batch.HashSetAsync(dayKey, "maxMs", maxMs.ToString());
|
||||
var taskDayMin = batch.HashSetAsync(dayKey, "minMs", minMs.ToString());
|
||||
|
||||
// Add to sorted set indices with score = epoch seconds of bucket start
|
||||
var hourScore = ToEpochSeconds(hourStart);
|
||||
var dayScore = ToEpochSeconds(dayStart);
|
||||
|
||||
var taskZAddHour = batch.SortedSetAddAsync(hoursIndex, hourKey, hourScore);
|
||||
var taskZAddDay = batch.SortedSetAddAsync(daysIndex, dayKey, dayScore);
|
||||
|
||||
// Execute batch
|
||||
batch.Execute();
|
||||
|
||||
// Await tasks to ensure completion
|
||||
await Task.WhenAll(taskHourCount, taskHourTotal, taskHourMax, taskHourMin,
|
||||
taskDayCount, taskDayTotal, taskDayMax, taskDayMin,
|
||||
taskZAddHour, taskZAddDay);
|
||||
}
|
||||
|
||||
_stats.Clear();
|
||||
}
|
||||
catch (TaskCanceledException) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error flushing metrics");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Fields
|
||||
|
||||
@@ -79,6 +79,7 @@ namespace MP.IOC.Services
|
||||
public List<WeightDTO> GetAllWeights()
|
||||
{
|
||||
var result = new List<WeightDTO>();
|
||||
#if false
|
||||
var keys = _db.KeyScan($"{_keyPrefix}*");
|
||||
|
||||
foreach (var key in keys)
|
||||
@@ -99,7 +100,8 @@ namespace MP.IOC.Services
|
||||
newW = Math.Clamp(parsedNew, 0, 100);
|
||||
|
||||
result.Add(new WeightDTO { Method = methodName, OldWeight = oldW, NewWeight = newW });
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"DetailedErrors": true,
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
// Setup del chart desiderato con id univoco
|
||||
window.setup = (id, config) => {
|
||||
var ctx = document.getElementById(id).getContext('2d');
|
||||
|
||||
const el = document.getElementById(id);
|
||||
if (!el) {
|
||||
//console.error("Elemento canvas non trovato:", id);
|
||||
return;
|
||||
}
|
||||
// TECNICA INFALLIBILE:
|
||||
// Chiediamo a Chart.js di trovare l'istanza associata a questo elemento DOM
|
||||
// Indipendentemente da quale ID avesse prima.
|
||||
const existingChart = Chart.getChart(el);
|
||||
if (existingChart) {
|
||||
existingChart.destroy();
|
||||
}
|
||||
|
||||
const ctx = el.getContext('2d');
|
||||
|
||||
//var ctx = document.getElementById(id).getContext('2d');
|
||||
//let currentDate = new Date();
|
||||
//console.log(currentDate + " - Calling setup...");
|
||||
//console.log(id);
|
||||
console.log(id);
|
||||
if (window['chart-' + id] instanceof Chart) {
|
||||
//window.myChart.destroy();
|
||||
window['chart-' + id].destroy();
|
||||
|
||||
Reference in New Issue
Block a user