feat(opcuaclient): subscription tuning, OperationLimits chunking, diagnostics counters, revocation options, DiscoveryUrl (#273 #275 #276 #277 #278)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient;
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe counters for the OPC UA Client driver (#276), surfaced through
|
||||
/// <c>DriverHealth.Diagnostics</c>. Writers are the SDK's notification / keep-alive threads and
|
||||
/// the request paths, so every field is touched only through <see cref="Interlocked"/>.
|
||||
/// </summary>
|
||||
internal sealed class OpcUaClientDiagnostics
|
||||
{
|
||||
private long _publishResponses;
|
||||
private long _notifications;
|
||||
private long _dataChanges;
|
||||
private long _badStatusValues;
|
||||
private long _publishErrors;
|
||||
private long _keepAliveMisses;
|
||||
private long _reconnectAttempts;
|
||||
private long _reconnectsSucceeded;
|
||||
private long _reconnectsExhausted;
|
||||
private long _lastReconnectUtcTicks;
|
||||
private long _readCalls;
|
||||
private long _readFailures;
|
||||
private long _writeCalls;
|
||||
private long _writeFailures;
|
||||
private long _chunkedRequests;
|
||||
|
||||
/// <summary>A NotificationMessage arrived (one per Publish response that carried data).</summary>
|
||||
public void RecordPublishResponse() => Interlocked.Increment(ref _publishResponses);
|
||||
|
||||
/// <summary>A Publish failed (missing publish requests, subscription errors).</summary>
|
||||
public void RecordPublishError() => Interlocked.Increment(ref _publishErrors);
|
||||
|
||||
/// <summary>One monitored-item data change was delivered.</summary>
|
||||
/// <param name="bad">Whether the value's status code was Bad.</param>
|
||||
public void RecordDataChange(bool bad)
|
||||
{
|
||||
Interlocked.Increment(ref _dataChanges);
|
||||
if (bad) Interlocked.Increment(ref _badStatusValues);
|
||||
}
|
||||
|
||||
/// <summary>Count raw notification items carried by a message.</summary>
|
||||
/// <param name="count">Number of items in the message.</param>
|
||||
public void RecordNotifications(int count) => Interlocked.Add(ref _notifications, count);
|
||||
|
||||
/// <summary>A keep-alive came back Bad.</summary>
|
||||
public void RecordKeepAliveMiss() => Interlocked.Increment(ref _keepAliveMisses);
|
||||
|
||||
/// <summary>The reconnect handler was armed.</summary>
|
||||
public void RecordReconnectAttempt() => Interlocked.Increment(ref _reconnectAttempts);
|
||||
|
||||
/// <summary>A reconnect landed a new session.</summary>
|
||||
public void RecordReconnectSucceeded()
|
||||
{
|
||||
Interlocked.Increment(ref _reconnectsSucceeded);
|
||||
Interlocked.Exchange(ref _lastReconnectUtcTicks, DateTime.UtcNow.Ticks);
|
||||
}
|
||||
|
||||
/// <summary>A reconnect handler exhausted its retry window.</summary>
|
||||
public void RecordReconnectExhausted() => Interlocked.Increment(ref _reconnectsExhausted);
|
||||
|
||||
/// <summary>A Read request (all chunks) finished.</summary>
|
||||
/// <param name="failed">Whether any chunk failed on the wire.</param>
|
||||
public void RecordRead(bool failed)
|
||||
{
|
||||
Interlocked.Increment(ref _readCalls);
|
||||
if (failed) Interlocked.Increment(ref _readFailures);
|
||||
}
|
||||
|
||||
/// <summary>A Write request (all chunks) finished.</summary>
|
||||
/// <param name="failed">Whether any chunk failed on the wire.</param>
|
||||
public void RecordWrite(bool failed)
|
||||
{
|
||||
Interlocked.Increment(ref _writeCalls);
|
||||
if (failed) Interlocked.Increment(ref _writeFailures);
|
||||
}
|
||||
|
||||
/// <summary>A logical request was split into more than one wire call to honour a server OperationLimit.</summary>
|
||||
public void RecordChunkedRequest() => Interlocked.Increment(ref _chunkedRequests);
|
||||
|
||||
/// <summary>
|
||||
/// Point-in-time copy for <c>DriverHealth.Diagnostics</c>. Keys are stable and
|
||||
/// <c>PascalCase</c>; <c>LastReconnectUtcTicks</c> is 0 until a reconnect succeeds.
|
||||
/// </summary>
|
||||
/// <returns>A fresh dictionary the caller may keep.</returns>
|
||||
public IReadOnlyDictionary<string, double> Snapshot() => new Dictionary<string, double>
|
||||
{
|
||||
["PublishResponseCount"] = Interlocked.Read(ref _publishResponses),
|
||||
["NotificationCount"] = Interlocked.Read(ref _notifications),
|
||||
["DataChangeCount"] = Interlocked.Read(ref _dataChanges),
|
||||
["BadStatusValueCount"] = Interlocked.Read(ref _badStatusValues),
|
||||
["PublishErrorCount"] = Interlocked.Read(ref _publishErrors),
|
||||
["KeepAliveMissCount"] = Interlocked.Read(ref _keepAliveMisses),
|
||||
["ReconnectAttemptCount"] = Interlocked.Read(ref _reconnectAttempts),
|
||||
["ReconnectSuccessCount"] = Interlocked.Read(ref _reconnectsSucceeded),
|
||||
["ReconnectExhaustedCount"] = Interlocked.Read(ref _reconnectsExhausted),
|
||||
["LastReconnectUtcTicks"] = Interlocked.Read(ref _lastReconnectUtcTicks),
|
||||
["ReadCallCount"] = Interlocked.Read(ref _readCalls),
|
||||
["ReadFailureCount"] = Interlocked.Read(ref _readFailures),
|
||||
["WriteCallCount"] = Interlocked.Read(ref _writeCalls),
|
||||
["WriteFailureCount"] = Interlocked.Read(ref _writeFailures),
|
||||
["ChunkedRequestCount"] = Interlocked.Read(ref _chunkedRequests),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user