diff --git a/Lidgren.Network/NetPeer.Internal.cs b/Lidgren.Network/NetPeer.Internal.cs index 5876a38..e939b2c 100644 --- a/Lidgren.Network/NetPeer.Internal.cs +++ b/Lidgren.Network/NetPeer.Internal.cs @@ -23,7 +23,7 @@ public partial class NetPeer internal readonly byte[] m_receiveBuffer; internal readonly NetIncomingMessage m_readHelperMessage; private EndPoint m_senderRemote; - private readonly object m_initializeLock = new object(); + internal readonly object m_initializeLock = new object(); private uint m_frameCounter; private double m_lastHeartbeat; private double m_lastSocketBind = float.MinValue; @@ -43,6 +43,8 @@ public partial class NetPeer private AutoResetEvent? m_messageReceivedEvent; private List>? m_receiveCallbacks; + internal Action? m_onShutdown; + /// /// Gets the socket, if Start() has been called /// @@ -303,6 +305,8 @@ private void ExecutePeerShutdown() m_connections.Clear(); m_connectionLookup.Clear(); m_handshakes.Clear(); + + m_onShutdown?.Invoke(); } return; diff --git a/Lidgren.Network/NetPeerMetrics.cs b/Lidgren.Network/NetPeerMetrics.cs new file mode 100644 index 0000000..7df91ef --- /dev/null +++ b/Lidgren.Network/NetPeerMetrics.cs @@ -0,0 +1,157 @@ +#if NET8_0_OR_GREATER + +using System; +using System.Collections.Generic; +using System.Diagnostics.Metrics; + +namespace Lidgren.Network; + +/// +/// Implements an exporter for exporting statistics about the into +/// System.Diagnostics.Metrics. +/// +public static class NetPeerMetrics +{ + // I originally had this in NetPeerConfiguration, + // but moved it here to a standalone class so that trimming solutions avoid pulling in the + // metrics stack if you don't use it. + + private const string MeterName = "Lidgren.Network.NetPeer"; + + /// + /// Start exporting metrics for a to System.Diagnostics.Metrics. + /// + /// + /// + /// This function must be called after the has been started. + /// + /// + /// Exported metrics can be viewed via tools such as or dotnet-counters. + /// + /// + /// If exporting metrics from multiple peers, + /// you should use to allow them to be distinguished. + /// + /// + /// The to start exporting metrics for. + /// Flags describing what metric features to enable. + /// Optional tags to add to the created . + /// + /// An optional to be used to create the . + /// + /// Thrown if the is not running. + public static void AddMetrics( + this NetPeer netPeer, + Flags flags = Flags.Statistics, + IEnumerable>? tags = null, + IMeterFactory? factory = null) + { + lock (netPeer.m_initializeLock) + { + if (netPeer.Status != NetPeerStatus.Running) + throw new InvalidOperationException("Peer must be running."); + + Meter meter; + if (factory != null) + { + meter = factory.Create(MeterName, tags: tags); + } + else + { + meter = new Meter(MeterName, null, tags: tags); + } + + if ((flags & Flags.Statistics) != 0) + { + var statistics = netPeer.Statistics; + + meter.CreateObservableCounter( + "packets_sent", + () => statistics.SentPackets, + null, + "Number of packets sent by this NetPeer."); + + meter.CreateObservableCounter( + "packets_received", + () => statistics.ReceivedPackets, + null, + "Number of packets received by this NetPeer."); + + meter.CreateObservableCounter( + "messages_sent", + () => statistics.SentMessages, + null, + "Number of messages sent by this NetPeer."); + + meter.CreateObservableCounter( + "messages_received", + () => statistics.ReceivedMessages, + null, + "Number of messages received by this NetPeer."); + + meter.CreateObservableCounter( + "sent_bytes", + () => statistics.SentBytes, + "bytes", + "Number of bytes sent by this NetPeer."); + + meter.CreateObservableCounter( + "sent_bytes", + () => statistics.ReceivedBytes, + "bytes", + "Number of bytes received by this NetPeer."); + + meter.CreateObservableCounter( + "storage_allocated_bytes", + () => statistics.StorageBytesAllocated, + "bytes", + "Number of bytes allocated (and possibly garbage collected) for message storage."); + + meter.CreateObservableUpDownCounter( + "recycle_pool_bytes", + () => statistics.BytesInRecyclePool, + "bytes", + "Number of bytes in the recycled pool."); + + meter.CreateObservableCounter( + "messages_resent", + () => new List> + { + new(statistics.ResentMessagesDueToHole, + tags: [new KeyValuePair("drop_reason", "hole")]), + new(statistics.ResentMessagesDueToDelay, + tags: [new KeyValuePair("drop_reason", "delay")]), + }, + null, + "Number of messages that had to be resent."); + + meter.CreateObservableCounter( + "messages_dropped", + () => statistics.DroppedMessages, + null, + "Number of messages that were dropped"); + } + + netPeer.m_onShutdown += () => meter.Dispose(); + } + } + + /// + /// Flag enum describing what metric features to enable. + /// + [Flags] + public enum Flags + { + /// + /// No metrics are enabled. + /// + None = 0, + + /// + /// Basic statistics from are reported. + /// + Statistics = 1 << 0, + } +} + +#endif diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index d7b5e49..46d573d 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -11,6 +11,7 @@ - A separate MTU value is now used for IPv6, as it permits a higher default. You can set it with `NetPeerConfiguration.MaximumTransmissionUnitV6` - Fixed MTU expansion not setting internal state correctly and spamming network packets. - Fixed `NetPeerConfiguration.ExpandMTUFailAttempts` not being respected completely. +- The new `NetPeerMetrics` allows exporting metrics about the library to `System.Diagnostics.Metrics`. ## 0.2.7