public void SerializeSpans(int traceCount, int spanCount, bool resizeExpected)
        {
            var buffer = new Ci.Agent.Payloads.EventsBuffer <Ci.IEvent>(10 * 1024 * 1024, Ci.Agent.MessagePack.CIFormatterResolver.Instance);

            var events = new List <Ci.IEvent>();

            for (int i = 0; i < traceCount; i++)
            {
                for (int j = 0; j < spanCount; j++)
                {
                    events.Add(new SpanEvent(new Span(new SpanContext(TraceId.CreateFromInt(i), (ulong)i), DateTimeOffset.UtcNow)));
                }
            }

            foreach (var @event in events)
            {
                Assert.True(buffer.TryWrite(@event));
            }

            buffer.Lock();

            Assert.Equal(traceCount * spanCount, buffer.Count);

            var content = buffer.Data;
            var resized = content.Count > Ci.Agent.Payloads.EventsBuffer <Ci.IEvent> .InitialBufferSize;

            // We want to test the case where the buffer is big enough from the start, and the case where it has to be resized
            // Make sure that the span/trace count assumptions are correct to test the scenario
            Assert.True(resizeExpected == resized, $"Total serialized size was {content.Count}");
        }
Ejemplo n.º 2
0
        static AgentWriterBenchmark()
        {
            var settings = TracerSettings.FromDefaultSources();

            settings.StartupDiagnosticLogEnabled = false;
            settings.TraceEnabled = false;

            var api = new Api(settings.AgentUri, new FakeApiRequestFactory(), statsd: null);

            AgentWriter = new AgentWriter(api, new NullMetrics(), automaticFlush: false);

            Spans         = new Span[SpanCount];
            EnrichedSpans = new Span[SpanCount];
            var now = DateTimeOffset.UtcNow;

            for (int i = 0; i < SpanCount; i++)
            {
                Spans[i]         = new Span(new SpanContext(TraceId.CreateFromInt(i), (ulong)i, SamplingPriority.UserReject, "Benchmark", null), now);
                EnrichedSpans[i] = new Span(new SpanContext(TraceId.CreateFromInt(i), (ulong)i, SamplingPriority.UserReject, "Benchmark", null), now);
                EnrichedSpans[i].SetTag(Tags.Env, "Benchmark");
                EnrichedSpans[i].SetMetric(Metrics.SamplingRuleDecision, 1.0);
            }

            // Run benchmarks once to reduce noise
            new AgentWriterBenchmark().WriteAndFlushTraces().GetAwaiter().GetResult();
            new AgentWriterBenchmark().WriteAndFlushEnrichedTraces().GetAwaiter().GetResult();
        }
Ejemplo n.º 3
0
        public void ClearingBuffer()
        {
            var buffer = new SpanBuffer(10 * 1024 * 1024, SpanFormatterResolver.Instance);

            var trace = new[]
            {
                new Span(new SpanContext(TraceId.CreateFromInt(1), 1), DateTimeOffset.UtcNow),
                new Span(new SpanContext(TraceId.CreateFromInt(2), 2), DateTimeOffset.UtcNow),
                new Span(new SpanContext(TraceId.CreateFromInt(3), 3), DateTimeOffset.UtcNow),
            };

            Assert.True(buffer.TryWrite(trace, ref _temporaryBuffer));

            Assert.Equal(1, buffer.TraceCount);
            Assert.Equal(3, buffer.SpanCount);

            buffer.Clear();

            Assert.Equal(0, buffer.TraceCount);
            Assert.Equal(0, buffer.SpanCount);

            buffer.Lock();

            var innerBuffer = buffer.Data;

            Assert.Equal(SpanBuffer.HeaderSize, innerBuffer.Count);
        }
Ejemplo n.º 4
0
        public void Extract_UnknownFormat_Throws()
        {
            const ulong parentId = 10;
            var         traceId  = TraceId.CreateFromInt(42);
            var         headers  = new MockTextMap();

            headers.Set(DDHttpHeaderNames.ParentId, parentId.ToString());
            headers.Set(DDHttpHeaderNames.TraceId, traceId.ToString());
            var mockFormat = new Mock <IFormat <ITextMap> >();

            Assert.Throws <NotSupportedException>(() => _tracer.Extract(mockFormat.Object, headers));
        }
