diff --git a/MP.Core/Conf/RedisScriptsConfig.cs b/MP.Core/Conf/RedisScriptsConfig.cs new file mode 100644 index 00000000..c33b5cbd --- /dev/null +++ b/MP.Core/Conf/RedisScriptsConfig.cs @@ -0,0 +1,7 @@ +namespace MP.Core.Conf +{ + public class RedisScriptsConfig + { + public Dictionary Scripts { get; set; } = new(); + } +} diff --git a/MP.IOC/MP.IOC.csproj b/MP.IOC/MP.IOC.csproj index a5e21b92..d02afa01 100644 --- a/MP.IOC/MP.IOC.csproj +++ b/MP.IOC/MP.IOC.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 6.16.2604.2008 + 6.16.2604.2010 @@ -48,6 +48,12 @@ Always + + Always + + + Always + diff --git a/MP.IOC/Program.cs b/MP.IOC/Program.cs index 45e518de..178dce5c 100644 --- a/MP.IOC/Program.cs +++ b/MP.IOC/Program.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Http.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.OpenApi.Models; +using MP.Core.Conf; using MP.Data; using MP.Data.Repository.Utils; using MP.Data.Services.Utils; @@ -59,11 +60,16 @@ builder.Services.AddSingleton(sp => }); logger.Info("YARP reverse proxy configured"); +builder.Services.Configure( + builder.Configuration.GetSection("RedisScripts")); +logger.Info("RedisScript Provider configured"); + // base services builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); +builder.Services.AddSingleton(); // Registra i servizi per Blazor builder.Services.AddRazorComponents() diff --git a/MP.IOC/RedisScript/RedisUpdateScript_v5.lua b/MP.IOC/RedisScript/RedisUpdateScript_v5.lua new file mode 100644 index 00000000..1b4a2477 --- /dev/null +++ b/MP.IOC/RedisScript/RedisUpdateScript_v5.lua @@ -0,0 +1,33 @@ +-- RedisUpdateScript_v5 +local key = KEYS[1] +local countInc = tonumber(ARGV[1]) or 0 +local totalMsInc = tonumber(ARGV[2]) or 0 +local newMax = tonumber(ARGV[3]) +local newMin = tonumber(ARGV[4]) +local sentinel = tonumber(ARGV[5]) + +-- Incrementi base +redis.call('HINCRBY', key, 'count', countInc) +redis.call('HINCRBYFLOAT', key, 'totalMs', totalMsInc) + +-- MAX +local currentMaxStr = redis.call('HGET', key, 'maxMs') +local currentMax = tonumber(currentMaxStr) + +if newMax ~= nil and newMax < sentinel then + if currentMax == nil or newMax > currentMax then + redis.call('HSET', key, 'maxMs', newMax) + end +end + +-- MIN +local currentMinStr = redis.call('HGET', key, 'minMs') +local currentMin = tonumber(currentMinStr) + +if newMin ~= nil and newMin < sentinel then + if currentMin == nil or newMin < currentMin then + redis.call('HSET', key, 'minMs', newMin) + end +end + +return 1 diff --git a/MP.IOC/RedisScript/RedisUpdateScript_v6.lua b/MP.IOC/RedisScript/RedisUpdateScript_v6.lua new file mode 100644 index 00000000..230ed7f9 --- /dev/null +++ b/MP.IOC/RedisScript/RedisUpdateScript_v6.lua @@ -0,0 +1,33 @@ +-- RedisUpdateScript_v6 +local key = KEYS[1] +local countInc = tonumber(ARGV[1]) or 0 +local totalMsInc = tonumber(ARGV[2]) or 0 +local newMax = tonumber(ARGV[3]) +local newMin = tonumber(ARGV[4]) +local sentinel = tonumber(ARGV[5]) + +-- Incrementi +redis.call('HINCRBY', key, 'count', countInc) +redis.call('HINCRBYFLOAT', key, 'totalMs', totalMsInc) + +-- MAX +local currentMaxStr = redis.call('HGET', key, 'maxMs') +local currentMax = tonumber(currentMaxStr) + +if newMax ~= nil and newMax < sentinel then + if currentMax == nil or newMax > currentMax then + redis.call('HSET', key, 'maxMs', tostring(newMax)) + end +end + +-- MIN +local currentMinStr = redis.call('HGET', key, 'minMs') +local currentMin = tonumber(currentMinStr) + +if newMin ~= nil and newMin < sentinel then + if currentMin == nil or newMin < currentMin then + redis.call('HSET', key, 'minMs', tostring(newMin)) + end +end + +return 1 diff --git a/MP.IOC/Resources/ChangeLog.html b/MP.IOC/Resources/ChangeLog.html index 00cc29dd..208bcf27 100644 --- a/MP.IOC/Resources/ChangeLog.html +++ b/MP.IOC/Resources/ChangeLog.html @@ -1,6 +1,6 @@ Modulo MP-IOC -

