perf(metrics): cache MxEventFamily names for per-event metric tags
WorkerClient.StageWorkerEvent and StreamEvents called Enum.ToString per event (per subscriber on the stream path). MxEventFamilyNames serves the contract-fixed names from a frozen lookup, falling back to ToString for undefined values so exported tag values are unchanged.
This commit is contained in:
@@ -183,7 +183,7 @@ public sealed class MxAccessGatewayService(
|
|||||||
// call is cancelled, instead of leaving it to the transport's own timeout.
|
// call is cancelled, instead of leaving it to the transport's own timeout.
|
||||||
await responseStream.WriteAsync(publicEvent, context.CancellationToken).ConfigureAwait(false);
|
await responseStream.WriteAsync(publicEvent, context.CancellationToken).ConfigureAwait(false);
|
||||||
metrics.RecordEventStreamSend(
|
metrics.RecordEventStreamSend(
|
||||||
publicEvent.Family.ToString(),
|
MxEventFamilyNames.Get(publicEvent.Family),
|
||||||
Stopwatch.GetElapsedTime(sendStartTimestamp));
|
Stopwatch.GetElapsedTime(sendStartTimestamp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Collections.Frozen;
|
||||||
|
using ZB.MOM.WW.MxGateway.Contracts.Proto;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.MxGateway.Server.Metrics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cached <see cref="MxEventFamily"/> names for the per-event metric tags.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <see cref="Enum.ToString()"/> allocates a new string on every call, and the event-received
|
||||||
|
/// and stream-send metrics tag every event with its family — once per event at the worker
|
||||||
|
/// boundary and once per event per subscriber on the stream. The names are fixed by the
|
||||||
|
/// contract, so they are computed once here. A value the contract does not define falls back
|
||||||
|
/// to <see cref="Enum.ToString()"/>, which is the same string the uncached call produced.
|
||||||
|
/// </remarks>
|
||||||
|
public static class MxEventFamilyNames
|
||||||
|
{
|
||||||
|
private static readonly FrozenDictionary<MxEventFamily, string> Names =
|
||||||
|
Enum.GetValues<MxEventFamily>()
|
||||||
|
.Distinct()
|
||||||
|
.ToFrozenDictionary(family => family, family => family.ToString());
|
||||||
|
|
||||||
|
/// <summary>Gets the name of an event family.</summary>
|
||||||
|
/// <param name="family">The event family.</param>
|
||||||
|
/// <returns>The family's name, identical to <see cref="Enum.ToString()"/>.</returns>
|
||||||
|
public static string Get(MxEventFamily family) =>
|
||||||
|
Names.TryGetValue(family, out string? name) ? name : family.ToString();
|
||||||
|
}
|
||||||
@@ -692,7 +692,7 @@ public sealed class WorkerClient : IWorkerClient
|
|||||||
{
|
{
|
||||||
if (workerEvent.Event is not null)
|
if (workerEvent.Event is not null)
|
||||||
{
|
{
|
||||||
_metrics?.EventReceived(SessionId, workerEvent.Event.Family.ToString());
|
_metrics?.EventReceived(SessionId, MxEventFamilyNames.Get(workerEvent.Event.Family));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_eventStaging.Writer.TryWrite(workerEvent))
|
if (_eventStaging.Writer.TryWrite(workerEvent))
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using ZB.MOM.WW.MxGateway.Contracts.Proto;
|
||||||
|
using ZB.MOM.WW.MxGateway.Server.Metrics;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.MxGateway.Tests.Metrics;
|
||||||
|
|
||||||
|
public sealed class MxEventFamilyNamesTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Every defined family maps to exactly the name <see cref="Enum.ToString()"/> produces, so
|
||||||
|
/// switching the per-event metric tags to the cached lookup changes no exported tag value.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void Get_ForEveryDefinedFamily_MatchesEnumToString()
|
||||||
|
{
|
||||||
|
foreach (MxEventFamily family in Enum.GetValues<MxEventFamily>())
|
||||||
|
{
|
||||||
|
Assert.Equal(family.ToString(), MxEventFamilyNames.Get(family));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>A defined family returns the same cached string instance on every call (no per-event allocation).</summary>
|
||||||
|
[Fact]
|
||||||
|
public void Get_ForDefinedFamily_ReturnsCachedInstance()
|
||||||
|
{
|
||||||
|
Assert.Same(
|
||||||
|
MxEventFamilyNames.Get(MxEventFamily.OnDataChange),
|
||||||
|
MxEventFamilyNames.Get(MxEventFamily.OnDataChange));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A numeric value the contract does not define (a newer worker, or a malformed frame) still
|
||||||
|
/// produces the <see cref="Enum.ToString()"/> form rather than throwing.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void Get_ForUndefinedValue_FallsBackToToString()
|
||||||
|
{
|
||||||
|
MxEventFamily undefined = (MxEventFamily)987654;
|
||||||
|
|
||||||
|
Assert.Equal(undefined.ToString(), MxEventFamilyNames.Get(undefined));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user