Ejemplo n.º 5
0
        public void Extract_TextMapFormat_HeadersProperlySet_SpanContext()
        {
            const ulong parentId = 10;
            var         traceId  = TraceId.CreateFromInt(42);
            var         headers  = new MockTextMap();

            headers.Set(DDHttpHeaderNames.ParentId, parentId.ToString());
            headers.Set(DDHttpHeaderNames.TraceId, traceId.ToString());

            var otSpanContext = (OpenTracingSpanContext)_tracer.Extract(BuiltinFormats.TextMap, headers);

            Assert.Equal(parentId, otSpanContext.Context.SpanId);
            Assert.Equal(traceId, otSpanContext.Context.TraceId);
        }
        public void KeyParsing(string key, string expectedService, string expectedEnv, float expectedRate)
        {
            var rule = new DefaultSamplingRule();

            rule.SetDefaultSampleRates(new[] { new KeyValuePair <string, float>(key, .5f) });

            var span = new Span(new SpanContext(TraceId.CreateFromInt(1), 1, null, serviceName: expectedService), DateTimeOffset.Now);

            span.SetTag(Tags.Env, expectedEnv);

            var samplingRate = rule.GetSamplingRate(span);

            Assert.Equal(expectedRate, samplingRate);
        }
Ejemplo n.º 7
0
        public void Extract_ValidParentAndTraceId_ProperSpanContext()
        {
            var         traceId  = TraceId.CreateFromInt(10);
            const ulong parentId = 120;

            var headers = new MockTextMap();

            headers.Set(HttpHeaderTraceId, traceId.ToString());
            headers.Set(HttpHeaderParentId, parentId.ToString());

            var spanContext = _codec.Extract(headers) as OpenTracingSpanContext;

            Assert.NotNull(spanContext);
            Assert.Equal(traceId, spanContext.Context.TraceId);
            Assert.Equal(parentId, spanContext.Context.SpanId);
        }
Ejemplo n.º 8
0
        public void Inject_SpanContext_HeadersWithCorrectInfo()
        {
            const ulong            spanId           = 10;
            var                    traceId          = TraceId.CreateFromInt(7);
            const SamplingPriority samplingPriority = SamplingPriority.UserKeep;

            var ddSpanContext = new SpanContext(traceId, spanId, samplingPriority);
            var spanContext   = new OpenTracingSpanContext(ddSpanContext);
            var headers       = new MockTextMap();

            _codec.Inject(spanContext, headers);

            Assert.Equal(spanId.ToString(), headers.Get(HttpHeaderParentId));
            Assert.Equal(traceId.ToString(), headers.Get(HttpHeaderTraceId));
            Assert.Equal(((int)samplingPriority).ToString(), headers.Get(HttpHeaderSamplingPriority));
        }
Ejemplo n.º 9
0
        public void LockingBuffer()
        {
            var buffer = new SpanBuffer(10 * 1024 * 1024, SpanFormatterResolver.Instance);

            var trace = new[] { new Span(new SpanContext(TraceId.CreateFromInt(1), 1), DateTimeOffset.UtcNow) };

            Assert.True(buffer.TryWrite(trace, ref _temporaryBuffer));

            buffer.Lock();

            Assert.False(buffer.TryWrite(trace, ref _temporaryBuffer));

            buffer.Clear();

            Assert.True(buffer.TryWrite(trace, ref _temporaryBuffer));
        }
        public void StartActive_SetParentManuallyFromExternalContext_ParentIsSet()
        {
            var                    traceId          = TraceId.CreateFromInt(11);
            const ulong            parentId         = 7;
            const SamplingPriority samplingPriority = SamplingPriority.UserKeep;

            var parent = new SpanContext(traceId, parentId, samplingPriority);
            var child  = _tracer.StartActive("Child", parent);

            Assert.True(child.Span.IsRootSpan);
            Assert.Equal(traceId, parent.TraceId);
            Assert.Equal(parentId, parent.SpanId);
            Assert.Null(parent.TraceContext);
            Assert.Equal(parent, child.Span.Context.Parent);
            Assert.Equal(parentId, child.Span.Context.ParentId);
            Assert.NotNull(child.Span.Context.TraceContext);
            Assert.Equal(samplingPriority, child.Span.Context.TraceContext.SamplingPriority);
        }