Versione: 6.16.2604.2008

+

Versione: 6.16.2604.2010


Note di rilascio:
  • diff --git a/MP.IOC/Resources/VersNum.txt b/MP.IOC/Resources/VersNum.txt index 9c6c9fcd..f93ea23a 100644 --- a/MP.IOC/Resources/VersNum.txt +++ b/MP.IOC/Resources/VersNum.txt @@ -1 +1 @@ -6.16.2604.2008 +6.16.2604.2010 diff --git a/MP.IOC/Resources/manifest.xml b/MP.IOC/Resources/manifest.xml index 2d3dc349..ddfff283 100644 --- a/MP.IOC/Resources/manifest.xml +++ b/MP.IOC/Resources/manifest.xml @@ -1,6 +1,6 @@ - 6.16.2604.2008 + 6.16.2604.2010 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 diff --git a/MP.IOC/Services/LuaScriptProvider.cs b/MP.IOC/Services/LuaScriptProvider.cs new file mode 100644 index 00000000..c7eb1e58 --- /dev/null +++ b/MP.IOC/Services/LuaScriptProvider.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.Options; +using MP.Core.Conf; + +namespace MP.IOC.Services +{ + public sealed class LuaScriptProvider + { + private readonly Dictionary _scripts; + + public IReadOnlyDictionary Scripts => _scripts; + + public LuaScriptProvider( + IOptions cfg, + IWebHostEnvironment env) + { + _scripts = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var kv in cfg.Value.Scripts) + { + var name = kv.Key; + var relativePath = kv.Value; + + var fullPath = Path.Combine(env.ContentRootPath, relativePath); + + if (!File.Exists(fullPath)) + throw new FileNotFoundException($"Script Lua non trovato: {fullPath}"); + + var content = File.ReadAllText(fullPath); + + _scripts[name] = content; + } + } + + public string Get(string name) + { + if (!_scripts.TryGetValue(name, out var script)) + throw new KeyNotFoundException($"Script Lua '{name}' non trovato."); + + return script; + } + } + +} diff --git a/MP.IOC/Services/MetricsCalcService.cs b/MP.IOC/Services/MetricsCalcService.cs index 9ec38409..effa805c 100644 --- a/MP.IOC/Services/MetricsCalcService.cs +++ b/MP.IOC/Services/MetricsCalcService.cs @@ -14,12 +14,16 @@ namespace MP.IOC.Services /// /// /// - public MetricsCalcService(RouteStatsManager stats, IConfiguration config, IConnectionMultiplexer mux) + public MetricsCalcService(RouteStatsManager stats, + LuaScriptProvider luaProvider, + IConfiguration config, + IConnectionMultiplexer mux) { _stats = stats; _config = config; _db = mux.GetDatabase(); _redisBaseKey = _config.GetValue("ServerConf:RedisBaseKey") ?? "MP_IOC"; + _updateScript = luaProvider.Get("Update"); } #endregion Public Constructors @@ -33,11 +37,18 @@ namespace MP.IOC.Services /// private const string SentinelValue = "999999999"; + /// + /// Script update Redis in Lua, recuperato dal provider script. + /// Lo script gestisce l'incremento e la logica condizionale per Min/Max in un'unica operazione atomica. + /// + private readonly string _updateScript; + +#if false /// /// Script update Redis in Lua, definito come costante per efficienza. /// Lo script gestisce l'incremento e la logica condizionale per Min/Max in un'unica operazione atomica. /// - private const string RedisUpdateScript = @" + private const string RedisUpdateScript_v6 = @" local key = KEYS[1] local countInc = tonumber(ARGV[1]) local totalMsInc = tonumber(ARGV[2]) @@ -63,6 +74,45 @@ namespace MP.IOC.Services return 1 "; + + private const string RedisUpdateScript_v5 = @" + local key = KEYS[1] + local countInc = tonumber(ARGV[1]) or 0 + local totalMsInc = tonumber(ARGV[2]) or 0 + local newMax = tonumber(ARGV[3]) + local newMin = tonumber(ARGV[4]) + local sentinel = tonumber(ARGV[5]) + + -- Incrementi base + redis.call('HINCRBY', key, 'count', countInc) + redis.call('HINCRBYFLOAT', key, 'totalMs', totalMsInc) + + -- MAX + local currentMaxStr = redis.call('HGET', key, 'maxMs') + local currentMax = tonumber(currentMaxStr) + + if newMax ~= nil and newMax < sentinel then + if currentMax == nil or newMax > currentMax then + redis.call('HSET', key, 'maxMs', newMax) + end + end + + -- MIN + local currentMinStr = redis.call('HGET', key, 'minMs') + local currentMin = tonumber(currentMinStr) + + if newMin ~= nil and newMin < sentinel then + if currentMin == nil or newMin < currentMin then + redis.call('HSET', key, 'minMs', newMin) + end + end + + return 1 + + "; +#endif + + // Classe di supporto per l'aggregazione locale dei valori Daily private class AggregatedStats { @@ -124,7 +174,7 @@ namespace MP.IOC.Services var hourScore = ToEpochSeconds(hourStart); // Usiamo lo script Lua per l'aggiornamento atomico dell'ora - tasks.Add(batch.ScriptEvaluateAsync(RedisUpdateScript, + tasks.Add(batch.ScriptEvaluateAsync(_updateScript, new RedisKey[] { hourKey }, new RedisValue[] { count.ToString(), @@ -167,7 +217,7 @@ namespace MP.IOC.Services ? double.Parse(SentinelValue) : agg.MinMs; - tasks.Add(batch.ScriptEvaluateAsync(RedisUpdateScript, + tasks.Add(batch.ScriptEvaluateAsync(_updateScript, new RedisKey[] { key }, new RedisValue[] { agg.Count.ToString(), diff --git a/MP.IOC/appsettings.Production.json b/MP.IOC/appsettings.Production.json index 703dacca..82f4a9bb 100644 --- a/MP.IOC/appsettings.Production.json +++ b/MP.IOC/appsettings.Production.json @@ -31,5 +31,10 @@ "Redis": "localhost:6379,DefaultDatabase=5,connectTimeout=5000,syncTimeout=5000,asyncTimeout=5000,abortConnect=false,ssl=false", "RedisAdmin": "localhost:6379,DefaultDatabase=5,connectTimeout=5000,syncTimeout=5000,asyncTimeout=5000,abortConnect=false,ssl=false,allowAdmin=true", "mdbConnString": "mongodb://localhost:27017" + }, + "RedisScripts": { + "Scripts": { + "Update": "RedisScript/RedisUpdateScript_v5.lua" + } } } diff --git a/MP.IOC/appsettings.json b/MP.IOC/appsettings.json index b54e18dc..64887672 100644 --- a/MP.IOC/appsettings.json +++ b/MP.IOC/appsettings.json @@ -88,6 +88,11 @@ "redisLongTimeCache": 60, "redisShortTimeCache": 30 }, + "RedisScripts": { + "Scripts": { + "Update": "RedisScript/RedisUpdateScript_v6.lua" + } + }, "ConnectionStrings": { "MP.Data": "Server=SQL2016DEV;Database=MoonPro; User ID=sa;Password=keyhammer16; integrated security=False; App=MP.IOC;", "MP.Flux": "Server=SQL2016DEV;Database=MoonPro_FluxData; User ID=sa;Password=keyhammer16; integrated security=False; MultipleActiveResultSets=True; App=MP.SPEC;",