This commit is contained in:
2026-09-03 04:31:37 +03:00
parent 61ec566f63
commit 69a50d8b20
4 changed files with 281 additions and 0 deletions
+10
View File
@@ -57,6 +57,16 @@ public class AsyncQueue<T> : AsyncReply<T>
//Action<T> callback; //Action<T> callback;
object queueLock = new object(); object queueLock = new object();
/// <summary>Current queued item count, exposed for opt-in runtime diagnostics.</summary>
public int Count
{
get
{
lock (queueLock)
return list.Count;
}
}
//public AsyncQueue<T> Then(Action<T> callback) //public AsyncQueue<T> Then(Action<T> callback)
//{ //{
// base.Then(new Action<object>(o => callback((T)o))); // base.Then(new Action<object>(o => callback((T)o)));
+34
View File
@@ -321,6 +321,12 @@ public partial class EpConnection : NetworkConnection, IStore
return; return;
Global.Counters["Ep Sent Packets"]++; Global.Counters["Ep Sent Packets"]++;
var metrics = _runtimeMetrics;
if (metrics != null)
{
Interlocked.Add(ref metrics.SentBytes, data.LongLength);
Interlocked.Increment(ref metrics.SentPackets);
}
if (_outboundProtectionState == OutboundProtectionState.Encrypted) if (_outboundProtectionState == OutboundProtectionState.Encrypted)
base.Send(ComposeEncryptedRecord(data)); base.Send(ComposeEncryptedRecord(data));
@@ -1343,6 +1349,30 @@ public partial class EpConnection : NetworkConnection, IStore
{ {
offset += (uint)rt; offset += (uint)rt;
var metrics = _runtimeMetrics;
if (metrics != null)
{
Interlocked.Increment(ref metrics.ReceivedPackets);
if (_packet.Method == EpPacketMethod.Request)
Interlocked.Increment(ref metrics.ReceivedRequests);
else if (_packet.Method == EpPacketMethod.Reply)
{
Interlocked.Increment(ref metrics.ReceivedReplies);
if (_packet.Reply == EpPacketReply.PermissionError ||
_packet.Reply == EpPacketReply.ExecutionError ||
_packet.Reply == EpPacketReply.Warning)
Interlocked.Increment(ref metrics.ReceivedErrors);
}
else if (_packet.Method == EpPacketMethod.Notification)
{
Interlocked.Increment(ref metrics.ReceivedNotifications);
if (_packet.Notification == EpPacketNotification.PropertyModified)
Interlocked.Increment(ref metrics.ReceivedPropertyModifications);
else if (_packet.Notification == EpPacketNotification.EventOccurred)
Interlocked.Increment(ref metrics.ReceivedEvents);
}
}
if (_packet.Tdu == null && if (_packet.Tdu == null &&
_packet.Method != EpPacketMethod.Reply && _packet.Method != EpPacketMethod.Reply &&
_packet.Method != EpPacketMethod.Extension) _packet.Method != EpPacketMethod.Extension)
@@ -3022,6 +3052,10 @@ public partial class EpConnection : NetworkConnection, IStore
if (msg == null) if (msg == null)
return; return;
var metrics = _runtimeMetrics;
if (metrics != null)
Interlocked.Add(ref metrics.ReceivedBytes, msg.LongLength);
this.Socket.Hold(); this.Socket.Hold();
try try
@@ -0,0 +1,209 @@
using System;
using System.Threading;
namespace Esiur.Protocol;
/// <summary>
/// Opt-in diagnostics for one EP connection. Esiur allocates no collector and
/// performs no metric increments until <see cref="EpConnection.EnableRuntimeMetrics"/>
/// is called.
/// </summary>
public sealed class EpConnectionRuntimeMetricsSnapshot
{
public bool Enabled { get; set; }
public DateTime? StartedAt { get; set; }
public DateTime SampledAt { get; set; }
public double MonitoringDurationMilliseconds { get; set; }
public bool Connected { get; set; }
public bool Authenticated { get; set; }
public bool Encrypted { get; set; }
public long SentBytes { get; set; }
public long ReceivedBytes { get; set; }
public long SentPackets { get; set; }
public long ReceivedPackets { get; set; }
public long SentRequests { get; set; }
public long ReceivedRequests { get; set; }
public long SentReplies { get; set; }
public long ReceivedReplies { get; set; }
public long SentNotifications { get; set; }
public long ReceivedNotifications { get; set; }
public long SentPropertyModifications { get; set; }
public long ReceivedPropertyModifications { get; set; }
public long SentEvents { get; set; }
public long ReceivedEvents { get; set; }
public long SentErrors { get; set; }
public long ReceivedErrors { get; set; }
public double BytesPerSecond { get; set; }
public double PacketsPerSecond { get; set; }
public double NotificationsPerSecond { get; set; }
public double PropertyModificationsPerSecond { get; set; }
public double EventsPerSecond { get; set; }
public int AttachedResources { get; set; }
public int LocalResourceSubscriptions { get; set; }
public int EventSubscriptions { get; set; }
public int PendingRequests { get; set; }
public int PendingResourceAttachments { get; set; }
public int PendingTypeDefinitions { get; set; }
public int CachedTypeDefinitions { get; set; }
public int NeededResources { get; set; }
public int NeededTypeDefinitions { get; set; }
public int ActiveInvocations { get; set; }
public int QueuedNotificationWork { get; set; }
public uint RemoteMaximumPacketSize { get; set; }
public uint RemoteMaximumAllocationSize { get; set; }
public int RemoteMaximumCollectionItems { get; set; }
public int RemoteMaximumTypeMetadataDepth { get; set; }
public uint RemoteMaximumEncryptedRecordSize { get; set; }
}
sealed class EpConnectionMetricsCollector
{
internal readonly DateTime StartedAt = DateTime.UtcNow;
internal long SentBytes, ReceivedBytes, SentPackets, ReceivedPackets;
internal long SentRequests, ReceivedRequests, SentReplies, ReceivedReplies;
internal long SentNotifications, ReceivedNotifications;
internal long SentPropertyModifications, ReceivedPropertyModifications;
internal long SentEvents, ReceivedEvents, SentErrors, ReceivedErrors;
readonly object sampleLock = new();
DateTime previousSampledAt;
long previousBytes, previousPackets, previousNotifications, previousProperties, previousEvents;
internal EpConnectionMetricsCollector() => previousSampledAt = StartedAt;
internal EpConnectionRuntimeMetricsSnapshot Snapshot(EpConnectionRuntimeMetricsSnapshot state)
{
lock (sampleLock)
{
var sampledAt = DateTime.UtcNow;
var seconds = Math.Max((sampledAt - previousSampledAt).TotalSeconds, 0.001);
var sentBytes = Interlocked.Read(ref SentBytes);
var receivedBytes = Interlocked.Read(ref ReceivedBytes);
var sentPackets = Interlocked.Read(ref SentPackets);
var receivedPackets = Interlocked.Read(ref ReceivedPackets);
var sentNotifications = Interlocked.Read(ref SentNotifications);
var receivedNotifications = Interlocked.Read(ref ReceivedNotifications);
var sentProperties = Interlocked.Read(ref SentPropertyModifications);
var receivedProperties = Interlocked.Read(ref ReceivedPropertyModifications);
var sentEvents = Interlocked.Read(ref SentEvents);
var receivedEvents = Interlocked.Read(ref ReceivedEvents);
var totalBytes = sentBytes + receivedBytes;
var totalPackets = sentPackets + receivedPackets;
var totalNotifications = sentNotifications + receivedNotifications;
var totalProperties = sentProperties + receivedProperties;
var totalEvents = sentEvents + receivedEvents;
var snapshot = new EpConnectionRuntimeMetricsSnapshot
{
Enabled = true,
StartedAt = StartedAt,
SampledAt = sampledAt,
MonitoringDurationMilliseconds = (sampledAt - StartedAt).TotalMilliseconds,
Connected = state.Connected,
Authenticated = state.Authenticated,
Encrypted = state.Encrypted,
SentBytes = sentBytes,
ReceivedBytes = receivedBytes,
SentPackets = sentPackets,
ReceivedPackets = receivedPackets,
SentRequests = Interlocked.Read(ref SentRequests),
ReceivedRequests = Interlocked.Read(ref ReceivedRequests),
SentReplies = Interlocked.Read(ref SentReplies),
ReceivedReplies = Interlocked.Read(ref ReceivedReplies),
SentNotifications = sentNotifications,
ReceivedNotifications = receivedNotifications,
SentPropertyModifications = sentProperties,
ReceivedPropertyModifications = receivedProperties,
SentEvents = sentEvents,
ReceivedEvents = receivedEvents,
SentErrors = Interlocked.Read(ref SentErrors),
ReceivedErrors = Interlocked.Read(ref ReceivedErrors),
BytesPerSecond = (totalBytes - previousBytes) / seconds,
PacketsPerSecond = (totalPackets - previousPackets) / seconds,
NotificationsPerSecond = (totalNotifications - previousNotifications) / seconds,
PropertyModificationsPerSecond = (totalProperties - previousProperties) / seconds,
EventsPerSecond = (totalEvents - previousEvents) / seconds,
AttachedResources = state.AttachedResources,
LocalResourceSubscriptions = state.LocalResourceSubscriptions,
EventSubscriptions = state.EventSubscriptions,
PendingRequests = state.PendingRequests,
PendingResourceAttachments = state.PendingResourceAttachments,
PendingTypeDefinitions = state.PendingTypeDefinitions,
CachedTypeDefinitions = state.CachedTypeDefinitions,
NeededResources = state.NeededResources,
NeededTypeDefinitions = state.NeededTypeDefinitions,
ActiveInvocations = state.ActiveInvocations,
QueuedNotificationWork = state.QueuedNotificationWork,
RemoteMaximumPacketSize = state.RemoteMaximumPacketSize,
RemoteMaximumAllocationSize = state.RemoteMaximumAllocationSize,
RemoteMaximumCollectionItems = state.RemoteMaximumCollectionItems,
RemoteMaximumTypeMetadataDepth = state.RemoteMaximumTypeMetadataDepth,
RemoteMaximumEncryptedRecordSize = state.RemoteMaximumEncryptedRecordSize,
};
previousSampledAt = sampledAt;
previousBytes = totalBytes;
previousPackets = totalPackets;
previousNotifications = totalNotifications;
previousProperties = totalProperties;
previousEvents = totalEvents;
return snapshot;
}
}
}
public partial class EpConnection
{
EpConnectionMetricsCollector _runtimeMetrics;
/// <summary>Starts a fresh per-connection diagnostics window.</summary>
public EpConnectionRuntimeMetricsSnapshot EnableRuntimeMetrics()
{
Volatile.Write(ref _runtimeMetrics, new EpConnectionMetricsCollector());
return GetRuntimeMetrics();
}
/// <summary>Stops diagnostics and releases the counter store.</summary>
public void DisableRuntimeMetrics() => Volatile.Write(ref _runtimeMetrics, null);
/// <summary>Reads totals, current rates, queues, attachments, and negotiated limits.</summary>
public EpConnectionRuntimeMetricsSnapshot GetRuntimeMetrics()
{
int subscriptions;
int eventSubscriptions;
lock (_subscriptionsLock)
{
subscriptions = _subscriptions.Count;
eventSubscriptions = 0;
foreach (var indexes in _subscriptions.Values)
eventSubscriptions += indexes.Count;
}
int invocations;
lock (_invocationsLock)
invocations = _invocations.Count;
var state = new EpConnectionRuntimeMetricsSnapshot
{
SampledAt = DateTime.UtcNow,
Connected = IsConnected,
Authenticated = _authenticated,
Encrypted = IsEncrypted,
AttachedResources = _attachedResources.Count,
LocalResourceSubscriptions = subscriptions,
EventSubscriptions = eventSubscriptions,
PendingRequests = _requests.Count,
PendingResourceAttachments = _resourceRequests.Count,
PendingTypeDefinitions = _typeDefRequests.Count,
CachedTypeDefinitions = _cachedTypeDefs.Count,
NeededResources = _neededResources.Count,
NeededTypeDefinitions = _neededTypeDefs.Count,
ActiveInvocations = invocations,
QueuedNotificationWork = _queue.Count,
RemoteMaximumPacketSize = _session?.RemoteMaximumPacketSize ?? 0,
RemoteMaximumAllocationSize = _session?.RemoteMaximumAllocationSize ?? 0,
RemoteMaximumCollectionItems = _session?.RemoteMaximumCollectionItems ?? 0,
RemoteMaximumTypeMetadataDepth = _session?.RemoteMaximumTypeMetadataDepth ?? 0,
RemoteMaximumEncryptedRecordSize = _session?.RemoteMaximumEncryptedRecordSize ?? 0,
};
var collector = Volatile.Read(ref _runtimeMetrics);
return collector?.Snapshot(state) ?? state;
}
}
@@ -328,6 +328,9 @@ partial class EpConnection
var c = (uint)Interlocked.Increment(ref _callbackCounter); var c = (uint)Interlocked.Increment(ref _callbackCounter);
//callbackCounter++; // avoid thread racing //callbackCounter++; // avoid thread racing
_requests.Add(c, reply); _requests.Add(c, reply);
var metrics = _runtimeMetrics;
if (metrics != null)
Interlocked.Increment(ref metrics.SentRequests);
try try
{ {
@@ -383,6 +386,9 @@ partial class EpConnection
() => SendRequest(EpPacketRequest.ResumeExecution, callbackId)); () => SendRequest(EpPacketRequest.ResumeExecution, callbackId));
_requests.Add(callbackId, reply); _requests.Add(callbackId, reply);
var metrics = _runtimeMetrics;
if (metrics != null)
Interlocked.Increment(ref metrics.SentRequests);
try try
{ {
SendRequestPacket(action, callbackId, args); SendRequestPacket(action, callbackId, args);
@@ -409,6 +415,9 @@ partial class EpConnection
() => SendRequest(EpPacketRequest.ResumeExecution, callbackId)); () => SendRequest(EpPacketRequest.ResumeExecution, callbackId));
_requests.Add(callbackId, reply); _requests.Add(callbackId, reply);
var metrics = _runtimeMetrics;
if (metrics != null)
Interlocked.Increment(ref metrics.SentRequests);
try try
{ {
SendRequestPacket(action, callbackId, args); SendRequestPacket(action, callbackId, args);
@@ -507,6 +516,15 @@ partial class EpConnection
{ {
var reply = new AsyncReply(); var reply = new AsyncReply();
var metrics = _runtimeMetrics;
if (metrics != null)
{
Interlocked.Increment(ref metrics.SentNotifications);
if (action == EpPacketNotification.PropertyModified)
Interlocked.Increment(ref metrics.SentPropertyModifications);
else if (action == EpPacketNotification.EventOccurred)
Interlocked.Increment(ref metrics.SentEvents);
}
if (args.Length == 0) if (args.Length == 0)
{ {
@@ -535,6 +553,16 @@ partial class EpConnection
if (Instance == null) if (Instance == null)
return; return;
var metrics = _runtimeMetrics;
if (metrics != null)
{
Interlocked.Increment(ref metrics.SentReplies);
if (action == EpPacketReply.PermissionError ||
action == EpPacketReply.ExecutionError ||
action == EpPacketReply.Warning)
Interlocked.Increment(ref metrics.SentErrors);
}
try try
{ {
if (args.Length == 0) if (args.Length == 0)