Skip to content

Commit

Permalink
Add metric exporter for System.Diagnostics.Metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
PJB3005 committed Mar 22, 2024
1 parent 045f90d commit 82d82fb
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 1 deletion.
6 changes: 5 additions & 1 deletion Lidgren.Network/NetPeer.Internal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -43,6 +43,8 @@ public partial class NetPeer
private AutoResetEvent? m_messageReceivedEvent;
private List<NetTuple<SynchronizationContext, SendOrPostCallback>>? m_receiveCallbacks;

internal Action? m_onShutdown;

/// <summary>
/// Gets the socket, if Start() has been called
/// </summary>
Expand Down Expand Up @@ -303,6 +305,8 @@ private void ExecutePeerShutdown()
m_connections.Clear();
m_connectionLookup.Clear();
m_handshakes.Clear();

m_onShutdown?.Invoke();
}

return;
Expand Down
157 changes: 157 additions & 0 deletions Lidgren.Network/NetPeerMetrics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#if NET8_0_OR_GREATER

using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;

namespace Lidgren.Network;

/// <summary>
/// Implements an exporter for exporting statistics about the <see cref="NetPeer"/> into
/// <c>System.Diagnostics.Metrics</c>.
/// </summary>
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";

/// <summary>
/// Start exporting metrics for a <see cref="NetPeer"/> to <c>System.Diagnostics.Metrics</c>.
/// </summary>
/// <remarks>
/// <para>
/// This function must be called after the <see cref="NetPeer"/> has been started.
/// </para>
/// <para>
/// Exported metrics can be viewed via tools such as <see cref="MeterListener"/> or <c>dotnet-counters</c>.
/// </para>
/// <para>
/// If exporting metrics from multiple peers,
/// you should use <paramref name="tags"/> to allow them to be distinguished.
/// </para>
/// </remarks>
/// <param name="netPeer">The <see cref="NetPeer"/> to start exporting metrics for.</param>
/// <param name="flags">Flags describing what metric features to enable.</param>
/// <param name="tags">Optional tags to add to the created <see cref="Meter"/>.</param>
/// <param name="factory">
/// An optional <see cref="IMeterFactory"/> to be used to create the <see cref="Meter"/>.
/// </param>
/// <exception cref="InvalidOperationException">Thrown if the <see cref="NetPeer"/> is not running.</exception>
public static void AddMetrics(
this NetPeer netPeer,
Flags flags = Flags.Statistics,
IEnumerable<KeyValuePair<string, object?>>? 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<Measurement<long>>
{
new(statistics.ResentMessagesDueToHole,
tags: [new KeyValuePair<string, object?>("drop_reason", "hole")]),
new(statistics.ResentMessagesDueToDelay,
tags: [new KeyValuePair<string, object?>("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();
}
}

/// <summary>
/// Flag enum describing what metric features to enable.
/// </summary>
[Flags]
public enum Flags
{
/// <summary>
/// No metrics are enabled.
/// </summary>
None = 0,

/// <summary>
/// Basic statistics from <see cref="NetPeerStatistics"/> are reported.
/// </summary>
Statistics = 1 << 0,
}
}

#endif
1 change: 1 addition & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 82d82fb

Please sign in to comment.