diff --git a/EgwCoreLib.Lux.Data/Services/IRedisService.cs b/EgwCoreLib.Lux.Data/Services/IRedisService.cs index 4525ea5c..f6138f91 100644 --- a/EgwCoreLib.Lux.Data/Services/IRedisService.cs +++ b/EgwCoreLib.Lux.Data/Services/IRedisService.cs @@ -22,6 +22,19 @@ namespace EgwCoreLib.Lux.Data.Services bool Set(string key, string value, TimeSpan? expiry = null); string? Get(string key); + + long QueueCount(RedisKey queueName); + Task QueueCountAsync(RedisKey queueName); + List QueueListAll(RedisKey queueName); + Task> QueueListAllAsync(RedisKey queueName); + RedisValue QueuePop(RedisKey queueName); + Task QueuePopAsync(RedisKey queueName); + List QueuePopAll(RedisKey queueName); + Task> QueuePopAllAsync(RedisKey queueName); + List QueuePopList(RedisKey queueName, int maxElem); + Task> QueuePopListAsync(RedisKey queueName, int maxElem); + long QueuePush(RedisKey queueName, RedisValue value); + Task QueuePushAsync(RedisKey queueName, RedisValue value); } } diff --git a/EgwCoreLib.Lux.Data/Services/ProdService.cs b/EgwCoreLib.Lux.Data/Services/ProdService.cs new file mode 100644 index 00000000..fc036267 --- /dev/null +++ b/EgwCoreLib.Lux.Data/Services/ProdService.cs @@ -0,0 +1,157 @@ +using EgwCoreLib.Lux.Core.RestPayload; +using EgwCoreLib.Lux.Data.Controllers; +using EgwMultiEngineManager.Data; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; +using NLog; +using StackExchange.Redis; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EgwCoreLib.Lux.Data.Services +{ + public class ProdService : BaseServ + { + #region Public Constructors + + public ProdService(IConfiguration configuration, IConnectionMultiplexer RedisConn, IRedisService redisService) : base(configuration, RedisConn) + { + // conf redis service + _redisService = redisService; + chPub = _config.GetValue("ServerConf:ChannelPub") ?? ""; + queueKey = (RedisKey)$"{redisBaseKey}:OrderQueue"; + redisOrderReqKey = $"{redisBaseKey}:OrderReq"; + redisOrderRunKey = $"{redisBaseKey}:OrderRun"; + redisOrderDoneKey = $"{redisBaseKey}:OrderDone"; + Log.Info($"ProdService | Init OK"); + } + + #endregion Public Constructors + + #region Public Methods + + /// + /// Accoda una richiesta di calcolo + /// + /// + /// + /// + /// + public async Task EnqueueRequest(string reqType, string reqUid, CalcRequestDTO currRequest) + { + bool done = false; + int nId = 1; + // salvo su cache x successivo reinvio da currRequest + QuestionDTO calcRequest = new QuestionDTO(nId, currRequest.EnvType, currRequest.DictExec); + // salvo in cache contenuto della richiesta x UID + string currKey = $"{redisOrderReqKey}:{reqUid.Replace("/", ":")}"; + done = await _redisService.SetAsync(currKey, calcRequest.sProcessArgs); + // accodo la nuova richiesta + //RedisKey queueKey = (RedisKey)$"{redisBaseKey}:OrderQueue:{reqType}"; + _redisService.QueuePush(queueKey, (RedisValue)reqUid); + // dizionario richieste: è il serializzato dell'elenco degli UID da calcolare... + var currList = await _redisService.QueueListAllAsync(queueKey); + Dictionary calcDict = new Dictionary(); + calcDict.Add("ReqLen", $"{calcDict.Count}"); + string listReq = JsonConvert.SerializeObject(currList); + calcDict.Add("ReqList", listReq); + // preparo richiesta di calcolo x UID da inviare + QuestionDTO chRequest = new QuestionDTO(nId, currRequest.EnvType, calcDict); + // invio sul channel redis della richiesta di processing + await _redisService.PublishAsync(chPub, chRequest.sProcessArgs); + // ritorno + return done; + } + + /// + /// Restituzione singolo Job specifico + /// + /// + /// + public async Task GetJob(string id) + { + Stopwatch sw = new Stopwatch(); + sw.Start(); + string result = ""; + // recupero richiesta serializzata + string currKey = $"{redisOrderReqKey}:{id.Replace("/", ":")}"; + var rawRes = await _redisService.GetAsync(currKey); + if (!string.IsNullOrEmpty(rawRes)) + { + result = rawRes; + } + sw.Stop(); + Log.Info($"GetJob | {id} | {sw.Elapsed.TotalMilliseconds:N3} ms"); + return result; + } + + /// + /// Restituisce il contenuto della prox richiesta da eseguire + /// + /// + public async Task GetNext() + { + Stopwatch sw = new Stopwatch(); + sw.Start(); + string result = ""; + // prendo dalla coda primo job (rimuovendolo...) + var rawReq = await _redisService.QueuePopAsync(queueKey); + if (rawReq.HasValue) + { + string reqUid = $"{rawReq}"; + // metto UID in coda running + _redisService.QueuePush(queueKey, (RedisValue)reqUid); + // FixMe ToDo !!!: salvataggio data-ora per indicare avvio calcolo... + + // recupero richiesta serializzata + string currKey = $"{redisOrderReqKey}:{reqUid.Replace("/", ":")}"; + var rawRes = await _redisService.GetAsync(currKey); + if (!string.IsNullOrEmpty(rawRes)) + { + result = rawRes; + } + } + sw.Stop(); + Log.Info($"GetNext | {sw.Elapsed.TotalMilliseconds:N3} ms"); + return result; + } + + /// + /// Numero di Job in attesa su coda + /// + /// + public async Task QueueLen() + { + Stopwatch sw = new Stopwatch(); + sw.Start(); + long numWaiting = await _redisService.QueueCountAsync(queueKey); + sw.Stop(); + Log.Info($"QueueLen | {sw.Elapsed.TotalMilliseconds:N3} ms"); + return numWaiting; + } + + #endregion Public Methods + + #region Private Fields + + private static Logger Log = LogManager.GetCurrentClassLogger(); + private readonly IRedisService _redisService; + private readonly string chPub = ""; + + /// + /// Key della coda redis delle richieste x PROD Engine + /// + private RedisKey queueKey; + + private string redisBaseKey = "Lux:Prod"; + private string redisOrderDoneKey = ""; + private string redisOrderReqKey = ""; + private string redisOrderRunKey = ""; + + #endregion Private Fields + } +} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Services/RedisService.cs b/EgwCoreLib.Lux.Data/Services/RedisService.cs index 3265a1f3..d46813a5 100644 --- a/EgwCoreLib.Lux.Data/Services/RedisService.cs +++ b/EgwCoreLib.Lux.Data/Services/RedisService.cs @@ -91,6 +91,157 @@ namespace EgwCoreLib.Lux.Data.Services return numCli; } + /// + /// Conteggio elementi in QUEUE (FIFO) + /// + /// + public long QueueCount(RedisKey queueName) + { + return _db.ListLength(queueName); + } + + /// + /// Conteggio elementi in QUEUE (FIFO) + /// + /// + public async Task QueueCountAsync(RedisKey queueName) + { + return await _db.ListLengthAsync(queueName); + } + + /// + /// Recupero list di TUTTI i valori in QUEUE (FIFO) senza eliminare + /// + /// + /// num max di elementi da recuperare + public List QueueListAll(RedisKey queueName) + { + // lettura + reset in blocco + var listData = _db.ListRange(queueName, 0, -1).ToList(); + return listData; + } + + /// + /// Recupero list di TUTTI i valori in QUEUE (FIFO) senza eliminare + /// + /// + /// num max di elementi da recuperare + public async Task> QueueListAllAsync(RedisKey queueName) + { + // lettura + reset in blocco + var listData = await _db.ListRangeAsync(queueName, 0, -1); + return listData.ToList(); + } + + /// + /// Recupero valore in QUEUE (FIFO) + /// + /// + public RedisValue QueuePop(RedisKey queueName) + { + return _db.ListLeftPop(queueName); + } + + /// + /// Recupero list di TUTTI i valori in QUEUE (FIFO) eliminandoli dalla coda + /// + /// + /// num max di elementi da recuperare + public List QueuePopAll(RedisKey queueName) + { + // lettura + reset in blocco + var listData = _db.ListRange(queueName, 0, -1).ToList(); + if (listData.Count > 0) + { + _db.KeyDelete(queueName); // remove the entire list + } + return listData; + } + + /// + /// Recupero list di TUTTI i valori in QUEUE (FIFO) eliminandoli dalla coda in modo Async + /// + /// + /// num max di elementi da recuperare + public async Task> QueuePopAllAsync(RedisKey queueName) + { + // lettura + reset in blocco + var rawData = await _db.ListRangeAsync(queueName, 0, -1); + var listData = rawData.ToList(); + if (listData.Count > 0) + { + _db.KeyDelete(queueName); // remove the entire list + } + return listData; + } + + /// + /// Recupero valore in QUEUE (FIFO) Async + /// + /// + public async Task QueuePopAsync(RedisKey queueName) + { + return await _db.ListLeftPopAsync(queueName); + } + + /// + /// Recupero una list di valori in QUEUE (FIFO) + /// + /// + /// num max di elementi da recuperare + public List QueuePopList(RedisKey queueName, int maxElem) + { + // nuovo metodo con rimozione + var results = new List(maxElem); + for (int i = 0; i < maxElem; i++) + { + var item = _db.ListLeftPop(queueName); + if (item.IsNull) break; // queue empty + results.Add(item); + } + return results; + } + + /// + /// Recupero una list di valori in QUEUE (FIFO) + /// + /// + /// num max di elementi da recuperare + public async Task> QueuePopListAsync(RedisKey queueName, int maxElem) + { + // nuovo metodo con rimozione + var results = new List(maxElem); + for (int i = 0; i < maxElem; i++) + { + var item = await _db.ListLeftPopAsync(queueName); + if (item.IsNull) break; // queue empty + results.Add(item); + } + return results; + } + + /// + /// Scrittura valore in QUEUE (FIFO) + /// + /// + /// + public long QueuePush(RedisKey queueName, RedisValue value) + { + long qLen = _db.ListRightPush(queueName, value); + return qLen; + } + + /// + /// Scrittura valore in QUEUE (FIFO) + /// + /// + /// + public async Task QueuePushAsync(RedisKey queueName, RedisValue value) + { + long qLen = await _db.ListRightPushAsync(queueName, value); + return qLen; + } + /// /// Scrittura string su cache REDIS in modalità Async /// diff --git a/Lux.API/Controllers/ProdController.cs b/Lux.API/Controllers/ProdController.cs index 1f2887d6..73f9d3eb 100644 --- a/Lux.API/Controllers/ProdController.cs +++ b/Lux.API/Controllers/ProdController.cs @@ -1,11 +1,10 @@ using EgwCoreLib.Lux.Core.RestPayload; -using EgwCoreLib.Lux.Data; -using EgwCoreLib.Lux.Data.DbModel.Config; using EgwCoreLib.Lux.Data.Services; -using Microsoft.AspNetCore.Http; +using EgwMultiEngineManager.Data; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.SignalR; +using Newtonsoft.Json; using NLog; +using StackExchange.Redis; using System.Diagnostics; namespace Lux.API.Controllers @@ -16,11 +15,9 @@ namespace Lux.API.Controllers { #region Public Constructors - public ProdController(IConfiguration config, IRedisService redisService, ImageCacheService imgServ) + public ProdController(ProdService prodService) { - _config = config; - _redisService = redisService; - chPub = _config.GetValue("ServerConf:ChannelPub") ?? ""; + PService = prodService; } #endregion Public Constructors @@ -44,6 +41,155 @@ namespace Lux.API.Controllers return Ok("OK"); } + /// + /// Chiamata GET: + /// - fornisce il job da eseguire dalla coda (SE presente) + /// GET: api/Prod/getjob/ABC012345 + /// + /// + [HttpGet("getjob/{id}")] + public async Task> GetJob(string id) + { + var result = await PService.GetJob(id); + return Ok(result); + } + + /// + /// Chiamata GET: + /// - fornisce il primo job da eseguire dalla coda (SE presente) + /// - viene registrato come "in corso" e spostato dalla coda richiesta + /// GET: api/Prod/getnext + /// + /// + [HttpGet("getnext")] + public async Task> GetNext() + { + var result = await PService.GetNext(); + return Ok(result); +#if false + Stopwatch sw = new Stopwatch(); + sw.Start(); + string result = ""; + // prendo dalla coda primo job (rimuovendolo...) + var rawReq = await _redisService.QueuePopAsync(queueKey); + if (rawReq.HasValue) + { + string reqUid = $"{rawReq}"; + // metto UID in coda running + _redisService.QueuePush(queueKey, (RedisValue)reqUid); + // FixMe ToDo !!!: salvataggio data-ora per indicare avvio calcolo... + + // recupero richiesta serializzata + string currKey = $"{redisOrderReqKey}:{reqUid.Replace("/", ":")}"; + var rawRes = await _redisService.GetAsync(currKey); + if (!string.IsNullOrEmpty(rawRes)) + { + result = rawRes; + } + } + sw.Stop(); + Log.Info($"GetNext | {sw.Elapsed.TotalMilliseconds:N3} ms"); + return Ok(result); +#endif + } + + /// + /// Chiamata GET: num richieste in coda (tot) + /// GET: api/Prod/alive + /// + /// id oggetto + /// + [HttpGet("queue")] + public async Task QueueLen() + { + var result = await PService.QueueLen(); + return Ok(result); +#if false + Stopwatch sw = new Stopwatch(); + sw.Start(); + long numWaiting = await _redisService.QueueCountAsync(queueKey); + sw.Stop(); + Log.Info($"QueueLen | {sw.Elapsed.TotalMilliseconds:N3} ms"); + return Ok(numWaiting); +#endif + } + + #endregion Public Methods + + #region Private Fields + + private static Logger Log = LogManager.GetCurrentClassLogger(); + private ProdService PService; + + #endregion Private Fields + +#if false + [HttpPost("enqueue")] + /// + /// Accodamento richiesta di calcolo prod + /// + /// Tipo richiesta + /// UID (riga ordine) + /// Contenuto della richiesta come QuestionDTO + /// + public async Task EnqueueRequest(string reqType, string reqUid, CalcRequestDTO currRequest) + { + bool done = false; + int nId = 1; + // salvo su cache x successivo reinvio da currRequest + QuestionDTO calcRequest = new QuestionDTO(nId, currRequest.EnvType, currRequest.DictExec); + // salvo in cache contenuto della richiesta x UID + string currKey = $"{redisOrderReqKey}:{reqUid.Replace("/", ":")}"; + done = await _redisService.SetAsync(currKey, calcRequest.sProcessArgs); + // accodo la nuova richiesta + //RedisKey queueKey = (RedisKey)$"{redisBaseKey}:OrderQueue:{reqType}"; + _redisService.QueuePush(queueKey, (RedisValue)reqUid); + // dizionario richieste: è il serializzato dell'elenco degli UID da calcolare... + var currList = await _redisService.QueueListAllAsync(queueKey); + Dictionary calcDict = new Dictionary(); + calcDict.Add("ReqLen", $"{calcDict.Count}"); + string listReq = JsonConvert.SerializeObject(currList); + calcDict.Add("ReqList", listReq); + // preparo richiesta di calcolo x UID da inviare + QuestionDTO chRequest = new QuestionDTO(nId, currRequest.EnvType, calcDict); + // invio sul channel redis della richiesta di processing + await _redisService.PublishAsync(chPub, chRequest.sProcessArgs); + // ritorno + return done; + } + + /// + /// Chiamata GET: dizionario stato richieste + /// GET: api/Prod/alive + /// + /// id oggetto + /// + [HttpGet("queue-status")] + public async Task QueueStatus() + { + Stopwatch sw = new Stopwatch(); + sw.Start(); + List? listEstim = new List(); + List? listOptim = new List(); + Dictionary queueStatus = new Dictionary(); + // cerco in redis... + RedisValue rawEstim = await _redisDb.StringGetAsync($"{redisBaseKey}:EstimReq"); + if (rawEstim.HasValue) + { + listEstim = JsonConvert.DeserializeObject>($"{rawEstim}"); + } + RedisValue rawOptim = await _redisDb.StringGetAsync($"{redisBaseKey}:OptimReq"); + if (rawOptim.HasValue) + { + listOptim = JsonConvert.DeserializeObject>($"{rawOptim}"); + } + // simulo status... + queueStatus.Add("estimation", listEstim?.Count ?? 0); + queueStatus.Add("optimization", listOptim?.Count ?? 0); + sw.Stop(); + Log.Info($"QueueStatus | {sw.Elapsed.TotalMilliseconds:N3} ms"); + return Ok(queueStatus); + } /// /// Chiamata GET: /// - elenco delle richieste di stima da eseguire @@ -72,16 +218,34 @@ namespace Lux.API.Controllers Log.Info($"EstimationRequestQueue | {sw.Elapsed.TotalMilliseconds:N3} ms"); return Ok(listReq); } + /// + /// Chiamata GET: + /// - elenco delle richieste di ottimizzazione/nesting da eseguire + /// - vengono registrate come "passate" al calcolo alla data-ora della richiesta + /// GET: api/Prod/estimation + /// + /// + [HttpGet("optmization")] + public async Task>> OptimitionRequestQueue() + { + Stopwatch sw = new Stopwatch(); + sw.Start(); + var listReq = new List(); + // vado a recuperare da REDIS elenco degli ordini NON ancora associati ad 1/+ prod - #endregion Public Methods + // opzione 1: restituisco TUTTI ordini NON ancora eseguiti + // opzione 2: restituisco dall'inizio solo max(n) non ancora eseguiti? (es primi 5 ordini) - #region Private Fields + // genero elenco degli ordini e per ogni ordine aggiungo il Dict + await Task.Delay(100); - private static Logger Log = LogManager.GetCurrentClassLogger(); - private readonly IRedisService _redisService; - private readonly string chPub = ""; - private IConfiguration _config; + // opzione 1: per tutti gli ordini ritornato registro data-ora invio e tolgo dalla coda... + // opzione 2: aspetto conferma dal sistema che li ha presi in carico e registro data-ora... - #endregion Private Fields + sw.Stop(); + Log.Info($"EstimationRequestQueue | {sw.Elapsed.TotalMilliseconds:N3} ms"); + return Ok(listReq); + } +#endif } } \ No newline at end of file diff --git a/Lux.API/Lux.API.csproj b/Lux.API/Lux.API.csproj index 53ad85d4..350e07af 100644 --- a/Lux.API/Lux.API.csproj +++ b/Lux.API/Lux.API.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.9.2511.1911 + 0.9.2511.1917 diff --git a/Lux.API/Program.cs b/Lux.API/Program.cs index 5e3c5290..d8e7cebf 100644 --- a/Lux.API/Program.cs +++ b/Lux.API/Program.cs @@ -52,7 +52,7 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddSingleton(); - +builder.Services.AddSingleton(); var app = builder.Build(); diff --git a/Lux.API/appsettings.json b/Lux.API/appsettings.json index f9edf26a..75d305d9 100644 --- a/Lux.API/appsettings.json +++ b/Lux.API/appsettings.json @@ -58,7 +58,6 @@ "CalcTag": "calc", "ChannelPub": "EgwEngineInput", "ChannelSub": "EgwEngineOutput", - "ChannelPng": "luxdev:png:img", "ChannelSvg": "luxdev:svg:img", "ChannelShape": "luxdev:shape:curr", @@ -67,15 +66,6 @@ "ChannelProfList": "luxdev:prof:list", "ChannelBom": "luxdev:bom", "ChannelUpdate": "luxdev:update", - - //"ChannelPng": "Egw:png:img", - //"ChannelSvg": "Egw:svg:img", - //"ChannelShape": "Egw:shape:curr", - //"ChannelHwList": "Egw:hw:list", - //"ChannelHwOpt": "Egw:hw:opt", - //"ChannelProfList": "Egw:prof:list", - //"ChannelBom": "Egw:bom", - //"ChannelUpdate": "Egw:update", "BaseUrl": "/lux/srv/", "ImageBaseUrl": "https://iis01.egalware.com/lux/srv/api/window/", "ImageCalcTag": "svg-preview", diff --git a/Lux.UI/Lux.UI.csproj b/Lux.UI/Lux.UI.csproj index c7e532c6..17ace105 100644 --- a/Lux.UI/Lux.UI.csproj +++ b/Lux.UI/Lux.UI.csproj @@ -5,7 +5,7 @@ enable enable aspnet-Lux.UI-a758c101-a2f4-4e38-977d-1c4887dbbd50 - 0.9.2511.1911 + 0.9.2511.1917 diff --git a/Resources/ChangeLog.html b/Resources/ChangeLog.html index 09a1b4b4..fe0c7540 100644 --- a/Resources/ChangeLog.html +++ b/Resources/ChangeLog.html @@ -1,6 +1,6 @@ LUX - Web Windows MES -

Versione: 0.9.2511.1911

+

Versione: 0.9.2511.1917


Note di rilascio:
  • diff --git a/Resources/VersNum.txt b/Resources/VersNum.txt index 52f08b82..d1e67481 100644 --- a/Resources/VersNum.txt +++ b/Resources/VersNum.txt @@ -1 +1 @@ -0.9.2511.1911 +0.9.2511.1917 diff --git a/Resources/manifest.xml b/Resources/manifest.xml index 16987c7d..24d9ef63 100644 --- a/Resources/manifest.xml +++ b/Resources/manifest.xml @@ -1,6 +1,6 @@ - 0.9.2511.1911 + 0.9.2511.1917 http://nexus.steamware.net/repository/SWS/GPW/stable/GPW.UI.zip http://nexus.steamware.net/repository/SWS/GPW/stable/ChangeLog.html false