365 lines
13 KiB
C#
365 lines
13 KiB
C#
using EgwCoreLib.Lux.Data.DbModel.Stats;
|
|
|
|
namespace Lux.UI.Components.Compo.Stats
|
|
{
|
|
public partial class HistoricalStats
|
|
{
|
|
#region Protected Methods
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
// Se il servizio restituisce il “periodo di aggregazione” (es. ultimi 7 gg)
|
|
periodoLimit = await Calc.StatsAggrRangeAsync();
|
|
// aggiungo padding 1h pre-post...
|
|
periodoLimit.Inizio = periodoLimit.Inizio.AddDays(-1);
|
|
periodoLimit.Fine = periodoLimit.Fine.AddDays(1);
|
|
|
|
// Normalizzo i bound dei picker
|
|
periodoSel.Inizio = periodoSel.Inizio < periodoLimit.Inizio ? periodoLimit.Inizio : periodoSel.Inizio;
|
|
|
|
// carica!
|
|
await DoReload();
|
|
}
|
|
|
|
#endregion Protected Methods
|
|
|
|
#region Private Fields
|
|
|
|
private List<double> avgData = new();
|
|
private bool isLoading = false;
|
|
|
|
// Dati da passare a Chart.js
|
|
private List<string> labels = new();
|
|
|
|
private List<double> maxData = new();
|
|
private bool needReload = false;
|
|
private List<double> noData = new();
|
|
private EgwCoreLib.Utils.DtUtils.Periodo periodoLimit = new EgwCoreLib.Utils.DtUtils.Periodo(EgwCoreLib.Utils.DtUtils.PeriodSet.LastWeek);
|
|
private EgwCoreLib.Utils.DtUtils.Periodo periodoSel = new EgwCoreLib.Utils.DtUtils.Periodo(DateTime.Today.AddHours(DateTime.Now.Hour - 24 * 2), DateTime.Today.AddHours(DateTime.Now.Hour + 1));
|
|
private List<double> reqData = new();
|
|
|
|
#endregion Private Fields
|
|
|
|
#region Private Properties
|
|
|
|
private string btnReload => needReload ? "btn-primary" : "btn-secondary";
|
|
|
|
[Inject]
|
|
private ICalcRuidService Calc { get; set; } = null!;
|
|
|
|
private DateTime dtFrom
|
|
{
|
|
get => periodoSel.Inizio;
|
|
set
|
|
{
|
|
if (periodoSel.Inizio != value)
|
|
{
|
|
periodoSel.Inizio = value;
|
|
needReload = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private DateTime dtTo
|
|
{
|
|
get => periodoSel.Fine;
|
|
set
|
|
{
|
|
if (periodoSel.Fine != value)
|
|
{
|
|
periodoSel.Fine = value;
|
|
needReload = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
[Inject]
|
|
private IJSRuntime JSRuntime { get; set; } = null!;
|
|
|
|
#endregion Private Properties
|
|
|
|
#region Private Methods
|
|
|
|
/// <summary>
|
|
/// Helper creazione grafico Chart.js single line
|
|
/// </summary>
|
|
/// <param name="canvasId"></param>
|
|
/// <param name="label"></param>
|
|
/// <param name="data"></param>
|
|
/// <param name="borderColor"></param>
|
|
/// <returns></returns>
|
|
private async Task CreateChartAsync(string canvasId, string label, List<double> data, string borderColor)
|
|
{
|
|
var config = new
|
|
{
|
|
type = "line",
|
|
data = new
|
|
{
|
|
labels = (IEnumerable<object>)labels,
|
|
datasets = new[]
|
|
{
|
|
new
|
|
{
|
|
label = label,
|
|
data = data,
|
|
borderColor = borderColor,
|
|
fill = false,
|
|
tension = 0
|
|
}
|
|
}
|
|
},
|
|
options = new
|
|
{
|
|
responsive = true,
|
|
plugins = new
|
|
{
|
|
legend = new { display = true },
|
|
tooltip = new { mode = "index", intersect = false }
|
|
},
|
|
scales = new
|
|
{
|
|
x = new
|
|
{
|
|
type = "time",
|
|
time = new
|
|
{
|
|
unit = "hour",
|
|
displayFormats = new { hour = "MM.DD HH:mm" }
|
|
},
|
|
title = new { display = true, text = "Data/Ora" }
|
|
},
|
|
y = new { display = true, title = new { display = true, text = "Call (#/h)" } }
|
|
}
|
|
}
|
|
};
|
|
|
|
await JSRuntime.InvokeVoidAsync("chartsInterop.createChart", canvasId, config);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper creazione grafico Chart.js multi line
|
|
/// </summary>
|
|
/// <param name="canvasId"></param>
|
|
/// <param name="labels"></param>
|
|
/// <param name="avgData"></param>
|
|
/// <param name="maxData"></param>
|
|
/// <returns></returns>
|
|
private async Task CreateChartMultiAsync(string canvasId, string yAxisType, List<string> labels, List<double> avgData, List<double> maxData)
|
|
{
|
|
var config = new
|
|
{
|
|
type = "line",
|
|
data = new
|
|
{
|
|
labels = labels,
|
|
datasets = new[]
|
|
{
|
|
new {
|
|
label = "Tempo medio (s)",
|
|
data = avgData,
|
|
borderColor = "green",
|
|
fill = true,
|
|
tension = 0
|
|
},
|
|
new {
|
|
label = "Tempo massimo (s)",
|
|
data = maxData,
|
|
borderColor = "red",
|
|
fill = false,
|
|
tension = 0
|
|
}
|
|
}
|
|
},
|
|
options = new
|
|
{
|
|
responsive = true,
|
|
plugins = new
|
|
{
|
|
legend = new { display = true },
|
|
tooltip = new { mode = "index", intersect = false }
|
|
},
|
|
scales = new
|
|
{
|
|
x = new
|
|
{
|
|
type = "time",
|
|
time = new
|
|
{
|
|
unit = "hour",
|
|
displayFormats = new { hour = "MM.DD HH:mm" }
|
|
},
|
|
title = new { display = true, text = "Data/Ora" }
|
|
},
|
|
y = new
|
|
{
|
|
display = true,
|
|
type = yAxisType, // <-- Questa linea attiva l'asse Y lineare/logaritmico
|
|
title = new { display = true, text = "Durata (s)" }
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
await JSRuntime.InvokeVoidAsync("chartsInterop.createChart", canvasId, config);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creazione di un barchart stacked tra chiamate OK e perse (no answer)
|
|
/// </summary>
|
|
/// <param name="canvasId"></param>
|
|
/// <param name="label"></param>
|
|
/// <param name="listResp"></param>
|
|
/// <param name="listMissed"></param>
|
|
/// <param name="borderColorResp"></param>
|
|
/// <param name="borderColorMissed"></param>
|
|
/// <returns></returns>
|
|
private async Task CreateMultiBarChartAsync(string canvasId, string yAxisType, string label, List<double> listResp, List<double> listMissed, string borderColorResp = "rgba(0, 0, 255, 1)", string borderColorMissed = "rgba(255, 0, 0, 1)")
|
|
{
|
|
var config = new
|
|
{
|
|
type = "bar",
|
|
data = new
|
|
{
|
|
labels = (IEnumerable<object>)labels, // Assicurati di avere la lista labels definita
|
|
datasets = new[]
|
|
{
|
|
new
|
|
{
|
|
label = "Ok",
|
|
data = listResp,
|
|
backgroundColor = "rgba(0, 0, 255, 0.3)", // blu semitrasparente
|
|
borderColor = borderColorResp,
|
|
borderWidth = 2
|
|
},
|
|
new
|
|
{
|
|
label = "Miss",
|
|
data = listMissed,
|
|
backgroundColor = "rgba(255, 0, 0, 0.3)", // rosso semitrasparente
|
|
borderColor = borderColorMissed,
|
|
borderWidth = 2
|
|
}
|
|
}
|
|
},
|
|
options = new
|
|
{
|
|
responsive = true,
|
|
plugins = new
|
|
{
|
|
legend = new { display = true },
|
|
tooltip = new { mode = "index", intersect = false }
|
|
},
|
|
scales = new
|
|
{
|
|
x = new
|
|
{
|
|
stacked = true,
|
|
type = "time",
|
|
time = new
|
|
{
|
|
unit = "hour",
|
|
displayFormats = new { hour = "MM.DD HH:mm" }
|
|
},
|
|
title = new { display = true, text = "Data/Ora" }
|
|
},
|
|
y = new
|
|
{
|
|
stacked = true,
|
|
display = true,
|
|
type = yAxisType, // <-- Questa linea attiva l'asse Y lineare/logaritmico
|
|
title = new { display = true, text = "Call (#/h)" }
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
await JSRuntime.InvokeVoidAsync("chartsInterop.createChart", canvasId, config);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Caricamento e conversione dati per plotting
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private async Task DoReload()
|
|
{
|
|
// [1] Recupero dei dati aggregati
|
|
List<StatsAggregatedModel> stats = await Calc.StatsAggrListAsync(periodoSel.Inizio, periodoSel.Fine);
|
|
|
|
// [2] Conversione in liste compatibili con Chart.js
|
|
labels.Clear();
|
|
reqData.Clear();
|
|
noData.Clear();
|
|
avgData.Clear();
|
|
maxData.Clear();
|
|
|
|
// Calcolo dei massimi/minimi per decidere tipo asse
|
|
double globalMax = 0;
|
|
double globalMin = double.MaxValue;
|
|
|
|
foreach (var s in stats)
|
|
{
|
|
//if (s.RequestCount > 0) globalMin = Math.Min(globalMin, s.RequestCount);
|
|
//if (s.NoReply > 0) globalMin = Math.Min(globalMin, s.NoReply);
|
|
if (s.AvgDuration > 0) globalMin = Math.Min(globalMin, s.AvgDuration);
|
|
if (s.MaxDuration > 0) globalMin = Math.Min(globalMin, s.MaxDuration);
|
|
|
|
globalMax = Math.Max(globalMax, Math.Max(s.AvgDuration, s.MaxDuration));
|
|
}
|
|
|
|
double factor = (globalMin > 0) ? globalMax / globalMin : globalMax;
|
|
string yAxisType = factor > 20 ? "logarithmic" : "linear";
|
|
|
|
// Valore di riempimento per missing data
|
|
double fillValue = yAxisType == "logarithmic" ? 0.01 : 0;
|
|
|
|
// [2b] Popolamento liste
|
|
for (var t = periodoSel.Inizio; t <= periodoSel.Fine; t = t.AddHours(1))
|
|
{
|
|
var s = stats.FirstOrDefault(x => x.Hour == t);
|
|
labels.Add(t.ToString("yyyy-MM-ddTHH:mm:ss")); // ISO string
|
|
|
|
if (s != null)
|
|
{
|
|
reqData.Add(s.RequestCount);
|
|
noData.Add(s.NoReply);
|
|
avgData.Add(s.AvgDuration);
|
|
maxData.Add(s.MaxDuration);
|
|
}
|
|
else
|
|
{
|
|
//reqData.Add(fillValue);
|
|
//noData.Add(fillValue);
|
|
reqData.Add(0);
|
|
noData.Add(0);
|
|
avgData.Add(fillValue);
|
|
maxData.Add(fillValue);
|
|
}
|
|
}
|
|
|
|
// [3] Creazione grafici con tipo asse passato
|
|
await CreateMultiBarChartAsync("chartBarRequests", "linear", "Richieste/ora", reqData, noData, "blue", "red");
|
|
await CreateChartMultiAsync("chartHistoricalTimes", yAxisType, labels, avgData, maxData);
|
|
|
|
needReload = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ricalcolo delle statistiche (esporta su DB)
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private async Task RicalcolaStats()
|
|
{
|
|
isLoading = true;
|
|
await InvokeAsync(StateHasChanged);
|
|
// non fa mai eliminazione la chiamata interattiva
|
|
await Calc.ExportStatsToDbAsync(false);
|
|
isLoading = false;
|
|
await InvokeAsync(StateHasChanged);
|
|
await Task.Delay(50);
|
|
// Dopo il ricalcolo possiamo ricaricare i dati così da mostrarli subito
|
|
await DoReload();
|
|
}
|
|
|
|
#endregion Private Methods
|
|
}
|
|
} |