Ejemplo n.º 11
0
        public void Extract_WrongHeaderCase_ExtractionStillWorks()
        {
            var                    traceId          = TraceId.CreateFromInt(10);
            const ulong            parentId         = 120;
            const SamplingPriority samplingPriority = SamplingPriority.UserKeep;

            var headers = new MockTextMap();

            headers.Set(HttpHeaderTraceId.ToUpper(), traceId.ToString());
            headers.Set(HttpHeaderParentId.ToUpper(), parentId.ToString());
            headers.Set(HttpHeaderSamplingPriority.ToUpper(), ((int)samplingPriority).ToString());

            var spanContext = _codec.Extract(headers) as OpenTracingSpanContext;

            Assert.NotNull(spanContext);
            Assert.Equal(traceId, spanContext.Context.TraceId);
            Assert.Equal(parentId, spanContext.Context.SpanId);
        }
Ejemplo n.º 12
0
        internal void InjectExtract_Identity(IHeadersCollection headers)
        {
            var       traceId = TraceId.CreateFromInt(9);
            const int spanId  = 7;
            const SamplingPriority samplingPriority = SamplingPriority.UserKeep;
            const string           origin           = "synthetics";

            var context = new SpanContext(traceId, spanId, samplingPriority, null, origin);

            _propagator.Inject(context, headers);
            var resultContext = _propagator.Extract(headers);

            Assert.NotNull(resultContext);
            Assert.Equal(context.SpanId, resultContext.SpanId);
            Assert.Equal(context.TraceId, resultContext.TraceId);
            Assert.Equal(context.SamplingPriority, resultContext.SamplingPriority);
            Assert.Equal(context.Origin, resultContext.Origin);
        }
        public void WebRequest_InjectExtract_Identity()
        {
            var propagator = new B3SpanContextPropagator();

            var       traceId = TraceId.CreateFromInt(9);
            const int spanId  = 7;
            const SamplingPriority samplingPriority = SamplingPriority.UserKeep;

            IHeadersCollection headers = WebRequest.CreateHttp("http://localhost").Headers.Wrap();
            var context = new SpanContext(traceId, spanId, samplingPriority);

            propagator.Inject(context, headers);
            var resultContext = propagator.Extract(headers);

            Assert.NotNull(resultContext);
            Assert.Equal(context.SpanId, resultContext.SpanId);
            Assert.Equal(context.TraceId, resultContext.TraceId);
            Assert.Equal(context.SamplingPriority, resultContext.SamplingPriority);
        }
        public void Extract_InvalidSamplingPriority(string samplingPriority)
        {
            var propagator = new B3SpanContextPropagator();

            var         traceId = TraceId.CreateFromInt(9);
            const ulong spanId  = 7;

            var headers = InjectContext(
                traceId.ToString(),
                spanId.ToString("x16", CultureInfo.InvariantCulture),
                samplingPriority);

            var resultContext = propagator.Extract(headers);

            Assert.NotNull(resultContext);
            Assert.Equal(traceId, resultContext.TraceId);
            Assert.Equal(spanId, resultContext.SpanId);
            Assert.Null(resultContext.SamplingPriority);
        }
        public void Extract_InvalidSpanId(string spanId)
        {
            var propagator = new B3SpanContextPropagator();

            var traceId = TraceId.CreateFromInt(9);
            const SamplingPriority samplingPriority = SamplingPriority.UserKeep;

            var headers = InjectContext(
                traceId.ToString(),
                spanId,
                ((int)samplingPriority).ToString(CultureInfo.InvariantCulture));

            var resultContext = propagator.Extract(headers);

            Assert.NotNull(resultContext);
            Assert.Equal(traceId, resultContext.TraceId);
            Assert.Equal(default(ulong), resultContext.SpanId);
            Assert.Equal(samplingPriority, resultContext.SamplingPriority);
        }
