diff --git a/src/NATS.Server/Imports/LatencyTracker.cs b/src/NATS.Server/Imports/LatencyTracker.cs new file mode 100644 index 0000000..d4e9d43 --- /dev/null +++ b/src/NATS.Server/Imports/LatencyTracker.cs @@ -0,0 +1,47 @@ +using System.Text.Json.Serialization; + +namespace NATS.Server.Imports; + +public sealed class ServiceLatencyMsg +{ + [JsonPropertyName("type")] + public string Type { get; set; } = "io.nats.server.metric.v1.service_latency"; + + [JsonPropertyName("requestor")] + public string Requestor { get; set; } = string.Empty; + + [JsonPropertyName("responder")] + public string Responder { get; set; } = string.Empty; + + [JsonPropertyName("status")] + public int Status { get; set; } = 200; + + [JsonPropertyName("svc_latency")] + public long ServiceLatencyNanos { get; set; } + + [JsonPropertyName("total_latency")] + public long TotalLatencyNanos { get; set; } +} + +public static class LatencyTracker +{ + public static bool ShouldSample(ServiceLatency latency) + { + if (latency.SamplingPercentage <= 0) return false; + if (latency.SamplingPercentage >= 100) return true; + return Random.Shared.Next(100) < latency.SamplingPercentage; + } + + public static ServiceLatencyMsg BuildLatencyMsg( + string requestor, string responder, + TimeSpan serviceLatency, TimeSpan totalLatency) + { + return new ServiceLatencyMsg + { + Requestor = requestor, + Responder = responder, + ServiceLatencyNanos = serviceLatency.Ticks * 100, + TotalLatencyNanos = totalLatency.Ticks * 100, + }; + } +} diff --git a/tests/NATS.Server.Tests/ResponseRoutingTests.cs b/tests/NATS.Server.Tests/ResponseRoutingTests.cs index 6f29340..33badf8 100644 --- a/tests/NATS.Server.Tests/ResponseRoutingTests.cs +++ b/tests/NATS.Server.Tests/ResponseRoutingTests.cs @@ -124,4 +124,26 @@ public class ResponseRoutingTests resp2.To.ShouldBe("_INBOX.reply2"); resp1.From.ShouldNotBe(resp2.From); } + + [Fact] + public void LatencyTracker_should_sample_respects_percentage() + { + var latency = new ServiceLatency { SamplingPercentage = 0, Subject = "latency.test" }; + LatencyTracker.ShouldSample(latency).ShouldBeFalse(); + + var latency100 = new ServiceLatency { SamplingPercentage = 100, Subject = "latency.test" }; + LatencyTracker.ShouldSample(latency100).ShouldBeTrue(); + } + + [Fact] + public void LatencyTracker_builds_latency_message() + { + var msg = LatencyTracker.BuildLatencyMsg("requester", "responder", + TimeSpan.FromMilliseconds(5), TimeSpan.FromMilliseconds(10)); + + msg.Requestor.ShouldBe("requester"); + msg.Responder.ShouldBe("responder"); + msg.ServiceLatencyNanos.ShouldBeGreaterThan(0); + msg.TotalLatencyNanos.ShouldBeGreaterThan(0); + } }