public async Task AddsTraceKeepRateMetricToRootSpan() { // Traces should be dropped when both buffers are full var calculator = new MovingAverageKeepRateCalculator(windowSize: 10, Timeout.InfiniteTimeSpan); var tracer = new Mock <IDatadogTracer>(); tracer.Setup(x => x.DefaultServiceName).Returns("Default"); var traceContext = new TraceContext(tracer.Object); var rootSpanContext = new SpanContext(null, traceContext, null); var rootSpan = new Span(rootSpanContext, DateTimeOffset.UtcNow); var childSpan = new Span(new SpanContext(rootSpanContext, traceContext, null), DateTimeOffset.UtcNow); traceContext.AddSpan(rootSpan); traceContext.AddSpan(childSpan); var trace = new[] { rootSpan, childSpan }; var sizeOfTrace = ComputeSizeOfTrace(trace); // Make the buffer size big enough for a single trace var api = new Mock <IApi>(); api.Setup(x => x.SendTracesAsync(It.IsAny <ArraySegment <byte> >(), It.IsAny <int>())) .ReturnsAsync(() => true); var agent = new AgentWriter(api.Object, statsd: null, calculator, automaticFlush: false, maxBufferSize: (sizeOfTrace * 2) + SpanBuffer.HeaderSize - 1, batchInterval: 100); // Fill both buffers agent.WriteTrace(trace); agent.WriteTrace(trace); // Drop one agent.WriteTrace(trace); await agent.FlushTracesAsync(); // Force a flush to make sure the trace is written to the API // Write another one agent.WriteTrace(trace); await agent.FlushTracesAsync(); // Force a flush to make sure the trace is written to the API api.Verify(); api.Invocations.Clear(); // Write trace and update keep rate calculator.UpdateBucket(); agent.WriteTrace(trace); await agent.FlushTracesAsync(); // Force a flush to make sure the trace is written to the API const double expectedTraceKeepRate = 0.75; rootSpan.SetMetric(Metrics.TracesKeepRate, expectedTraceKeepRate); var expectedData = Vendors.MessagePack.MessagePackSerializer.Serialize(trace, new FormatterResolverWrapper(SpanFormatterResolver.Instance)); await agent.FlushAndCloseAsync(); api.Verify(x => x.SendTracesAsync(It.Is <ArraySegment <byte> >(y => Equals(y, expectedData)), It.Is <int>(i => i == 1)), Times.Once); }
public void FullFlushShouldNotPropagateSamplingPriority() { const int partialFlushThreshold = 3; Span CreateSpan() => new Span(new SpanContext(42, SpanIdGenerator.ThreadInstance.CreateNew()), DateTimeOffset.UtcNow); var tracer = new Mock <IDatadogTracer>(); tracer.Setup(t => t.Settings).Returns(new Trace.Configuration.TracerSettings { Exporter = new Trace.Configuration.ExporterSettings() { PartialFlushEnabled = true, PartialFlushMinSpans = partialFlushThreshold } }.Build()); ArraySegment <Span>?spans = null; tracer.Setup(t => t.Write(It.IsAny <ArraySegment <Span> >())) .Callback <ArraySegment <Span> >(s => spans = s); var traceContext = new TraceContext(tracer.Object); traceContext.SetSamplingPriority(SamplingPriorityValues.UserKeep); var rootSpan = CreateSpan(); traceContext.AddSpan(rootSpan); for (int i = 0; i < partialFlushThreshold - 1; i++) { var span = CreateSpan(); traceContext.AddSpan(span); traceContext.CloseSpan(span); } // At this point, only one span is missing to reach the threshold for partial flush spans.Should().BeNull("partial flush should not have been triggered"); // Closing the root span brings the number of closed spans to the threshold // but a full flush should be triggered rather than a partial, because every span in the trace has been closed traceContext.CloseSpan(rootSpan); spans.Value.Should().NotBeNullOrEmpty("a full flush should have been triggered"); rootSpan.GetMetric(Metrics.SamplingPriority).Should().Be(SamplingPriorityValues.UserKeep, "priority should be assigned to the root span"); spans.Value.Should().OnlyContain(s => s == rootSpan || s.GetMetric(Metrics.SamplingPriority) == null, "only the root span should have a priority"); }
public void PartialFlushShouldPropagateSamplingPriority() { const int partialFlushThreshold = 2; Span CreateSpan() => new Span(new SpanContext(42, SpanIdGenerator.ThreadInstance.CreateNew()), DateTimeOffset.UtcNow); var tracer = new Mock <IDatadogTracer>(); tracer.Setup(t => t.Settings).Returns(new Trace.Configuration.TracerSettings { Exporter = new Trace.Configuration.ExporterSettings() { PartialFlushEnabled = true, PartialFlushMinSpans = partialFlushThreshold } }.Build()); ArraySegment <Span>?spans = null; tracer.Setup(t => t.Write(It.IsAny <ArraySegment <Span> >())) .Callback <ArraySegment <Span> >(s => spans = s); var traceContext = new TraceContext(tracer.Object); traceContext.SetSamplingPriority(SamplingPriorityValues.UserKeep); var rootSpan = CreateSpan(); // Root span will stay open for the duration of the test traceContext.AddSpan(rootSpan); // Add enough child spans to trigger partial flush for (int i = 0; i < partialFlushThreshold; i++) { var span = CreateSpan(); traceContext.AddSpan(span); traceContext.CloseSpan(span); } spans.Value.Should().NotBeNullOrEmpty("partial flush should have been triggered"); spans.Value.Should().OnlyContain(s => (int)s.GetMetric(Metrics.SamplingPriority) == SamplingPriorityValues.UserKeep); }
public void FlushPartialTraces(bool partialFlush) { var tracer = new Mock <IDatadogTracer>(); tracer.Setup(t => t.Settings).Returns(new Trace.Configuration.TracerSettings { Exporter = new Trace.Configuration.ExporterSettings() { PartialFlushEnabled = partialFlush, PartialFlushMinSpans = 5 } }.Build()); var traceContext = new TraceContext(tracer.Object); void AddAndCloseSpan() { var span = new Span(new SpanContext(42, SpanIdGenerator.ThreadInstance.CreateNew()), DateTimeOffset.UtcNow); traceContext.AddSpan(span); traceContext.CloseSpan(span); } var rootSpan = new Span(new SpanContext(42, SpanIdGenerator.ThreadInstance.CreateNew()), DateTimeOffset.UtcNow); traceContext.AddSpan(rootSpan); for (int i = 0; i < 4; i++) { AddAndCloseSpan(); } // At this point in time, we have 4 closed spans in the trace tracer.Verify(t => t.Write(It.IsAny <ArraySegment <Span> >()), Times.Never); AddAndCloseSpan(); // Now we have 5 closed spans, partial flush should kick-in if activated if (partialFlush) { tracer.Verify(t => t.Write(It.Is <ArraySegment <Span> >(s => s.Count == 5)), Times.Once); } else { tracer.Verify(t => t.Write(It.IsAny <ArraySegment <Span> >()), Times.Never); } for (int i = 0; i < 5; i++) { AddAndCloseSpan(); } // We have 5 more closed spans, partial flush should kick-in a second time if activated if (partialFlush) { tracer.Verify(t => t.Write(It.Is <ArraySegment <Span> >(s => s.Count == 5)), Times.Exactly(2)); } else { tracer.Verify(t => t.Write(It.IsAny <ArraySegment <Span> >()), Times.Never); } traceContext.CloseSpan(rootSpan); // Now the remaining spans are flushed if (partialFlush) { tracer.Verify(t => t.Write(It.Is <ArraySegment <Span> >(s => s.Count == 1)), Times.Once); } else { tracer.Verify(t => t.Write(It.Is <ArraySegment <Span> >(s => s.Count == 11)), Times.Once); } }