添加协议层数据统计结构,日志文件保存在 Logs 下

This commit is contained in:
2026-03-27 17:39:25 +08:00
parent ca26ab8e38
commit e361510100
13 changed files with 866 additions and 12 deletions
@@ -1,4 +1,4 @@
using System;
using System;
using System.Net;
using System.Runtime.InteropServices;
using kcp;
@@ -61,6 +61,7 @@ namespace Network.NetworkTransport
var result = KCP.ikcp_send(_kcp, buffer, payload.Length);
if (result < 0)
{
_owner.RecordTransportError("kcp-send", RemoteEndPoint, $"KCP send failed with error code {result}.");
throw new InvalidOperationException($"KCP send failed with error code {result}.");
}
}
@@ -91,6 +92,7 @@ namespace Network.NetworkTransport
var result = KCP.ikcp_input(_kcp, buffer, datagram.Length);
if (result < 0)
{
_owner.RecordTransportError("kcp-input", RemoteEndPoint, $"KCP input failed with error code {result}.");
Console.WriteLine($"[KcpTransport] KCP input failed for {RemoteEndPoint}: {result}");
return;
}
@@ -125,6 +127,7 @@ namespace Network.NetworkTransport
var result = KCP.ikcp_recv(_kcp, buffer, payload.Length);
if (result < 0)
{
_owner.RecordTransportError("kcp-recv", RemoteEndPoint, $"KCP recv failed with error code {result}.");
payload = null;
return false;
}
@@ -1,5 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
@@ -25,6 +26,7 @@ namespace Network.NetworkTransport
private readonly bool _isServer;
private readonly IPEndPoint _defaultRemoteEndPoint;
private readonly uint _defaultConv;
private readonly ITransportMetricsModule _metricsModule;
private readonly ConcurrentDictionary<string, KcpSession> _sessions = new();
private readonly object _socketSendLock = new();
@@ -37,16 +39,17 @@ namespace Network.NetworkTransport
internal int ActiveSessionCount => _sessions.Count;
public KcpTransport(int listenPort, uint conv = DefaultConv)
public KcpTransport(int listenPort, uint conv = DefaultConv, ITransportMetricsModule metricsModule = null)
{
_client = new UdpClient(listenPort);
_isServer = true;
_defaultConv = conv;
_metricsModule = metricsModule ?? new DefaultTransportMetricsModule();
Console.WriteLine($"[KcpTransport] 服务端模式,监听端口: {listenPort}");
}
public KcpTransport(string serverIp, int serverPort, uint conv = DefaultConv)
public KcpTransport(string serverIp, int serverPort, uint conv = DefaultConv, ITransportMetricsModule metricsModule = null)
{
if (string.IsNullOrWhiteSpace(serverIp))
{
@@ -56,10 +59,16 @@ namespace Network.NetworkTransport
_client = new UdpClient(0);
_defaultRemoteEndPoint = new IPEndPoint(IPAddress.Parse(serverIp), serverPort);
_defaultConv = conv;
_metricsModule = metricsModule ?? new DefaultTransportMetricsModule();
Console.WriteLine($"[KcpTransport] 客户端模式,目标: {_defaultRemoteEndPoint}, conv={conv}");
}
public TransportMetricsSnapshot GetMetricsSnapshot()
{
return _metricsModule.GetCurrentSnapshot();
}
public Task StartAsync()
{
if (_isRunning)
@@ -68,6 +77,7 @@ namespace Network.NetworkTransport
}
_sessions.Clear();
_metricsModule.BeginRun(new TransportRunDescriptor(nameof(KcpTransport), _isServer, _defaultRemoteEndPoint));
_cancellationTokenSource = new CancellationTokenSource();
_isRunning = true;
@@ -97,6 +107,7 @@ namespace Network.NetworkTransport
DisposeAllSessions();
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
_metricsModule.CompleteRun();
Console.WriteLine("[KcpTransport] 传输层停止");
}
@@ -133,6 +144,7 @@ namespace Network.NetworkTransport
var session = GetOrCreateSession(target, _defaultConv);
session.Send(data);
_metricsModule.RecordPayloadSent(session.RemoteEndPoint, data.Length);
}
public void SendToAll(byte[] data)
@@ -152,6 +164,7 @@ namespace Network.NetworkTransport
foreach (var session in _sessions.Values)
{
session.Send(data);
_metricsModule.RecordPayloadSent(session.RemoteEndPoint, data.Length);
}
}
@@ -162,6 +175,7 @@ namespace Network.NetworkTransport
try
{
var result = await _client.ReceiveAsync();
_metricsModule.RecordDatagramReceived(result.RemoteEndPoint, result.Buffer.Length);
var session = GetOrCreateSession(result.RemoteEndPoint, ResolveConv(result.Buffer));
session.Input(result.Buffer);
DrainReceivedMessages(session);
@@ -176,6 +190,7 @@ namespace Network.NetworkTransport
}
catch (Exception exception)
{
_metricsModule.RecordError("socket-receive", null, exception.Message);
Console.WriteLine($"[KcpTransport] 接收错误:{exception.Message}");
}
}
@@ -208,6 +223,7 @@ namespace Network.NetworkTransport
{
while (session.TryReceive(out var payload))
{
_metricsModule.RecordPayloadReceived(session.RemoteEndPoint, payload.Length);
OnReceive?.Invoke(payload, session.RemoteEndPoint);
}
}
@@ -217,7 +233,20 @@ namespace Network.NetworkTransport
var normalizedEndPoint = NormalizeEndPoint(remoteEndPoint);
var key = normalizedEndPoint.ToString();
return _sessions.GetOrAdd(key, _ => new KcpSession(this, normalizedEndPoint, conv));
if (_sessions.TryGetValue(key, out var existing))
{
return existing;
}
var created = new KcpSession(this, normalizedEndPoint, conv);
if (_sessions.TryAdd(key, created))
{
_metricsModule.RecordSessionOpened(created.RemoteEndPoint);
return created;
}
created.Dispose();
return _sessions[key];
}
private IPEndPoint NormalizeEndPoint(IPEndPoint remoteEndPoint)
@@ -276,6 +305,7 @@ namespace Network.NetworkTransport
{
foreach (var innerException in exception.InnerExceptions)
{
_metricsModule.RecordError("stop-wait", null, innerException.Message);
Console.WriteLine($"[KcpTransport] 停止等待错误:{innerException.Message}");
}
}
@@ -283,12 +313,14 @@ namespace Network.NetworkTransport
private void DisposeAllSessions()
{
foreach (var pair in _sessions)
{
pair.Value.Dispose();
}
var sessions = _sessions.Values.ToArray();
_sessions.Clear();
foreach (var session in sessions)
{
session.Dispose();
_metricsModule.RecordSessionClosed(session.RemoteEndPoint);
}
}
private unsafe int SendDatagram(byte* buffer, int length, IPEndPoint remoteEndPoint)
@@ -305,7 +337,9 @@ namespace Network.NetworkTransport
{
lock (_socketSendLock)
{
return _client.Send(datagram, datagram.Length, remoteEndPoint);
var bytes = _client.Send(datagram, datagram.Length, remoteEndPoint);
_metricsModule.RecordDatagramSent(remoteEndPoint, bytes);
return bytes;
}
}
catch (ObjectDisposedException) when (!_isRunning)
@@ -314,11 +348,15 @@ namespace Network.NetworkTransport
}
catch (SocketException exception)
{
_metricsModule.RecordError("socket-send", remoteEndPoint, exception.Message);
Console.WriteLine($"[KcpTransport] 发送错误:{exception.Message}");
return -1;
}
}
private void RecordTransportError(string stage, IPEndPoint remoteEndPoint, string detail)
{
_metricsModule.RecordError(stage, remoteEndPoint, detail);
}
}
}
@@ -0,0 +1,512 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
namespace Network.NetworkTransport
{
public interface ITransportMetricsModule
{
void BeginRun(TransportRunDescriptor descriptor);
void RecordSessionOpened(IPEndPoint remoteEndPoint);
void RecordSessionClosed(IPEndPoint remoteEndPoint);
void RecordPayloadSent(IPEndPoint remoteEndPoint, int bytes);
void RecordPayloadReceived(IPEndPoint remoteEndPoint, int bytes);
void RecordDatagramSent(IPEndPoint remoteEndPoint, int bytes);
void RecordDatagramReceived(IPEndPoint remoteEndPoint, int bytes);
void RecordError(string stage, IPEndPoint remoteEndPoint, string detail = null);
TransportMetricsSnapshot GetCurrentSnapshot();
TransportMetricsSnapshot CompleteRun();
}
public sealed class TransportRunDescriptor
{
public TransportRunDescriptor(string transportName, bool isServer, IPEndPoint defaultRemoteEndPoint = null)
{
if (string.IsNullOrWhiteSpace(transportName))
{
throw new ArgumentException("Transport name is required.", nameof(transportName));
}
TransportName = transportName;
IsServer = isServer;
DefaultRemoteEndPoint = defaultRemoteEndPoint == null
? null
: new IPEndPoint(defaultRemoteEndPoint.Address, defaultRemoteEndPoint.Port);
}
public string TransportName { get; }
public bool IsServer { get; }
public IPEndPoint DefaultRemoteEndPoint { get; }
}
[DataContract]
public sealed class TransportMetricsSnapshot
{
[DataMember(Order = 1)] public string RunId { get; set; }
[DataMember(Order = 2)] public string TransportName { get; set; }
[DataMember(Order = 3)] public string Mode { get; set; }
[DataMember(Order = 4)] public string DefaultRemoteEndPoint { get; set; }
[DataMember(Order = 5)] public DateTimeOffset? StartedAtUtc { get; set; }
[DataMember(Order = 6)] public DateTimeOffset? CompletedAtUtc { get; set; }
[DataMember(Order = 7)] public long DurationMs { get; set; }
[DataMember(Order = 8)] public string ReportPath { get; set; }
[DataMember(Order = 9)] public int ActiveSessions { get; set; }
[DataMember(Order = 10)] public int PeakActiveSessions { get; set; }
[DataMember(Order = 11)] public long SessionsCreated { get; set; }
[DataMember(Order = 12)] public long SessionsClosed { get; set; }
[DataMember(Order = 13)] public long PayloadMessagesSent { get; set; }
[DataMember(Order = 14)] public long PayloadBytesSent { get; set; }
[DataMember(Order = 15)] public long PayloadMessagesReceived { get; set; }
[DataMember(Order = 16)] public long PayloadBytesReceived { get; set; }
[DataMember(Order = 17)] public long DatagramsSent { get; set; }
[DataMember(Order = 18)] public long DatagramBytesSent { get; set; }
[DataMember(Order = 19)] public long DatagramsReceived { get; set; }
[DataMember(Order = 20)] public long DatagramBytesReceived { get; set; }
[DataMember(Order = 21)] public long SendErrors { get; set; }
[DataMember(Order = 22)] public long ReceiveErrors { get; set; }
[DataMember(Order = 23)] public long OtherErrors { get; set; }
[DataMember(Order = 24)] public Dictionary<string, long> ErrorCountsByStage { get; set; } = new(StringComparer.Ordinal);
[DataMember(Order = 25)] public List<TransportPeerMetricsSnapshot> PeerSummaries { get; set; } = new();
}
[DataContract]
public sealed class TransportPeerMetricsSnapshot
{
[DataMember(Order = 1)] public string RemoteEndPoint { get; set; }
[DataMember(Order = 2)] public DateTimeOffset? FirstSeenUtc { get; set; }
[DataMember(Order = 3)] public DateTimeOffset? LastActivityUtc { get; set; }
[DataMember(Order = 4)] public long SessionOpens { get; set; }
[DataMember(Order = 5)] public long SessionCloses { get; set; }
[DataMember(Order = 6)] public long PayloadMessagesSent { get; set; }
[DataMember(Order = 7)] public long PayloadBytesSent { get; set; }
[DataMember(Order = 8)] public long PayloadMessagesReceived { get; set; }
[DataMember(Order = 9)] public long PayloadBytesReceived { get; set; }
[DataMember(Order = 10)] public long DatagramsSent { get; set; }
[DataMember(Order = 11)] public long DatagramBytesSent { get; set; }
[DataMember(Order = 12)] public long DatagramsReceived { get; set; }
[DataMember(Order = 13)] public long DatagramBytesReceived { get; set; }
[DataMember(Order = 14)] public Dictionary<string, long> ErrorCountsByStage { get; set; } = new(StringComparer.Ordinal);
}
public sealed class DefaultTransportMetricsModule : ITransportMetricsModule
{
private readonly object gate = new();
private readonly Func<DateTimeOffset> utcNowProvider;
private readonly TextWriter consoleWriter;
private readonly string reportDirectory;
private readonly Dictionary<string, PeerAccumulator> peers = new(StringComparer.Ordinal);
private readonly Dictionary<string, long> errorCountsByStage = new(StringComparer.Ordinal);
private string runId;
private string transportName;
private string mode;
private string defaultRemoteEndPoint;
private DateTimeOffset? startedAtUtc;
private DateTimeOffset? completedAtUtc;
private string reportPath;
private bool hasRun;
private bool completed;
private long sessionsCreated;
private long sessionsClosed;
private int activeSessions;
private int peakActiveSessions;
private long payloadMessagesSent;
private long payloadBytesSent;
private long payloadMessagesReceived;
private long payloadBytesReceived;
private long datagramsSent;
private long datagramBytesSent;
private long datagramsReceived;
private long datagramBytesReceived;
private long sendErrors;
private long receiveErrors;
private long otherErrors;
private TransportMetricsSnapshot completedSnapshot;
public DefaultTransportMetricsModule(string reportDirectory = null, Func<DateTimeOffset> utcNowProvider = null, TextWriter consoleWriter = null)
{
this.reportDirectory = string.IsNullOrWhiteSpace(reportDirectory)
? Path.Combine(Directory.GetCurrentDirectory(), "Logs", "transport-metrics")
: reportDirectory;
this.utcNowProvider = utcNowProvider ?? (() => DateTimeOffset.UtcNow);
this.consoleWriter = consoleWriter ?? Console.Out;
}
public void BeginRun(TransportRunDescriptor descriptor)
{
if (descriptor == null)
{
throw new ArgumentNullException(nameof(descriptor));
}
lock (gate)
{
peers.Clear();
errorCountsByStage.Clear();
runId = Guid.NewGuid().ToString("N");
transportName = descriptor.TransportName;
mode = descriptor.IsServer ? "server" : "client";
defaultRemoteEndPoint = FormatEndPoint(descriptor.DefaultRemoteEndPoint);
startedAtUtc = utcNowProvider();
completedAtUtc = null;
reportPath = null;
hasRun = true;
completed = false;
sessionsCreated = 0;
sessionsClosed = 0;
activeSessions = 0;
peakActiveSessions = 0;
payloadMessagesSent = 0;
payloadBytesSent = 0;
payloadMessagesReceived = 0;
payloadBytesReceived = 0;
datagramsSent = 0;
datagramBytesSent = 0;
datagramsReceived = 0;
datagramBytesReceived = 0;
sendErrors = 0;
receiveErrors = 0;
otherErrors = 0;
completedSnapshot = null;
}
}
public void RecordSessionOpened(IPEndPoint remoteEndPoint) => Update(remoteEndPoint, peer =>
{
sessionsCreated++;
activeSessions++;
peakActiveSessions = Math.Max(peakActiveSessions, activeSessions);
peer.SessionOpens++;
});
public void RecordSessionClosed(IPEndPoint remoteEndPoint) => Update(remoteEndPoint, peer =>
{
sessionsClosed++;
activeSessions = Math.Max(0, activeSessions - 1);
peer.SessionCloses++;
});
public void RecordPayloadSent(IPEndPoint remoteEndPoint, int bytes) => Update(remoteEndPoint, peer =>
{
payloadMessagesSent++;
payloadBytesSent += bytes;
peer.PayloadMessagesSent++;
peer.PayloadBytesSent += bytes;
});
public void RecordPayloadReceived(IPEndPoint remoteEndPoint, int bytes) => Update(remoteEndPoint, peer =>
{
payloadMessagesReceived++;
payloadBytesReceived += bytes;
peer.PayloadMessagesReceived++;
peer.PayloadBytesReceived += bytes;
});
public void RecordDatagramSent(IPEndPoint remoteEndPoint, int bytes) => Update(remoteEndPoint, peer =>
{
datagramsSent++;
datagramBytesSent += bytes;
peer.DatagramsSent++;
peer.DatagramBytesSent += bytes;
});
public void RecordDatagramReceived(IPEndPoint remoteEndPoint, int bytes) => Update(remoteEndPoint, peer =>
{
datagramsReceived++;
datagramBytesReceived += bytes;
peer.DatagramsReceived++;
peer.DatagramBytesReceived += bytes;
});
public void RecordError(string stage, IPEndPoint remoteEndPoint, string detail = null)
{
lock (gate)
{
if (!hasRun)
{
return;
}
Increment(errorCountsByStage, stage);
if (remoteEndPoint != null)
{
var peer = GetOrCreatePeer(remoteEndPoint);
Increment(peer.ErrorCountsByStage, stage);
peer.LastActivityUtc = utcNowProvider();
}
if (!string.IsNullOrWhiteSpace(stage) && stage.IndexOf("send", StringComparison.OrdinalIgnoreCase) >= 0)
{
sendErrors++;
}
else if (!string.IsNullOrWhiteSpace(stage) && (stage.IndexOf("receive", StringComparison.OrdinalIgnoreCase) >= 0 || stage.IndexOf("recv", StringComparison.OrdinalIgnoreCase) >= 0 || stage.IndexOf("input", StringComparison.OrdinalIgnoreCase) >= 0))
{
receiveErrors++;
}
else
{
otherErrors++;
}
if (!string.IsNullOrWhiteSpace(detail))
{
consoleWriter.WriteLine($"[TransportMetrics] {stage}: {detail}");
}
}
}
public TransportMetricsSnapshot GetCurrentSnapshot()
{
lock (gate)
{
return completed && completedSnapshot != null ? completedSnapshot : BuildSnapshot();
}
}
public TransportMetricsSnapshot CompleteRun()
{
lock (gate)
{
if (!hasRun)
{
return completedSnapshot ?? new TransportMetricsSnapshot();
}
if (completed && completedSnapshot != null)
{
return completedSnapshot;
}
completedAtUtc = utcNowProvider();
completed = true;
completedSnapshot = BuildSnapshot();
completedSnapshot.ReportPath = WriteJsonReport(completedSnapshot);
reportPath = completedSnapshot.ReportPath;
consoleWriter.WriteLine($"[TransportMetrics] {completedSnapshot.TransportName} mode={completedSnapshot.Mode} run={completedSnapshot.RunId} durationMs={completedSnapshot.DurationMs} peak={completedSnapshot.PeakActiveSessions} payloadTx={completedSnapshot.PayloadMessagesSent}/{completedSnapshot.PayloadBytesSent}B payloadRx={completedSnapshot.PayloadMessagesReceived}/{completedSnapshot.PayloadBytesReceived}B datagramTx={completedSnapshot.DatagramsSent}/{completedSnapshot.DatagramBytesSent}B datagramRx={completedSnapshot.DatagramsReceived}/{completedSnapshot.DatagramBytesReceived}B errors={completedSnapshot.SendErrors + completedSnapshot.ReceiveErrors + completedSnapshot.OtherErrors} report={completedSnapshot.ReportPath ?? "none"}");
return completedSnapshot;
}
}
private void Update(IPEndPoint remoteEndPoint, Action<PeerAccumulator> update)
{
lock (gate)
{
if (!hasRun)
{
return;
}
var peer = GetOrCreatePeer(remoteEndPoint);
update(peer);
peer.LastActivityUtc = utcNowProvider();
}
}
private TransportMetricsSnapshot BuildSnapshot()
{
var end = completedAtUtc ?? utcNowProvider();
return new TransportMetricsSnapshot
{
RunId = runId,
TransportName = transportName,
Mode = mode,
DefaultRemoteEndPoint = defaultRemoteEndPoint,
StartedAtUtc = startedAtUtc,
CompletedAtUtc = completedAtUtc,
DurationMs = startedAtUtc.HasValue ? Math.Max(0L, (long)(end - startedAtUtc.Value).TotalMilliseconds) : 0L,
ReportPath = reportPath,
ActiveSessions = activeSessions,
PeakActiveSessions = peakActiveSessions,
SessionsCreated = sessionsCreated,
SessionsClosed = sessionsClosed,
PayloadMessagesSent = payloadMessagesSent,
PayloadBytesSent = payloadBytesSent,
PayloadMessagesReceived = payloadMessagesReceived,
PayloadBytesReceived = payloadBytesReceived,
DatagramsSent = datagramsSent,
DatagramBytesSent = datagramBytesSent,
DatagramsReceived = datagramsReceived,
DatagramBytesReceived = datagramBytesReceived,
SendErrors = sendErrors,
ReceiveErrors = receiveErrors,
OtherErrors = otherErrors,
ErrorCountsByStage = errorCountsByStage.ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.Ordinal),
PeerSummaries = peers.Values.Select(peer => peer.ToSnapshot()).OrderBy(peer => peer.RemoteEndPoint, StringComparer.Ordinal).ToList()
};
}
private string WriteJsonReport(TransportMetricsSnapshot snapshot)
{
try
{
Directory.CreateDirectory(reportDirectory);
var timestamp = (snapshot.CompletedAtUtc ?? utcNowProvider()).ToString("yyyyMMdd-HHmmssfff");
var filePath = Path.Combine(reportDirectory, $"{snapshot.Mode}-{snapshot.TransportName}-{timestamp}-{snapshot.RunId}.json");
using var stream = new MemoryStream();
var serializer = new DataContractJsonSerializer(typeof(TransportMetricsSnapshot));
serializer.WriteObject(stream, snapshot);
var json = Encoding.UTF8.GetString(stream.ToArray());
File.WriteAllText(filePath, FormatJson(json), new UTF8Encoding(false));
return filePath;
}
catch (Exception exception)
{
consoleWriter.WriteLine($"[TransportMetrics] Failed to write report: {exception.Message}");
return null;
}
}
private PeerAccumulator GetOrCreatePeer(IPEndPoint remoteEndPoint)
{
var key = FormatEndPoint(remoteEndPoint) ?? "unknown";
if (!peers.TryGetValue(key, out var peer))
{
peer = new PeerAccumulator(key, utcNowProvider());
peers.Add(key, peer);
}
return peer;
}
private static string FormatEndPoint(IPEndPoint remoteEndPoint)
{
return remoteEndPoint == null ? null : new IPEndPoint(remoteEndPoint.Address, remoteEndPoint.Port).ToString();
}
private static void Increment(IDictionary<string, long> counts, string stage)
{
var key = string.IsNullOrWhiteSpace(stage) ? "unknown" : stage;
counts.TryGetValue(key, out var current);
counts[key] = current + 1;
}
private static string FormatJson(string json)
{
if (string.IsNullOrWhiteSpace(json))
{
return json;
}
var builder = new StringBuilder(json.Length + 128);
var indentation = 0;
var inString = false;
var isEscaped = false;
foreach (var character in json)
{
if (isEscaped)
{
builder.Append(character);
isEscaped = false;
continue;
}
if (character == '\\' && inString)
{
builder.Append(character);
isEscaped = true;
continue;
}
if (character == '"')
{
inString = !inString;
builder.Append(character);
continue;
}
if (inString)
{
builder.Append(character);
continue;
}
switch (character)
{
case '{':
case '[':
builder.Append(character);
builder.AppendLine();
indentation++;
builder.Append(new string(' ', indentation * 2));
break;
case '}':
case ']':
builder.AppendLine();
indentation = Math.Max(0, indentation - 1);
builder.Append(new string(' ', indentation * 2));
builder.Append(character);
break;
case ',':
builder.Append(character);
builder.AppendLine();
builder.Append(new string(' ', indentation * 2));
break;
case ':':
builder.Append(": ");
break;
default:
if (!char.IsWhiteSpace(character))
{
builder.Append(character);
}
break;
}
}
return builder.ToString();
}
private sealed class PeerAccumulator
{
public PeerAccumulator(string remoteEndPoint, DateTimeOffset now)
{
RemoteEndPoint = remoteEndPoint;
FirstSeenUtc = now;
LastActivityUtc = now;
}
public string RemoteEndPoint { get; }
public DateTimeOffset FirstSeenUtc { get; }
public DateTimeOffset LastActivityUtc { get; set; }
public long SessionOpens { get; set; }
public long SessionCloses { get; set; }
public long PayloadMessagesSent { get; set; }
public long PayloadBytesSent { get; set; }
public long PayloadMessagesReceived { get; set; }
public long PayloadBytesReceived { get; set; }
public long DatagramsSent { get; set; }
public long DatagramBytesSent { get; set; }
public long DatagramsReceived { get; set; }
public long DatagramBytesReceived { get; set; }
public Dictionary<string, long> ErrorCountsByStage { get; } = new(StringComparer.Ordinal);
public TransportPeerMetricsSnapshot ToSnapshot()
{
return new TransportPeerMetricsSnapshot
{
RemoteEndPoint = RemoteEndPoint,
FirstSeenUtc = FirstSeenUtc,
LastActivityUtc = LastActivityUtc,
SessionOpens = SessionOpens,
SessionCloses = SessionCloses,
PayloadMessagesSent = PayloadMessagesSent,
PayloadBytesSent = PayloadBytesSent,
PayloadMessagesReceived = PayloadMessagesReceived,
PayloadBytesReceived = PayloadBytesReceived,
DatagramsSent = DatagramsSent,
DatagramBytesSent = DatagramBytesSent,
DatagramsReceived = DatagramsReceived,
DatagramBytesReceived = DatagramBytesReceived,
ErrorCountsByStage = ErrorCountsByStage.ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.Ordinal)
};
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d3ae5cc796484e9698f058e34de23a3f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: