318 lines
11 KiB
C#
318 lines
11 KiB
C#
using EgwCoreLib.Lux.Core.Stats;
|
|
using StackExchange.Redis;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace EgwCoreLib.Lux.Data.Services
|
|
{
|
|
/// <summary>
|
|
/// Gestione servizio indice richieste
|
|
/// </summary>
|
|
public class CalcRuidService
|
|
{
|
|
#region Public Constructors
|
|
|
|
public CalcRuidService(ConnectionMultiplexer redis, TimeSpan retention, string redisBaseKey)
|
|
{
|
|
_db = redis.GetDatabase();
|
|
_retention = retention;
|
|
_base = redisBaseKey.TrimEnd(':');
|
|
//_base = redisBaseKey.EndsWith(":") ? redisBaseKey : redisBaseKey + ":";
|
|
}
|
|
|
|
#endregion Public Constructors
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Metodo Creazione nuova richiesta
|
|
/// </summary>
|
|
/// <param name="envir">Environment calcolo</param>
|
|
/// <param name="tipo">Tipologia richiesta</param>
|
|
/// <param name="uid">UID di riferimento</param>
|
|
/// <returns>restituisce il valore del RUID (ID univoco richiesta)</returns>
|
|
public async Task<string> AddRequestAsync(string envir, string tipo, string uid)
|
|
{
|
|
var ruid = GenerateRuid();
|
|
var processStart = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
|
|
var hashKey = GetRequestKey(ruid);
|
|
var setKey = GetSortedSetKey(envir, tipo);
|
|
var uidKey = GetUidSetKey(uid);
|
|
var combKey = GetCombinationsKey();
|
|
|
|
var batch = _db.CreateBatch();
|
|
|
|
// NON await qui!
|
|
var t1 = batch.HashSetAsync(hashKey, new HashEntry[]
|
|
{
|
|
new HashEntry("processStart", processStart),
|
|
new HashEntry("UID", uid),
|
|
new HashEntry("tipo", tipo),
|
|
new HashEntry("envir", envir)
|
|
});
|
|
|
|
var t2 = batch.SortedSetAddAsync(setKey, ruid, processStart);
|
|
var t3 = batch.SetAddAsync(uidKey, ruid);
|
|
|
|
string comb = $"{envir}|{tipo}";
|
|
var t4 = batch.SetAddAsync(combKey, comb);
|
|
|
|
RedisKey minuteKey = Key($"stats:requests:count:{DateTime.UtcNow:yyyyMMddHHmm}");
|
|
RedisKey hourKey = Key($"stats:requests:count:{DateTime.UtcNow:yyyyMMddHH}");
|
|
|
|
var t5 = batch.StringIncrementAsync(minuteKey);
|
|
var t6 = batch.StringIncrementAsync(hourKey);
|
|
|
|
// Esegue il batch
|
|
batch.Execute();
|
|
|
|
// Ora puoi attendere le task
|
|
await Task.WhenAll(t1, t2, t3, t4, t5, t6);
|
|
|
|
return ruid;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Metodo di Cleanup periodico
|
|
/// </summary>
|
|
/// <param name="environment">Environment calcolo</param>
|
|
/// <param name="tipo">Tipologia richiesta</param>
|
|
/// <returns></returns>
|
|
public async Task CleanupOldRequestsAsync(string environment, string tipo)
|
|
{
|
|
var cutoff = DateTimeOffset.UtcNow.Add(-_retention).ToUnixTimeMilliseconds();
|
|
var setKey = GetSortedSetKey(environment, tipo);
|
|
|
|
var oldIds = await _db.SortedSetRangeByScoreAsync(setKey, stop: cutoff);
|
|
if (oldIds.Length == 0) return;
|
|
|
|
var batch = _db.CreateBatch();
|
|
var tasks = new List<Task>();
|
|
|
|
foreach (var id in oldIds)
|
|
{
|
|
var ruid = id.ToString();
|
|
var hashKey = GetRequestKey(ruid);
|
|
|
|
var uid = await _db.HashGetAsync(hashKey, "UID");
|
|
if (!uid.IsNull)
|
|
{
|
|
var uidKey = GetUidSetKey(uid);
|
|
tasks.Add(batch.SetRemoveAsync(uidKey, ruid));
|
|
|
|
tasks.Add(batch.SetLengthAsync(uidKey).ContinueWith(t =>
|
|
{
|
|
if (t.Result == 0)
|
|
_db.KeyDelete(uidKey);
|
|
}));
|
|
}
|
|
|
|
tasks.Add(batch.KeyDeleteAsync(hashKey));
|
|
}
|
|
|
|
tasks.Add(batch.SortedSetRemoveRangeByScoreAsync(setKey, double.NegativeInfinity, cutoff));
|
|
|
|
tasks.Add(batch.SortedSetLengthAsync(setKey).ContinueWith(t =>
|
|
{
|
|
if (t.Result == 0)
|
|
_db.KeyDelete(setKey);
|
|
}));
|
|
|
|
batch.Execute();
|
|
await Task.WhenAll(tasks);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metodo di Aggiornamento richiesta esistente
|
|
/// </summary>
|
|
/// <param name="ruid">RUID richiesta</param>
|
|
/// <returns></returns>
|
|
public async Task CompleteRequestAsync(string ruid)
|
|
{
|
|
var hashKey = GetRequestKey(ruid);
|
|
var processEnd = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
|
|
var processStartValue = await _db.HashGetAsync(hashKey, "processStart");
|
|
if (processStartValue.IsNull) return;
|
|
|
|
var processStart = (long)processStartValue;
|
|
var elapsed = (double)(processEnd - processStart) / 1000.0; // secondi con decimali
|
|
|
|
var batch = _db.CreateBatch();
|
|
|
|
var t1 = batch.HashSetAsync(hashKey, new HashEntry[]
|
|
{
|
|
new HashEntry("processEnd", processEnd),
|
|
new HashEntry("processElapsed", elapsed)
|
|
});
|
|
|
|
RedisKey hourKeySum = Key($"stats:processing:sum:{DateTime.UtcNow:yyyyMMddHH}");
|
|
RedisKey hourKeyMax = Key($"stats:processing:max:{DateTime.UtcNow:yyyyMMddHH}");
|
|
|
|
var t2 = batch.StringIncrementAsync(hourKeySum, elapsed);
|
|
|
|
var currentMax = await _db.StringGetAsync(hourKeyMax);
|
|
Task t3 = Task.CompletedTask;
|
|
|
|
if (currentMax.IsNull || double.Parse(currentMax) < elapsed)
|
|
t3 = batch.StringSetAsync(hourKeyMax, elapsed);
|
|
|
|
batch.Execute();
|
|
|
|
await Task.WhenAll(t1, t2, t3);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metodo Recupero combinazioni envir/tipo
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public async Task<IEnumerable<(string env, string tipo)>> GetCombinationsAsync()
|
|
{
|
|
var members = await _db.SetMembersAsync(GetCombinationsKey());
|
|
return members
|
|
.Select(x => x.ToString().Split('|'))
|
|
.Select(a => (a[0], a[1]));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metodo Recupero richieste per UID
|
|
/// </summary>
|
|
/// <param name="uid"></param>
|
|
/// <returns></returns>
|
|
public async Task<IEnumerable<string>> GetRequestsByUidAsync(string uid)
|
|
{
|
|
var uidKey = GetUidSetKey(uid);
|
|
var members = await _db.SetMembersAsync(uidKey);
|
|
return members.Select(x => x.ToString());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metodo Statistiche aggregate
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public async Task<object> GetStatsAsync()
|
|
{
|
|
RedisKey minuteKey = Key($"stats:requests:count:{DateTime.UtcNow:yyyyMMddHHmm}");
|
|
RedisKey hourKey = Key($"stats:requests:count:{DateTime.UtcNow:yyyyMMddHH}");
|
|
RedisKey hourSumKey = Key($"stats:processing:sum:{DateTime.UtcNow:yyyyMMddHH}");
|
|
RedisKey hourMaxKey = Key($"stats:processing:max:{DateTime.UtcNow:yyyyMMddHH}");
|
|
|
|
var minuteCount = await _db.StringGetAsync(minuteKey);
|
|
var hourCount = await _db.StringGetAsync(hourKey);
|
|
var sum = await _db.StringGetAsync(hourSumKey);
|
|
var max = await _db.StringGetAsync(hourMaxKey);
|
|
|
|
long minCnt = minuteCount.IsNull ? 0 : (long)minuteCount;
|
|
long hourCnt = hourCount.IsNull ? 0 : (long)hourCount;
|
|
|
|
double sumVal = sum.IsNull ? 0 : (double)sum;
|
|
double maxVal = max.IsNull ? 0 : (double)max;
|
|
|
|
double avg = hourCnt > 0 ? sumVal / hourCnt : 0;
|
|
|
|
return new
|
|
{
|
|
RequestsLastMinute = minCnt,
|
|
RequestsLastHour = hourCnt,
|
|
ProcessingAvgLastHour = avg,
|
|
ProcessingMaxLastHour = maxVal
|
|
};
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Restituisce le statistiche per range date
|
|
/// </summary>
|
|
/// <param name="from"></param>
|
|
/// <param name="to"></param>
|
|
/// <returns></returns>
|
|
public async Task<StatsRangeDto> GetStatsRangeAsync(DateTime from, DateTime to)
|
|
{
|
|
var dto = new StatsRangeDto();
|
|
|
|
var hourKeys = new List<RedisKey>();
|
|
var sumKeys = new List<RedisKey>();
|
|
var maxKeys = new List<RedisKey>();
|
|
|
|
var cursor = from;
|
|
|
|
while (cursor <= to)
|
|
{
|
|
hourKeys.Add(Key($"stats:requests:count:{cursor:yyyyMMddHH}"));
|
|
sumKeys.Add(Key($"stats:processing:sum:{cursor:yyyyMMddHH}"));
|
|
maxKeys.Add(Key($"stats:processing:max:{cursor:yyyyMMddHH}"));
|
|
|
|
dto.HourLabels.Add(cursor.ToString("dd/MM HH:mm"));
|
|
cursor = cursor.AddHours(1);
|
|
}
|
|
|
|
var batch = _db.CreateBatch();
|
|
|
|
var hourTasks = hourKeys.Select(k => batch.StringGetAsync(k)).ToArray();
|
|
var sumTasks = sumKeys.Select(k => batch.StringGetAsync(k)).ToArray();
|
|
var maxTasks = maxKeys.Select(k => batch.StringGetAsync(k)).ToArray();
|
|
|
|
batch.Execute();
|
|
|
|
await Task.WhenAll(hourTasks);
|
|
await Task.WhenAll(sumTasks);
|
|
await Task.WhenAll(maxTasks);
|
|
|
|
for (int i = 0; i < hourTasks.Length; i++)
|
|
{
|
|
long req = hourTasks[i].Result.IsNull ? 0 : (long)hourTasks[i].Result;
|
|
double sum = sumTasks[i].Result.IsNull ? 0 : (double)sumTasks[i].Result;
|
|
double max = maxTasks[i].Result.IsNull ? 0 : (double)maxTasks[i].Result;
|
|
|
|
dto.Requests.Add(req);
|
|
dto.MaxProcessing.Add(max);
|
|
|
|
double avg = req > 0 ? sum / req : 0;
|
|
dto.AvgProcessing.Add(avg);
|
|
}
|
|
|
|
return dto;
|
|
}
|
|
|
|
#endregion Public Methods
|
|
|
|
#region Private Fields
|
|
|
|
private readonly string _base;
|
|
private readonly IDatabase _db;
|
|
private readonly TimeSpan _retention;
|
|
private readonly Random _rnd = new Random();
|
|
|
|
#endregion Private Fields
|
|
|
|
#region Private Methods
|
|
|
|
/// <summary>
|
|
/// Generatore RUID:
|
|
/// ID incrementale = timestamp ms + random 4 chars HEX
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private string GenerateRuid()
|
|
{
|
|
long ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
string rand = Convert.ToString(_rnd.Next(0x1000, 0xFFFF), 16).ToUpper();
|
|
return $"{ts}-{rand}";
|
|
}
|
|
|
|
private string GetCombinationsKey() => Key("req:combinations");
|
|
|
|
private string GetRequestKey(string ruid) => Key($"request:{ruid}");
|
|
|
|
private string GetSortedSetKey(string envir, string tipo) => Key($"requests:{envir}:{tipo}");
|
|
|
|
private string GetUidSetKey(string uid) => Key($"uid:{uid}:requests");
|
|
|
|
private RedisKey Key(string suffix) => (RedisKey)($"{_base}:RUID:{suffix}");
|
|
|
|
#endregion Private Methods
|
|
}
|
|
} |