Ejemplo n.º 16
0
        public void SerializeSpans(int traceCount, int spanCount, bool resizeExpected)
        {
            var buffer = new SpanBuffer(10 * 1024 * 1024, SpanFormatterResolver.Instance);

            var traces = new List <Span[]>();

            for (int i = 0; i < traceCount; i++)
            {
                var spans = new Span[spanCount];

                for (int j = 0; j < spanCount; j++)
                {
                    spans[j] = new Span(new SpanContext(TraceId.CreateFromInt(i), (ulong)i), DateTimeOffset.UtcNow);
                }

                traces.Add(spans);
            }

            foreach (var trace in traces)
            {
                Assert.True(buffer.TryWrite(trace, ref _temporaryBuffer));
            }

            buffer.Lock();

            Assert.Equal(traceCount, buffer.TraceCount);
            Assert.Equal(traceCount * spanCount, buffer.SpanCount);

            var content = buffer.Data;

            var result = MessagePack.MessagePackSerializer.Deserialize <FakeSpan[][]>(content);

            var resized = content.Count > SpanBuffer.InitialBufferSize;

            // We want to test the case where the buffer is big enough from the start, and the case where it has to be resized
            // Make sure that the span/trace count assumptions are correct to test the scenario
            Assert.True(resizeExpected == resized, $"Total serialized size was {content.Count}");

            Assert.Equal(traceCount, result.Length);
            Assert.Equal(traceCount * spanCount, result.Sum(t => t.Length));
        }
        public void OriginHeader_RootSpanTag()
        {
            var                    traceId          = TraceId.CreateFromInt(9);
            const ulong            spanId           = 7;
            const SamplingPriority samplingPriority = SamplingPriority.UserKeep;
            const string           origin           = "synthetics";

            var propagatedContext = new SpanContext(traceId, spanId, samplingPriority, null, origin);

            Assert.Equal(origin, propagatedContext.Origin);

            using var firstSpan = _tracer.StartActive("First Span", propagatedContext);
            Assert.True(firstSpan.Span.IsRootSpan);
            Assert.Equal(origin, firstSpan.Span.Context.Origin);
            Assert.Equal(origin, firstSpan.Span.GetTag(Tags.Origin));

            using var secondSpan = _tracer.StartActive("Child", firstSpan.Span.Context);
            Assert.False(secondSpan.Span.IsRootSpan);
            Assert.Equal(origin, secondSpan.Span.Context.Origin);
            Assert.Null(secondSpan.Span.GetTag(Tags.Origin));
        }
Ejemplo n.º 18
0
        internal void Extract_InvalidSamplingPriority(IHeadersCollection headers, string samplingPriority)
        {
            var          traceId = TraceId.CreateFromInt(9);
            const ulong  spanId  = 7;
            const string origin  = "synthetics";

            InjectContext(
                headers,
                traceId.ToString(),
                spanId.ToString(CultureInfo.InvariantCulture),
                samplingPriority,
                origin);

            var resultContext = _propagator.Extract(headers);

            Assert.NotNull(resultContext);
            Assert.Equal(traceId, resultContext.TraceId);
            Assert.Equal(spanId, resultContext.SpanId);
            Assert.Null(resultContext.SamplingPriority);
            Assert.Equal(origin, resultContext.Origin);
        }
        public async Task WriteTrace_2Traces_SendToApi()
        {
            var trace         = new[] { new Span(new SpanContext(TraceId.CreateFromInt(1), 1), DateTimeOffset.UtcNow) };
            var expectedData1 = Vendors.MessagePack.MessagePackSerializer.Serialize(trace, new FormatterResolverWrapper(SpanFormatterResolver.Instance));

            _agentWriter.WriteTrace(trace);
            await _agentWriter.FlushTracesAsync(); // Force a flush to make sure the trace is written to the API

            _api.Verify(x => x.SendTracesAsync(It.Is <ArraySegment <byte> >(y => Equals(y, expectedData1)), It.Is <int>(i => i == 1)), Times.Once);

            _api.Invocations.Clear();

            trace = new[] { new Span(new SpanContext(TraceId.CreateFromInt(2), 2), DateTimeOffset.UtcNow) };
            var expectedData2 = Vendors.MessagePack.MessagePackSerializer.Serialize(trace, new FormatterResolverWrapper(SpanFormatterResolver.Instance));

            _agentWriter.WriteTrace(trace);
            await _agentWriter.FlushTracesAsync(); // Force a flush to make sure the trace is written to the API

            _api.Verify(x => x.SendTracesAsync(It.Is <ArraySegment <byte> >(y => Equals(y, expectedData2)), It.Is <int>(i => i == 1)), Times.Once);

            await _agentWriter.FlushAndCloseAsync();
        }
        public void OriginHeader_InjectFromChildSpan()
        {
            var                    traceId          = TraceId.CreateFromInt(9);
            const ulong            spanId           = 7;
            const SamplingPriority samplingPriority = SamplingPriority.UserKeep;
            const string           origin           = "synthetics";

            var propagatedContext = new SpanContext(traceId, spanId, samplingPriority, null, origin);
            var propagator        = new DDSpanContextPropagator(new DatadogTraceIdConvention());

            using var firstSpan  = _tracer.StartActive("First Span", propagatedContext);
            using var secondSpan = _tracer.StartActive("Child", firstSpan.Span.Context);

            IHeadersCollection headers = WebRequest.CreateHttp("http://localhost").Headers.Wrap();

            propagator.Inject(secondSpan.Span.Context, headers);
            var resultContext = propagator.Extract(headers);

            Assert.NotNull(resultContext);
            Assert.Equal(firstSpan.Span.Context.Origin, resultContext.Origin);
            Assert.Equal(secondSpan.Span.Context.Origin, resultContext.Origin);
            Assert.Equal(origin, resultContext.Origin);
        }
