Surface historian plugin and alarm-tracking health in the status dashboard so operators can detect misconfiguration and runtime degradation that previously showed as fully healthy
Wraps the 4 HistoryRead overrides and OnAlarmAcknowledge with PerformanceMetrics.BeginOperation, adds alarm counters to LmxNodeManager, publishes a structured HistorianPluginOutcome from HistorianPluginLoader, and extends HealthCheckService with plugin-load, history-read, and alarm-ack-failure degradation rules. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using ZB.MOM.WW.LmxOpcUa.Host.Configuration;
|
||||
using ZB.MOM.WW.LmxOpcUa.Host.Domain;
|
||||
using ZB.MOM.WW.LmxOpcUa.Host.GalaxyRepository;
|
||||
using ZB.MOM.WW.LmxOpcUa.Host.Historian;
|
||||
using ZB.MOM.WW.LmxOpcUa.Host.Metrics;
|
||||
using ZB.MOM.WW.LmxOpcUa.Host.OpcUa;
|
||||
|
||||
@@ -22,6 +24,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
|
||||
private GalaxyRepositoryStats? _galaxyStats;
|
||||
private PerformanceMetrics? _metrics;
|
||||
|
||||
private HistorianConfiguration? _historianConfig;
|
||||
private IMxAccessClient? _mxAccessClient;
|
||||
private LmxNodeManager? _nodeManager;
|
||||
private RedundancyConfiguration? _redundancyConfig;
|
||||
@@ -53,7 +56,8 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
|
||||
public void SetComponents(IMxAccessClient? mxAccessClient, PerformanceMetrics? metrics,
|
||||
GalaxyRepositoryStats? galaxyStats, OpcUaServerHost? serverHost,
|
||||
LmxNodeManager? nodeManager = null,
|
||||
RedundancyConfiguration? redundancyConfig = null, string? applicationUri = null)
|
||||
RedundancyConfiguration? redundancyConfig = null, string? applicationUri = null,
|
||||
HistorianConfiguration? historianConfig = null)
|
||||
{
|
||||
_mxAccessClient = mxAccessClient;
|
||||
_metrics = metrics;
|
||||
@@ -62,6 +66,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
|
||||
_nodeManager = nodeManager;
|
||||
_redundancyConfig = redundancyConfig;
|
||||
_applicationUri = applicationUri;
|
||||
_historianConfig = historianConfig;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -71,6 +76,8 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
|
||||
public StatusData GetStatusData()
|
||||
{
|
||||
var connectionState = _mxAccessClient?.State ?? ConnectionState.Disconnected;
|
||||
var historianInfo = BuildHistorianStatusInfo();
|
||||
var alarmInfo = BuildAlarmStatusInfo();
|
||||
|
||||
return new StatusData
|
||||
{
|
||||
@@ -80,7 +87,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
|
||||
ReconnectCount = _mxAccessClient?.ReconnectCount ?? 0,
|
||||
ActiveSessions = _serverHost?.ActiveSessionCount ?? 0
|
||||
},
|
||||
Health = _healthCheck.CheckHealth(connectionState, _metrics),
|
||||
Health = _healthCheck.CheckHealth(connectionState, _metrics, historianInfo, alarmInfo),
|
||||
Subscriptions = new SubscriptionInfo
|
||||
{
|
||||
ActiveCount = _mxAccessClient?.ActiveSubscriptionCount ?? 0
|
||||
@@ -102,6 +109,8 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
|
||||
TotalEvents = _nodeManager?.TotalMxChangeEvents ?? 0
|
||||
},
|
||||
Operations = _metrics?.GetStatistics() ?? new Dictionary<string, MetricsStatistics>(),
|
||||
Historian = historianInfo,
|
||||
Alarms = alarmInfo,
|
||||
Redundancy = BuildRedundancyInfo(),
|
||||
Footer = new FooterInfo
|
||||
{
|
||||
@@ -111,6 +120,33 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
|
||||
};
|
||||
}
|
||||
|
||||
private HistorianStatusInfo BuildHistorianStatusInfo()
|
||||
{
|
||||
var outcome = HistorianPluginLoader.LastOutcome;
|
||||
return new HistorianStatusInfo
|
||||
{
|
||||
Enabled = _historianConfig?.Enabled ?? false,
|
||||
PluginStatus = outcome.Status.ToString(),
|
||||
PluginError = outcome.Error,
|
||||
PluginPath = outcome.PluginPath,
|
||||
ServerName = _historianConfig?.ServerName ?? "",
|
||||
Port = _historianConfig?.Port ?? 0
|
||||
};
|
||||
}
|
||||
|
||||
private AlarmStatusInfo BuildAlarmStatusInfo()
|
||||
{
|
||||
return new AlarmStatusInfo
|
||||
{
|
||||
TrackingEnabled = _nodeManager?.AlarmTrackingEnabled ?? false,
|
||||
ConditionCount = _nodeManager?.AlarmConditionCount ?? 0,
|
||||
ActiveAlarmCount = _nodeManager?.ActiveAlarmCount ?? 0,
|
||||
TransitionCount = _nodeManager?.AlarmTransitionCount ?? 0,
|
||||
AckEventCount = _nodeManager?.AlarmAckEventCount ?? 0,
|
||||
AckWriteFailures = _nodeManager?.AlarmAckWriteFailures ?? 0
|
||||
};
|
||||
}
|
||||
|
||||
private RedundancyInfo? BuildRedundancyInfo()
|
||||
{
|
||||
if (_redundancyConfig == null || !_redundancyConfig.Enabled)
|
||||
@@ -204,6 +240,26 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
|
||||
sb.AppendLine($"<p>Last Rebuild: {data.Galaxy.LastRebuildTime:O}</p>");
|
||||
sb.AppendLine("</div>");
|
||||
|
||||
// Historian panel
|
||||
var histColor = data.Historian.PluginStatus == "Loaded" ? "green"
|
||||
: !data.Historian.Enabled ? "gray" : "red";
|
||||
sb.AppendLine($"<div class='panel {histColor}'><h2>Historian</h2>");
|
||||
sb.AppendLine(
|
||||
$"<p>Enabled: <b>{data.Historian.Enabled}</b> | Plugin: <b>{data.Historian.PluginStatus}</b> | Server: {WebUtility.HtmlEncode(data.Historian.ServerName)}:{data.Historian.Port}</p>");
|
||||
if (!string.IsNullOrEmpty(data.Historian.PluginError))
|
||||
sb.AppendLine($"<p>Error: {WebUtility.HtmlEncode(data.Historian.PluginError)}</p>");
|
||||
sb.AppendLine("</div>");
|
||||
|
||||
// Alarms panel
|
||||
var alarmPanelColor = !data.Alarms.TrackingEnabled ? "gray"
|
||||
: data.Alarms.AckWriteFailures > 0 ? "yellow" : "green";
|
||||
sb.AppendLine($"<div class='panel {alarmPanelColor}'><h2>Alarms</h2>");
|
||||
sb.AppendLine(
|
||||
$"<p>Tracking: <b>{data.Alarms.TrackingEnabled}</b> | Conditions: {data.Alarms.ConditionCount} | Active: <b>{data.Alarms.ActiveAlarmCount}</b></p>");
|
||||
sb.AppendLine(
|
||||
$"<p>Transitions: {data.Alarms.TransitionCount:N0} | Ack Events: {data.Alarms.AckEventCount:N0} | Ack Write Failures: {data.Alarms.AckWriteFailures}</p>");
|
||||
sb.AppendLine("</div>");
|
||||
|
||||
// Operations table
|
||||
sb.AppendLine("<div class='panel gray'><h2>Operations</h2>");
|
||||
sb.AppendLine(
|
||||
@@ -254,7 +310,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
|
||||
var connectionState = _mxAccessClient?.State ?? ConnectionState.Disconnected;
|
||||
var mxConnected = connectionState == ConnectionState.Connected;
|
||||
var dbConnected = _galaxyStats?.DbConnected ?? false;
|
||||
var health = _healthCheck.CheckHealth(connectionState, _metrics);
|
||||
var historianInfo = BuildHistorianStatusInfo();
|
||||
var alarmInfo = BuildAlarmStatusInfo();
|
||||
var health = _healthCheck.CheckHealth(connectionState, _metrics, historianInfo, alarmInfo);
|
||||
var uptime = DateTime.UtcNow - _startTime;
|
||||
|
||||
var data = new HealthEndpointData
|
||||
@@ -265,7 +323,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
|
||||
{
|
||||
MxAccess = connectionState.ToString(),
|
||||
Database = dbConnected ? "Connected" : "Disconnected",
|
||||
OpcUaServer = _serverHost?.IsRunning ?? false ? "Running" : "Stopped"
|
||||
OpcUaServer = _serverHost?.IsRunning ?? false ? "Running" : "Stopped",
|
||||
Historian = historianInfo.PluginStatus,
|
||||
Alarms = alarmInfo.TrackingEnabled ? "Enabled" : "Disabled"
|
||||
},
|
||||
Uptime = FormatUptime(uptime),
|
||||
Timestamp = DateTime.UtcNow
|
||||
@@ -354,6 +414,10 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
|
||||
sb.AppendLine(
|
||||
$"<div class='redundancy'>Role: <b>{data.RedundancyRole}</b> | Mode: <b>{data.RedundancyMode}</b></div>");
|
||||
|
||||
var historianColor = data.Components.Historian == "Loaded" ? "#00cc66"
|
||||
: data.Components.Historian == "Disabled" ? "#666" : "#cc3333";
|
||||
var alarmColor = data.Components.Alarms == "Enabled" ? "#00cc66" : "#666";
|
||||
|
||||
// Component health cards
|
||||
sb.AppendLine("<div class='components'>");
|
||||
sb.AppendLine(
|
||||
@@ -362,6 +426,10 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
|
||||
$"<div class='component' style='border-color: {dbColor};'><div class='name'>Galaxy Database</div><div class='value' style='color: {dbColor};'>{data.Components.Database}</div></div>");
|
||||
sb.AppendLine(
|
||||
$"<div class='component' style='border-color: {uaColor};'><div class='name'>OPC UA Server</div><div class='value' style='color: {uaColor};'>{data.Components.OpcUaServer}</div></div>");
|
||||
sb.AppendLine(
|
||||
$"<div class='component' style='border-color: {historianColor};'><div class='name'>Historian</div><div class='value' style='color: {historianColor};'>{data.Components.Historian}</div></div>");
|
||||
sb.AppendLine(
|
||||
$"<div class='component' style='border-color: {alarmColor};'><div class='name'>Alarm Tracking</div><div class='value' style='color: {alarmColor};'>{data.Components.Alarms}</div></div>");
|
||||
sb.AppendLine("</div>");
|
||||
|
||||
// Footer
|
||||
|
||||
Reference in New Issue
Block a user