fix(r2-04): surface degraded address-space applies (Error log + otopcua.opcua.apply.failed)
This commit is contained in:
@@ -58,6 +58,14 @@ public static class OtOpcUaTelemetry
|
||||
Meter.CreateCounter<long>("otopcua.opcua.sink.write", unit: "{write}",
|
||||
description: "Writes that landed in IOpcUaAddressSpaceSink (kind=value|alarm|rebuild).");
|
||||
|
||||
/// <summary>Address-space apply/materialise passes that swallowed at least one sink failure —
|
||||
/// a failed rebuild (kind=rebuild) or per-node materialise failures (kind=nodes). A non-zero rate
|
||||
/// means the running server holds a stale or partially-materialised address space despite a
|
||||
/// reported-successful deploy (archreview 01/S-1).</summary>
|
||||
public static readonly Counter<long> OpcUaApplyFailed =
|
||||
Meter.CreateCounter<long>("otopcua.opcua.apply.failed", unit: "{apply}",
|
||||
description: "Apply/materialise passes with swallowed sink failures (kind=rebuild|nodes).");
|
||||
|
||||
public static readonly Counter<long> ServiceLevelChange =
|
||||
Meter.CreateCounter<long>("otopcua.redundancy.service_level_change", unit: "{change}",
|
||||
description: "OPC UA Server.ServiceLevel transitions emitted by the redundancy state.");
|
||||
|
||||
@@ -335,27 +335,43 @@ public sealed class OpcUaPublishActor : ReceiveActor, IWithTimers
|
||||
var outcome = _applier.Apply(plan);
|
||||
_lastApplied = composition;
|
||||
|
||||
_applier.MaterialiseHierarchy(composition);
|
||||
// Sum swallowed per-node materialise failures across every pass together with Apply's own
|
||||
// removal-pass tally (archreview 01/S-1). A degraded apply used to vanish into per-node
|
||||
// Warnings + an optimistic Info line; now it surfaces at Error + a dedicated meter.
|
||||
var failedNodes = outcome.FailedNodes;
|
||||
failedNodes += _applier.MaterialiseHierarchy(composition);
|
||||
// T14 — scripted alarms get their own pass right after the hierarchy so the equipment
|
||||
// folders they parent under already exist. Materialises real Part 9 AlarmConditionState
|
||||
// nodes (keyed by ScriptedAlarmId so AlarmStateUpdate writes target them); disabled
|
||||
// alarms are skipped.
|
||||
_applier.MaterialiseScriptedAlarms(composition);
|
||||
failedNodes += _applier.MaterialiseScriptedAlarms(composition);
|
||||
// Equipment-namespace tags get their own pass: ensures each signal's Variable (and any
|
||||
// FolderPath sub-folder) exists under its already-materialised equipment folder so
|
||||
// clients can browse them. Live values are pushed by DriverHostActor.ForwardToMux after
|
||||
// each subscription cycle; variables show BadWaitingForInitialData only until the first
|
||||
// publish interval fires.
|
||||
_applier.MaterialiseEquipmentTags(composition);
|
||||
failedNodes += _applier.MaterialiseEquipmentTags(composition);
|
||||
// Equipment-namespace VirtualTags get their own pass right after the equipment tags:
|
||||
// ensures each computed signal's Variable (and any FolderPath sub-folder) exists under its
|
||||
// equipment folder with a folder-scoped NodeId. VirtualTagHostActor.OnResult pushes live
|
||||
// values once the first dependency update arrives; until then variables show BadWaitingForInitialData.
|
||||
_applier.MaterialiseEquipmentVirtualTags(composition);
|
||||
failedNodes += _applier.MaterialiseEquipmentVirtualTags(composition);
|
||||
|
||||
OtOpcUaTelemetry.OpcUaSinkWrite.Add(1, new KeyValuePair<string, object?>("kind", "rebuild"));
|
||||
_log.Info("OpcUaPublish: applied rebuild (correlation={Correlation}, added={Added}, removed={Removed}, changed={Changed}, rebuild={Rebuild})",
|
||||
msg.Correlation, outcome.AddedNodes, outcome.RemovedNodes, outcome.ChangedNodes, outcome.RebuildCalled);
|
||||
if (outcome.RebuildFailed || failedNodes > 0)
|
||||
{
|
||||
OtOpcUaTelemetry.OpcUaApplyFailed.Add(1,
|
||||
new KeyValuePair<string, object?>("kind", outcome.RebuildFailed ? "rebuild" : "nodes"));
|
||||
_log.Error(
|
||||
"OpcUaPublish: address-space apply DEGRADED (correlation={Correlation}, rebuildFailed={RebuildFailed}, failedNodes={FailedNodes}, added={Added}, removed={Removed}, changed={Changed}) — the running address space may be stale or partial",
|
||||
msg.Correlation, outcome.RebuildFailed, failedNodes,
|
||||
outcome.AddedNodes, outcome.RemovedNodes, outcome.ChangedNodes);
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.Info("OpcUaPublish: applied rebuild (correlation={Correlation}, added={Added}, removed={Removed}, changed={Changed}, rebuild={Rebuild})",
|
||||
msg.Correlation, outcome.AddedNodes, outcome.RemovedNodes, outcome.ChangedNodes, outcome.RebuildCalled);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -412,7 +428,16 @@ public sealed class OpcUaPublishActor : ReceiveActor, IWithTimers
|
||||
_log.Debug("OpcUaPublish: no applier wired — discarding MaterialiseDiscoveredNodes for {Equipment}", msg.EquipmentRootNodeId);
|
||||
return;
|
||||
}
|
||||
_applier.MaterialiseDiscoveredNodes(msg.EquipmentRootNodeId, msg.Folders, msg.Variables);
|
||||
var failedNodes = _applier.MaterialiseDiscoveredNodes(msg.EquipmentRootNodeId, msg.Folders, msg.Variables);
|
||||
if (failedNodes > 0)
|
||||
{
|
||||
// archreview 01/S-1: a swallowed discovered-node injection failure surfaces at Error + the
|
||||
// dedicated meter instead of vanishing into per-node Warnings.
|
||||
OtOpcUaTelemetry.OpcUaApplyFailed.Add(1, new KeyValuePair<string, object?>("kind", "nodes"));
|
||||
_log.Error(
|
||||
"OpcUaPublish: discovered-node injection DEGRADED for {Equipment} (failedNodes={FailedNodes}) — some discovered nodes may be missing",
|
||||
msg.EquipmentRootNodeId, failedNodes);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleServiceLevelChanged(ServiceLevelChanged msg)
|
||||
|
||||
Reference in New Issue
Block a user