Ejemplo n.º 21
0
        public void Overflow()
        {
            var buffer = new SpanBuffer(10, SpanFormatterResolver.Instance);

            Assert.False(buffer.IsFull);

            var trace = new[] { new Span(new SpanContext(TraceId.CreateFromInt(1), 1), DateTimeOffset.UtcNow) };

            var result = buffer.TryWrite(trace, ref _temporaryBuffer);

            Assert.False(result);
            Assert.Equal(0, buffer.TraceCount);
            Assert.True(buffer.IsFull);

            buffer.Lock();

            var innerBuffer = buffer.Data;

            Assert.True(innerBuffer.Array.Skip(SpanBuffer.HeaderSize).All(b => b == 0x0), "No data should have been written to the buffer");

            buffer.Clear();

            Assert.False(buffer.IsFull);
        }
Ejemplo n.º 22
0
        private static RateLimitResult RunTest(int?intervalLimit, RateLimitLoadTest test)
        {
            var parallelism = test.NumberPerBurst;

            if (parallelism > Environment.ProcessorCount)
            {
                parallelism = Environment.ProcessorCount;
            }

            var clock = new SimpleClock();

            var limiter         = new RateLimiter(maxTracesPerInterval: intervalLimit);
            var barrier         = new Barrier(parallelism + 1, _ => clock.UtcNow += test.TimeBetweenBursts);
            var numberPerThread = test.NumberPerBurst / parallelism;
            var workers         = new Task[parallelism];
            int totalAttempted  = 0;
            int totalAllowed    = 0;

            for (int i = 0; i < workers.Length; i++)
            {
                workers[i] = Task.Factory.StartNew(
                    () =>
                {
                    using var lease = Clock.SetForCurrentThread(clock);

                    for (var i = 0; i < test.NumberOfBursts; i++)
                    {
                        // Wait for every worker to be ready for next burst
                        barrier.SignalAndWait();

                        for (int j = 0; j < numberPerThread; j++)
                        {
                            // trace id and span id are not used in rate-limiting
                            var spanContext = new SpanContext(TraceId.CreateFromInt(1), spanId: 1, serviceName: "Weeeee");

                            // pass a specific start time since there is no TraceContext
                            var span = new Span(spanContext, DateTimeOffset.UtcNow);

                            Interlocked.Increment(ref totalAttempted);

                            if (limiter.Allowed(span))
                            {
                                Interlocked.Increment(ref totalAllowed);
                            }
                        }
                    }
                },
                    TaskCreationOptions.LongRunning);
            }

            // Wait for all workers to be ready
            barrier.SignalAndWait();

            // We do not need to synchronize with workers anymore
            barrier.RemoveParticipant();

            // Wait for workers to finish
            Task.WaitAll(workers);

            var result = new RateLimitResult
            {
                RateLimiter    = limiter,
                ReportedRate   = limiter.GetEffectiveRate(),
                TotalAttempted = totalAttempted,
                TotalAllowed   = totalAllowed
            };

            return(result);
        }
 private static Span[] CreateTrace(int numberOfSpans)
 {
     return(Enumerable.Range(0, numberOfSpans)
            .Select(i => new Span(new SpanContext(TraceId.CreateFromInt(i + 1), (ulong)i + 1), DateTimeOffset.UtcNow))
            .ToArray());
 }
