feat(lmxproxy): phase 4 — host health monitoring, metrics, status web server

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-03-22 00:14:40 -04:00
parent 16d1b95e9a
commit 9eb81180c0
12 changed files with 1546 additions and 12 deletions

View File

@@ -3,8 +3,10 @@ using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Grpc.Core;
using GrpcStatus = Grpc.Core.Status;
using Serilog;
using ZB.MOM.WW.LmxProxy.Host.Domain;
using ZB.MOM.WW.LmxProxy.Host.Metrics;
using ZB.MOM.WW.LmxProxy.Host.Sessions;
using ZB.MOM.WW.LmxProxy.Host.Subscriptions;
@@ -21,15 +23,18 @@ namespace ZB.MOM.WW.LmxProxy.Host.Grpc.Services
private readonly IScadaClient _scadaClient;
private readonly SessionManager _sessionManager;
private readonly SubscriptionManager _subscriptionManager;
private readonly PerformanceMetrics? _performanceMetrics;
public ScadaGrpcService(
IScadaClient scadaClient,
SessionManager sessionManager,
SubscriptionManager subscriptionManager)
SubscriptionManager subscriptionManager,
PerformanceMetrics? performanceMetrics = null)
{
_scadaClient = scadaClient;
_sessionManager = sessionManager;
_subscriptionManager = subscriptionManager;
_performanceMetrics = performanceMetrics;
}
// -- Connection Management ------------------------------------
@@ -121,6 +126,7 @@ namespace ZB.MOM.WW.LmxProxy.Host.Grpc.Services
};
}
using var timing = _performanceMetrics?.BeginOperation("Read");
try
{
var vtq = await _scadaClient.ReadAsync(request.Tag, context.CancellationToken);
@@ -133,6 +139,7 @@ namespace ZB.MOM.WW.LmxProxy.Host.Grpc.Services
}
catch (Exception ex)
{
timing?.SetSuccess(false);
Log.Error(ex, "Read failed for tag {Tag}", request.Tag);
return new Scada.ReadResponse
{
@@ -155,6 +162,7 @@ namespace ZB.MOM.WW.LmxProxy.Host.Grpc.Services
};
}
using var timing = _performanceMetrics?.BeginOperation("ReadBatch");
try
{
var results = await _scadaClient.ReadBatchAsync(request.Tags, context.CancellationToken);
@@ -182,6 +190,7 @@ namespace ZB.MOM.WW.LmxProxy.Host.Grpc.Services
}
catch (Exception ex)
{
timing?.SetSuccess(false);
Log.Error(ex, "ReadBatch failed");
return new Scada.ReadBatchResponse
{
@@ -201,6 +210,7 @@ namespace ZB.MOM.WW.LmxProxy.Host.Grpc.Services
return new Scada.WriteResponse { Success = false, Message = "Invalid session" };
}
using var timing = _performanceMetrics?.BeginOperation("Write");
try
{
var value = TypedValueConverter.FromTypedValue(request.Value);
@@ -209,6 +219,7 @@ namespace ZB.MOM.WW.LmxProxy.Host.Grpc.Services
}
catch (Exception ex)
{
timing?.SetSuccess(false);
Log.Error(ex, "Write failed for tag {Tag}", request.Tag);
return new Scada.WriteResponse { Success = false, Message = ex.Message };
}
@@ -222,6 +233,7 @@ namespace ZB.MOM.WW.LmxProxy.Host.Grpc.Services
return new Scada.WriteBatchResponse { Success = false, Message = "Invalid session" };
}
using var timing = _performanceMetrics?.BeginOperation("WriteBatch");
var response = new Scada.WriteBatchResponse { Success = true, Message = "" };
foreach (var item in request.Items)
@@ -245,6 +257,11 @@ namespace ZB.MOM.WW.LmxProxy.Host.Grpc.Services
}
}
if (!response.Success)
{
timing?.SetSuccess(false);
}
return response;
}
@@ -336,7 +353,7 @@ namespace ZB.MOM.WW.LmxProxy.Host.Grpc.Services
{
if (!_sessionManager.ValidateSession(request.SessionId))
{
throw new RpcException(new Status(StatusCode.Unauthenticated, "Invalid session"));
throw new RpcException(new GrpcStatus(StatusCode.Unauthenticated, "Invalid session"));
}
var reader = _subscriptionManager.Subscribe(
@@ -360,7 +377,7 @@ namespace ZB.MOM.WW.LmxProxy.Host.Grpc.Services
catch (Exception ex)
{
Log.Error(ex, "Subscribe stream error for session {SessionId}", request.SessionId);
throw new RpcException(new Status(StatusCode.Internal, ex.Message));
throw new RpcException(new GrpcStatus(StatusCode.Internal, ex.Message));
}
finally
{