diff --git a/MP.Core/DTO/StatInfoDto.cs b/MP.Core/DTO/StatInfoDto.cs new file mode 100644 index 00000000..e2d96a99 --- /dev/null +++ b/MP.Core/DTO/StatInfoDto.cs @@ -0,0 +1,80 @@ +namespace MP.Core.DTO +{ + public class StatInfoDto + { + #region Public Enums + + /// + /// Modalità di aggregwzione del dato + /// + public enum AggrLevel + { + /// + /// Non specificato + /// + None, + + /// + /// Aggregazione x server (IO/IOC) + /// + Service, + + /// + /// Aggregazione x metodo chiamato + /// + Method, + + /// + /// Aggregazione x Macchina chiamante + /// + Machine + } + + /// + /// Tipologia del dato collezionato + /// + public enum DataType + { + /// + /// Non specificato + /// + None, + + /// + /// Conteggio + /// + Count, + + /// + /// Durata temporale (Avg) + /// + AvgDuration + } + + #endregion Public Enums + + #region Public Properties + + /// + /// Titolo della statistica + /// + public string Title { get; set; } = ""; + + /// + /// Livello di aggregazione delle statistiche collezionate + /// + public AggrLevel Grouping { get; set; } = AggrLevel.None; + + /// + /// Tipologia di dato della statistica + /// + public DataType Type { get; set; } = DataType.None; + + /// + /// Dati statistici ordinati in logica Pareto + /// + public List DataCollection { get; set; } = new(); + + #endregion Public Properties + } +} \ No newline at end of file diff --git a/MP.Data/Services/Utils/IStatsAggrService.cs b/MP.Data/Services/Utils/IStatsAggrService.cs index 77e2c4e1..a1905070 100644 --- a/MP.Data/Services/Utils/IStatsAggrService.cs +++ b/MP.Data/Services/Utils/IStatsAggrService.cs @@ -1,6 +1,7 @@ using EgwCoreLib.Utils; using MP.Core.DTO; using MP.Data.DbModels.Utils; +using MP.Data.DTO; using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -46,6 +47,14 @@ namespace MP.Data.Services.Utils /// Elenco dei record da inserire/aggiornare /// Se true elimina preventivamente i record nel periodo richiesto Task UpsertManyAsync(List listRecords, bool removeOld); + /// + /// Helper conversione dati aggregati in statistiche da inviare a ChartJS + /// + /// + /// raggruppamento per macchina/server + /// metrica conteggio/avgDuration + /// + List GetTimeSeriesData(List rawData, bool groupMach, bool getCount); #endregion Public Methods } diff --git a/MP.Data/Services/Utils/IStatsDetailService.cs b/MP.Data/Services/Utils/IStatsDetailService.cs index 31cb4da1..364a44a4 100644 --- a/MP.Data/Services/Utils/IStatsDetailService.cs +++ b/MP.Data/Services/Utils/IStatsDetailService.cs @@ -38,7 +38,9 @@ namespace MP.Data.Services.Utils /// Ogni elaborazione contiene Dictionary in forma pareto per una data statistica /// /// - Task>> GetParetoStatsDayAsync(); + //Task>> GetParetoStatsDayAsync(); + Task> GetParetoStatsDayAsync(); + /// /// Recupera il range di periodi valido per le chiamate di dettaglio. diff --git a/MP.Data/Services/Utils/StatsAggrService.cs b/MP.Data/Services/Utils/StatsAggrService.cs index 088b40fd..012031e9 100644 --- a/MP.Data/Services/Utils/StatsAggrService.cs +++ b/MP.Data/Services/Utils/StatsAggrService.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Configuration; using MP.Core.DTO; using MP.Data.DbModels.Utils; +using MP.Data.DTO; using MP.Data.Repository.Utils; using StackExchange.Redis; using System; @@ -41,18 +42,6 @@ namespace MP.Data.Services.Utils }); } - /// - public async Task>> GetParetoStatsWeekAsync() - { - return await TraceAsync($"{_className}.GetParetoStatsWeekAsync", async (activity) => - { - return await GetOrSetCacheAsync( - $"{_redisBaseKey}:{_className}:ParetoWeek", - async () => await GetParetoDataAsync(), - LongCache - ); - }); - } /// public async Task>> GetParetoStatsDayAsync(int numDay) { @@ -66,6 +55,19 @@ namespace MP.Data.Services.Utils }); } + /// + public async Task>> GetParetoStatsWeekAsync() + { + return await TraceAsync($"{_className}.GetParetoStatsWeekAsync", async (activity) => + { + return await GetOrSetCacheAsync( + $"{_redisBaseKey}:{_className}:ParetoWeek", + async () => await GetParetoDataAsync(), + LongCache + ); + }); + } + /// public async Task GetRangeAsync() { @@ -79,6 +81,59 @@ namespace MP.Data.Services.Utils }); } + /// + public List GetTimeSeriesData(List rawData, bool groupMach, bool getCount) + { + List series = new(); + // a seconda della richiesta cambio il group by... + if (groupMach) + { + series = rawData + .GroupBy(s => new { s.Destination, s.MachineId }) // Raggruppiamo per la chiave composta + .Select(group => new ChartSeriesDto + { + // Creiamo un nome leggibile per la legenda del grafico + SeriesName = $"{group.Key.Destination}|{group.Key.MachineId}", + + // Per ogni gruppo, creiamo la lista dei punti temporali + DataPoints = group + .OrderBy(p => p.Hour) // Fondamentale: l'asse X deve essere cronologico + .Select(p => new chartJsData.chartJsTSerie + { + x = p.Hour, + y = getCount ? p.RequestCount : p.AvgDuration * p.RequestCount // La metrica richiesta + }) + .ToList() + }) + .OrderBy(s => s.SeriesName) // Opzionale: ordina le serie alfabeticamente + .ToList(); + } + else + { + series = rawData + .GroupBy(s => new { s.MachineId, s.Destination }) // Raggruppiamo per la chiave composta + .Select(group => new ChartSeriesDto + { + // Creiamo un nome leggibile per la legenda del grafico + SeriesName = $"{group.Key.MachineId}|{group.Key.Destination}", + + // Per ogni gruppo, creiamo la lista dei punti temporali + DataPoints = group + .OrderBy(p => p.Hour) // Fondamentale: l'asse X deve essere cronologico + .Select(p => new chartJsData.chartJsTSerie + { + x = p.Hour, + y = getCount ? p.RequestCount : p.AvgDuration // La metrica richiesta + }) + .ToList() + }) + .OrderBy(s => s.SeriesName) // Opzionale: ordina le serie alfabeticamente + .ToList(); + } + + return series; + } + /// public async Task UpsertManyAsync(List listRecords, bool removeOld) { diff --git a/MP.Data/Services/Utils/StatsDetailService.cs b/MP.Data/Services/Utils/StatsDetailService.cs index 3f253562..14c9dd53 100644 --- a/MP.Data/Services/Utils/StatsDetailService.cs +++ b/MP.Data/Services/Utils/StatsDetailService.cs @@ -74,7 +74,7 @@ namespace MP.Data.Services.Utils } /// - public async Task>> GetParetoStatsDayAsync() + public async Task> GetParetoStatsDayAsync() { return await TraceAsync($"{_className}.GetDailyParetoStats", async (activity) => { @@ -152,8 +152,80 @@ namespace MP.Data.Services.Utils /// metodo locale per recupero e trasformazione dati da includere con processo generare di tracking & cache /// /// - protected async Task>> GetParetoDataAsync() + protected async Task> GetParetoDataAsync() { + List result = new(); + DateTime adesso = DateTime.Now; + var rawData = await GetFiltAsync(adesso.AddDays(-1), adesso); + + // calcolo le varie statistiche... + var pDestRequest = rawData.GroupBy(x => x.Destination) + .Select(g => new StatDataDTO + { + Label = g.Key, + Value = g.Sum(x => x.RequestCount) + }) + .OrderByDescending(x => x.Value) + .ToList(); + result.Add(new StatInfoDto() + { + Title = "Dest.Request (#)", + DataCollection = pDestRequest, + Grouping = StatInfoDto.AggrLevel.Service, + Type = StatInfoDto.DataType.Count + }); + + var pDestDuration = rawData.GroupBy(x => x.Destination) + .Select(g => new StatDataDTO + { + Label = g.Key, + Value = g.Sum(x => x.RequestCount * x.AvgDuration) + }) + .OrderByDescending(x => x.Value) + .ToList(); + result.Add(new StatInfoDto() + { + Title = "Dest.Duration (ms)", + DataCollection = pDestDuration, + Grouping = StatInfoDto.AggrLevel.Service, + Type = StatInfoDto.DataType.AvgDuration + }); + + var pTypeRequest = rawData.GroupBy(x => x.Type) + .Select(g => new StatDataDTO + { + Label = g.Key, + Value = g.Sum(x => x.RequestCount) + }) + .OrderByDescending(x => x.Value) + .ToList(); + result.Add(new StatInfoDto() + { + Title = "Type.Request (#)", + DataCollection = pTypeRequest, + Grouping = StatInfoDto.AggrLevel.Method, + Type = StatInfoDto.DataType.Count + }); + + var pTypeDuration = rawData.GroupBy(x => x.Type) + .Select(g => new StatDataDTO + { + Label = g.Key, + Value = g.Sum(x => x.RequestCount * x.AvgDuration) + }) + .OrderByDescending(x => x.Value) + .ToList(); + + result.Add(new StatInfoDto() + { + Title = "Type.Duration (ms)", + DataCollection = pTypeDuration, + Grouping = StatInfoDto.AggrLevel.Method, + Type = StatInfoDto.DataType.AvgDuration + }); + + return result; +#if false Dictionary> result = new(); DateTime adesso = DateTime.Now; var rawData = await GetFiltAsync(adesso.AddDays(-1), adesso); @@ -217,8 +289,10 @@ namespace MP.Data.Services.Utils //result.Add("DestType.Duration (ms)", pDestTypeDuration); return result; +#endif } + #endregion Protected Methods #region Private Fields diff --git a/MP.IOC/Components/Pages/CallStats.razor b/MP.IOC/Components/Pages/CallStats.razor index a6ff3ac0..eff4482b 100644 --- a/MP.IOC/Components/Pages/CallStats.razor +++ b/MP.IOC/Components/Pages/CallStats.razor @@ -22,9 +22,9 @@
    @foreach (var item in ParetoDay) { -
  • - @item.Key -
  • diff --git a/MP.IOC/Components/Pages/CallStats.razor.cs b/MP.IOC/Components/Pages/CallStats.razor.cs index ba03d5ad..570881b2 100644 --- a/MP.IOC/Components/Pages/CallStats.razor.cs +++ b/MP.IOC/Components/Pages/CallStats.razor.cs @@ -26,8 +26,7 @@ namespace MP.IOC.Components.Pages private string currHistId = ""; - private string currId = ""; - + private StatInfoDto? currStatSel = null; private string currPieId = ""; private string currTitle = ""; @@ -38,7 +37,7 @@ namespace MP.IOC.Components.Pages private int numDays = 5; - private Dictionary> ParetoDay = new(); + private List ParetoDay = new(); private List tsData = new List(); @@ -107,30 +106,29 @@ namespace MP.IOC.Components.Pages private string CheckSelect(string curKey) { - return !string.IsNullOrEmpty(currId) && currId == curKey ? "active" : ""; + return currStatSel != null && currStatSel.Title == curKey ? "active" : ""; } private void DoReset() { - currId = ""; + currStatSel = null; currHistId = ""; currPieId = ""; currTsId = ""; currData = new(); } - private void DoSelect(string reqKey) + private void DoSelect(StatInfoDto currStat) { - if (ParetoDay.ContainsKey(reqKey)) - { - currId = reqKey; - currHistId = $"Bar_{reqKey}"; - currPieId = $"Pie_{reqKey}"; - currTsId = $"TS_{reqKey}"; - currTitle = $"Pareto | {reqKey}"; - currData = ParetoDay[reqKey]; - tsDataDetail = new(); - } + // salvo dati! + currStatSel = currStat; + string reqKey = currStat.Title; + currHistId = $"Bar_{reqKey}"; + currPieId = $"Pie_{reqKey}"; + currTsId = $"TS_{reqKey}"; + currTitle = $"Pareto | {reqKey}"; + currData = currStat.DataCollection; + tsDataDetail = new(); } private List GetDistinctColors(int numRecords, string alpha) @@ -218,7 +216,13 @@ namespace MP.IOC.Components.Pages var machDay = await SAggService.GetParetoStatsDayAsync(0); foreach (var item in machDay) { - ParetoDay.TryAdd(item.Key, item.Value.Where(x => x.Label != "ALL").ToList()); + ParetoDay.Add(new StatInfoDto() + { + Title = item.Key, + DataCollection = item.Value.Where(x => x.Label != "ALL").ToList(), + Grouping = StatInfoDto.AggrLevel.Machine, + Type = item.Key.Contains("#") ? StatInfoDto.DataType.Count : StatInfoDto.DataType.AvgDuration + }); } } @@ -229,18 +233,52 @@ namespace MP.IOC.Components.Pages private async Task ShowDetail(string selDetail) { currDetail = selDetail; - // recupero dettaglio 7gg... - DateTime adesso = DateTime.Now; - List rawData = await SDetService.GetFiltAsync(adesso.AddDays(-numDays), adesso, "", selDetail); - // conversione con grouping - tsData = rawData.Select(r => new chartJsData.chartJsTSerie() { x = r.Hour, y = (double)r.AvgDuration }) - .OrderBy(o => o.x) - .ToList(); - // ...secondo tipo richiesto (duration/count) - bool showCount = currHistId.Contains("(#)"); - tsDataDetail = SDetService.GetTimeSeriesData(rawData, showCount); - lineTitles = tsDataDetail.Select(x => x.SeriesName).ToList(); - TSDataMulti = tsDataDetail.Select(x => x.DataPoints).ToList(); + if (currStatSel != null) + { + DateTime adesso = DateTime.Now; + bool isCount = currStatSel.Type == StatInfoDto.DataType.Count; + bool grpMachine = currStatSel.Grouping == StatInfoDto.AggrLevel.Machine; + switch (currStatSel.Grouping) + { + case StatInfoDto.AggrLevel.Service: + case StatInfoDto.AggrLevel.Machine: + // recupero statistiche aggregate 7 gg.. + var rawAggrData = await SAggService.GetFiltAsync(adesso.AddDays(-numDays), adesso); + // escludo gli "all" + // filtro x selezione (macchina o servizio)... + if (grpMachine) + { + rawAggrData = rawAggrData.Where(x => x.MachineId == selDetail).ToList(); + } + else + { + rawAggrData = rawAggrData.Where(x => !x.MachineId.Equals("all", StringComparison.InvariantCultureIgnoreCase) && x.Destination == selDetail).ToList(); + } + // conversione con grouping + tsData = rawAggrData.Select(r => new chartJsData.chartJsTSerie() { x = r.Hour, y = (double)r.AvgDuration }) + .OrderBy(o => o.x) + .ToList(); + // ...secondo tipo richiesto (macchina/server e duration/count) + tsDataDetail = SAggService.GetTimeSeriesData(rawAggrData, grpMachine, isCount); + break; + case StatInfoDto.AggrLevel.Method: + // recupero dettaglio 7gg... + List rawData = await SDetService.GetFiltAsync(adesso.AddDays(-numDays), adesso, "", selDetail); + // conversione con grouping + tsData = rawData.Select(r => new chartJsData.chartJsTSerie() { x = r.Hour, y = (double)r.AvgDuration }) + .OrderBy(o => o.x) + .ToList(); + // ...secondo tipo richiesto (duration/count) + tsDataDetail = SDetService.GetTimeSeriesData(rawData, isCount); + break; + case StatInfoDto.AggrLevel.None: + default: + break; + } + lineTitles = tsDataDetail.Select(x => x.SeriesName).ToList(); + TSDataMulti = tsDataDetail.Select(x => x.DataPoints).ToList(); + } + } #endregion Private Methods diff --git a/MP.IOC/Data/MpDataService.cs b/MP.IOC/Data/MpDataService.cs index 6e204f39..ee1733c8 100644 --- a/MP.IOC/Data/MpDataService.cs +++ b/MP.IOC/Data/MpDataService.cs @@ -3447,10 +3447,10 @@ namespace MP.IOC.Data /// public async Task RemRebootLogAddAsync(RemoteRebootLogModel newRec) { - // verifica preliminare ultima esecuzione (max 1 ogni 15 min...) + // verifica preliminare ultima esecuzione (max 1 ogni 60 min...) DateTime adesso = DateTime.Now; bool doClean = false; - if (adesso.Subtract(lastCleanupRRL).TotalMinutes > 15) + if (adesso.Subtract(lastCleanupRRL).TotalMinutes > 60) { lastCleanupRRL = adesso; doClean = true; diff --git a/MP.IOC/MP.IOC.csproj b/MP.IOC/MP.IOC.csproj index 699cb259..3e004ed0 100644 --- a/MP.IOC/MP.IOC.csproj +++ b/MP.IOC/MP.IOC.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 8.16.2604.2809 + 8.16.2604.2818 diff --git a/MP.IOC/Resources/ChangeLog.html b/MP.IOC/Resources/ChangeLog.html index acb718e8..f998854e 100644 --- a/MP.IOC/Resources/ChangeLog.html +++ b/MP.IOC/Resources/ChangeLog.html @@ -1,6 +1,6 @@ Modulo MP-IOC -

    Versione: 8.16.2604.2809

    +

    Versione: 8.16.2604.2818


    Note di rilascio:
    • diff --git a/MP.IOC/Resources/VersNum.txt b/MP.IOC/Resources/VersNum.txt index f4970186..fccf3868 100644 --- a/MP.IOC/Resources/VersNum.txt +++ b/MP.IOC/Resources/VersNum.txt @@ -1 +1 @@ -8.16.2604.2809 +8.16.2604.2818 diff --git a/MP.IOC/Resources/manifest.xml b/MP.IOC/Resources/manifest.xml index 5ad3ec09..833e7c55 100644 --- a/MP.IOC/Resources/manifest.xml +++ b/MP.IOC/Resources/manifest.xml @@ -1,6 +1,6 @@ - 8.16.2604.2809 + 8.16.2604.2818 https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/MP.IOC.zip https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/ChangeLog.html false