Ejemplo n.º 24
0
        public void FlushPartialTraces(bool partialFlush)
        {
            var tracer = new Mock <IDatadogTracer>();

            tracer.Setup(t => t.Settings).Returns(new Trace.Configuration.TracerSettings
            {
                PartialFlushEnabled  = partialFlush,
                PartialFlushMinSpans = 5
            });

            var traceContext = new TraceContext(tracer.Object);

            void AddAndCloseSpan()
            {
                var span = new Span(new SpanContext(TraceId.CreateFromInt(42), SpanIdGenerator.ThreadInstance.CreateNew()), DateTimeOffset.UtcNow);

                traceContext.AddSpan(span);
                traceContext.CloseSpan(span);
            }

            var rootSpan = new Span(new SpanContext(TraceId.CreateFromInt(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 <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 <Span[]>(s => s.Length == 5)), Times.Once);
            }
            else
            {
                tracer.Verify(t => t.Write(It.IsAny <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 <Span[]>(s => s.Length == 5)), Times.Exactly(2));
            }
            else
            {
                tracer.Verify(t => t.Write(It.IsAny <Span[]>()), Times.Never);
            }

            traceContext.CloseSpan(rootSpan);

            // Now the remaining spans are flushed
            if (partialFlush)
            {
                tracer.Verify(t => t.Write(It.Is <Span[]>(s => s.Length == 1)), Times.Once);
            }
            else
            {
                tracer.Verify(t => t.Write(It.Is <Span[]>(s => s.Length == 11)), Times.Once);
            }
        }
        public void CreateFromInt_CreatesIdCorrectly()
        {
            var traceId = TraceId.CreateFromInt(123);

            traceId.ToString().Should().Be("123");
        }
Ejemplo n.º 26
0
        public void Serialization(bool topLevelSpan)
        {
            var tags = new CommonTags();

            Span span;

            if (topLevelSpan)
            {
                span = new Span(new SpanContext(TraceId.CreateFromInt(42), 41), DateTimeOffset.UtcNow, tags);
            }
            else
            {
                // Assign a parent to prevent the span from being considered as top-level
                var traceContext = new TraceContext(Mock.Of <IDatadogTracer>());
                var parent       = new SpanContext(TraceId.CreateFromInt(42), 41);
                span = new Span(new SpanContext(parent, traceContext, null), DateTimeOffset.UtcNow, tags);
            }

            // The span has 1 "common" tag and 15 additional tags (and same number of metrics)
            // Those numbers are picked to test the variable-size header of MessagePack
            // The header is resized when there are 16 or more elements in the collection
            // Neither common or additional tags have enough elements, but put together they will cause to use a bigger header
            tags.Environment           = "Test";
            tags.SamplingLimitDecision = 0.5;

            for (int i = 0; i < 15; i++)
            {
                span.SetTag(i.ToString(), i.ToString());
            }

            for (int i = 0; i < 15; i++)
            {
                span.SetMetric(i.ToString(), i);
            }

            var buffer = new byte[0];

            var resolver = new FormatterResolverWrapper(SpanFormatterResolver.Instance);

            MessagePackSerializer.Serialize(ref buffer, 0, span, resolver);

            var deserializedSpan = MessagePack.MessagePackSerializer.Deserialize <FakeSpan>(buffer);

            Assert.Equal(16, deserializedSpan.Tags.Count);

            // For top-level spans, there is one metric added during serialization
            Assert.Equal(topLevelSpan ? 17 : 16, deserializedSpan.Metrics.Count);

            Assert.Equal("Test", deserializedSpan.Tags[Tags.Env]);
            Assert.Equal(0.5, deserializedSpan.Metrics[Metrics.SamplingLimitDecision]);

            for (int i = 0; i < 15; i++)
            {
                Assert.Equal(i.ToString(), deserializedSpan.Tags[i.ToString()]);
                Assert.Equal((double)i, deserializedSpan.Metrics[i.ToString()]);
            }

            if (topLevelSpan)
            {
                Assert.Equal(1.0, deserializedSpan.Metrics[Metrics.TopLevelSpan]);
            }
        }