diff --git a/src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGatewayService.cs b/src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGatewayService.cs index 5ed29ea..201d3e0 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGatewayService.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGatewayService.cs @@ -183,7 +183,7 @@ public sealed class MxAccessGatewayService( // call is cancelled, instead of leaving it to the transport's own timeout. await responseStream.WriteAsync(publicEvent, context.CancellationToken).ConfigureAwait(false); metrics.RecordEventStreamSend( - publicEvent.Family.ToString(), + MxEventFamilyNames.Get(publicEvent.Family), Stopwatch.GetElapsedTime(sendStartTimestamp)); } } diff --git a/src/ZB.MOM.WW.MxGateway.Server/Metrics/MxEventFamilyNames.cs b/src/ZB.MOM.WW.MxGateway.Server/Metrics/MxEventFamilyNames.cs new file mode 100644 index 0000000..1fe54d4 --- /dev/null +++ b/src/ZB.MOM.WW.MxGateway.Server/Metrics/MxEventFamilyNames.cs @@ -0,0 +1,28 @@ +using System.Collections.Frozen; +using ZB.MOM.WW.MxGateway.Contracts.Proto; + +namespace ZB.MOM.WW.MxGateway.Server.Metrics; + +/// +/// Cached names for the per-event metric tags. +/// +/// +/// 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 , which is the same string the uncached call produced. +/// +public static class MxEventFamilyNames +{ + private static readonly FrozenDictionary Names = + Enum.GetValues() + .Distinct() + .ToFrozenDictionary(family => family, family => family.ToString()); + + /// Gets the name of an event family. + /// The event family. + /// The family's name, identical to . + public static string Get(MxEventFamily family) => + Names.TryGetValue(family, out string? name) ? name : family.ToString(); +} diff --git a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs index 873dcdd..fc8b48b 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs @@ -692,7 +692,7 @@ public sealed class WorkerClient : IWorkerClient { 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)) diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Metrics/MxEventFamilyNamesTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Metrics/MxEventFamilyNamesTests.cs new file mode 100644 index 0000000..953280f --- /dev/null +++ b/src/ZB.MOM.WW.MxGateway.Tests/Metrics/MxEventFamilyNamesTests.cs @@ -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 +{ + /// + /// Every defined family maps to exactly the name produces, so + /// switching the per-event metric tags to the cached lookup changes no exported tag value. + /// + [Fact] + public void Get_ForEveryDefinedFamily_MatchesEnumToString() + { + foreach (MxEventFamily family in Enum.GetValues()) + { + Assert.Equal(family.ToString(), MxEventFamilyNames.Get(family)); + } + } + + /// A defined family returns the same cached string instance on every call (no per-event allocation). + [Fact] + public void Get_ForDefinedFamily_ReturnsCachedInstance() + { + Assert.Same( + MxEventFamilyNames.Get(MxEventFamily.OnDataChange), + MxEventFamilyNames.Get(MxEventFamily.OnDataChange)); + } + + /// + /// A numeric value the contract does not define (a newer worker, or a malformed frame) still + /// produces the form rather than throwing. + /// + [Fact] + public void Get_ForUndefinedValue_FallsBackToToString() + { + MxEventFamily undefined = (MxEventFamily)987654; + + Assert.Equal(undefined.ToString(), MxEventFamilyNames.Get(undefined)); + } +}