From 3103d7ca4849f0c7824fcaa25657f706ab95d1c1 Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Wed, 6 Oct 2021 19:36:20 +0200 Subject: [PATCH 01/13] Implement DroppedSpanStats --- src/Elastic.Apm/Model/DroppedSpanStats.cs | 52 +++++++ src/Elastic.Apm/Model/Span.cs | 21 +-- src/Elastic.Apm/Model/Transaction.cs | 96 ++++++++---- .../DroppedSpansStatsTests.cs | 147 ++++++++++++++++++ 4 files changed, 276 insertions(+), 40 deletions(-) create mode 100644 src/Elastic.Apm/Model/DroppedSpanStats.cs create mode 100644 test/Elastic.Apm.Tests/DroppedSpansStatsTests.cs diff --git a/src/Elastic.Apm/Model/DroppedSpanStats.cs b/src/Elastic.Apm/Model/DroppedSpanStats.cs new file mode 100644 index 000000000..c325bd51c --- /dev/null +++ b/src/Elastic.Apm/Model/DroppedSpanStats.cs @@ -0,0 +1,52 @@ +// Licensed to Elasticsearch B.V under +// one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using Elastic.Apm.Api; +using Elastic.Apm.Libraries.Newtonsoft.Json; + +namespace Elastic.Apm.Model +{ + /// + /// DroppedSpanStats holds information about spans that were dropped (for example due to transaction_max_spans or + /// exit_span_min_duration). + /// + internal class DroppedSpanStats + { + public DroppedSpanStats(string destinationServiceResource, Outcome outcome, double durationSumUs) + { + DurationCount = 1; + DestinationServiceResource = destinationServiceResource; + Outcome = outcome; + DurationSumUs = durationSumUs; + } + + /// + /// DestinationServiceResource identifies the destination service resource being operated on. e.g. 'http://elastic.co:80', + /// 'elasticsearch', 'rabbitmq/queue_name'. + /// + [JsonProperty("destination_service_resource")] + public string DestinationServiceResource { get; } + + /// + /// Duration holds duration aggregations about the dropped span. + /// Count holds the number of times the dropped span happened. + /// + [JsonProperty("duration.count")] + public int DurationCount { get; set; } + + + /// + /// Duration holds duration aggregations about the dropped span. + /// Sum holds dimensions about the dropped span's duration. + /// + [JsonProperty("duration.sum.us")] + public double DurationSumUs { get; set; } + + /// + /// Outcome of the aggregated spans. + /// + public Outcome Outcome { get; } + } +} diff --git a/src/Elastic.Apm/Model/Span.cs b/src/Elastic.Apm/Model/Span.cs index 751ef4515..6659f1145 100644 --- a/src/Elastic.Apm/Model/Span.cs +++ b/src/Elastic.Apm/Model/Span.cs @@ -362,17 +362,20 @@ public void End() else _enclosingTransaction.SpanTimings.TryAdd(new SpanTimerKey(Type, Subtype), new SpanTimer(SelfDuration)); - if (ShouldBeSentToApmServer && isFirstEndCall) + try { - try - { - DeduceDestination(); - } - catch (Exception e) - { - _logger.Warning()?.LogException(e, "Failed deducing destination fields for span."); - } + DeduceDestination(); + } + catch (Exception e) + { + _logger.Warning()?.LogException(e, "Failed deducing destination fields for span."); + } + if (_isDropped && Context?.Destination?.Service?.Resource != null && Duration.HasValue) + _enclosingTransaction.UpdateDroppedSpanStats(Context.Destination.Service.Resource, _outcome, Duration.Value); + + if (ShouldBeSentToApmServer && isFirstEndCall) + { // Spans are sent only for sampled transactions so it's only worth capturing stack trace for sampled spans // ReSharper disable once CompareOfFloatsByEqualityOperator if (Configuration.StackTraceLimit != 0 && Configuration.SpanFramesMinDurationInMilliseconds != 0 && RawStackTrace == null diff --git a/src/Elastic.Apm/Model/Transaction.cs b/src/Elastic.Apm/Model/Transaction.cs index 7edaca9b9..20967971a 100644 --- a/src/Elastic.Apm/Model/Transaction.cs +++ b/src/Elastic.Apm/Model/Transaction.cs @@ -14,11 +14,11 @@ using Elastic.Apm.Config; using Elastic.Apm.DistributedTracing; using Elastic.Apm.Helpers; +using Elastic.Apm.Libraries.Newtonsoft.Json; using Elastic.Apm.Logging; +using Elastic.Apm.Metrics.MetricsProvider; using Elastic.Apm.Report; using Elastic.Apm.ServerInfo; -using Elastic.Apm.Libraries.Newtonsoft.Json; -using Elastic.Apm.Metrics.MetricsProvider; namespace Elastic.Apm.Model { @@ -37,12 +37,13 @@ internal class Transaction : ITransaction /// have this activity as its parent and the TraceId will flow to all Activity instances. /// private readonly Activity _activity; + private readonly IApmServerInfo _apmServerInfo; - private readonly Lazy _context = new Lazy(); + private readonly BreakdownMetricsProvider _breakdownMetricsProvider; + private readonly Lazy _context = new(); private readonly ICurrentExecutionSegmentsContainer _currentExecutionSegmentsContainer; private readonly IApmLogger _logger; private readonly IPayloadSender _sender; - private readonly BreakdownMetricsProvider _breakdownMetricsProvider; [JsonConstructor] // ReSharper disable once UnusedMember.Local - this constructor is meant for serialization @@ -81,7 +82,10 @@ internal Transaction(ApmAgent agent, string name, string type, long? timestamp = /// /// The ExecutionSegmentsContainer which makes sure this transaction flows /// Component to fetch info about APM Server (e.g. APM Server version) - /// The instance which will capture the breakdown metrics + /// + /// The instance which will capture the + /// breakdown metrics + /// /// /// If set the transaction will ignore Activity.Current and it's trace id, /// otherwise the agent will try to keep ids in-sync across async work-flows @@ -281,25 +285,14 @@ internal Transaction( } } - private bool _isEnded; - - private string _name; - internal ChildDurationTimer ChildDurationTimer { get; } = new(); - /// - /// Holds configuration snapshot (which is immutable) that was current when this transaction started. - /// We would like transaction data to be consistent and not to be affected by possible changes in agent's configuration - /// between the start and the end of the transaction. That is why the way all the data is collected for the transaction - /// and its spans is controlled by this configuration snapshot. + /// Internal dictionary to keep track of and look up dropped span stats. /// - [JsonIgnore] - public IConfiguration Configuration { get; } + private Dictionary<(string, Outcome), DroppedSpanStats> _droppedSpanStatsMap; - /// - /// Any arbitrary contextual information regarding the event, captured by the agent, optionally provided by the user. - /// - /// - public Context Context => _context.Value; + private bool _isEnded; + + private string _name; /// /// In general if there is an error on the span, the outcome will be Outcome.Failure otherwise it'll be @@ -313,22 +306,29 @@ internal Transaction( private Outcome _outcome; private bool _outcomeChangedThroughApi; + internal ChildDurationTimer ChildDurationTimer { get; } = new(); /// - /// Changes the by checking the flag. - /// This method is intended for all auto instrumentation usages where the property needs to be set. - /// Setting outcome via the property is intended for users who use the public API. + /// Holds configuration snapshot (which is immutable) that was current when this transaction started. + /// We would like transaction data to be consistent and not to be affected by possible changes in agent's configuration + /// between the start and the end of the transaction. That is why the way all the data is collected for the transaction + /// and its spans is controlled by this configuration snapshot. /// - /// The outcome of the transaction will be set to this value if it wasn't change to the public API previously - internal void SetOutcome(Outcome outcome) - { - if (!_outcomeChangedThroughApi) - _outcome = outcome; - } + [JsonIgnore] + public IConfiguration Configuration { get; } + + /// + /// Any arbitrary contextual information regarding the event, captured by the agent, optionally provided by the user. + /// + /// + public Context Context => _context.Value; [JsonIgnore] public Dictionary Custom => Context.Custom; + [JsonProperty("dropped_spans_stats")] + public List DroppedSpanStats => _droppedSpanStatsMap?.Values.ToList(); + /// /// /// The duration of the transaction in ms with 3 decimal points. @@ -387,7 +387,7 @@ public Outcome Outcome } [JsonIgnore] - public DistributedTracingData OutgoingDistributedTracingData => new DistributedTracingData(TraceId, Id, IsSampled, _traceState); + public DistributedTracingData OutgoingDistributedTracingData => new(TraceId, Id, IsSampled, _traceState); [MaxLength] [JsonProperty("parent_id")] @@ -428,6 +428,21 @@ public Outcome Outcome [MaxLength] public string Type { get; set; } + /// + /// Changes the by checking the flag. + /// This method is intended for all auto instrumentation usages where the property needs to be set. + /// Setting outcome via the property is intended for users who use the public API. + /// + /// + /// The outcome of the transaction will be set to this value if it wasn't change to the public API + /// previously + /// + internal void SetOutcome(Outcome outcome) + { + if (!_outcomeChangedThroughApi) + _outcome = outcome; + } + private Activity StartActivity() { var activity = new Activity(ApmTransactionActivityName); @@ -436,6 +451,25 @@ private Activity StartActivity() return activity; } + internal void UpdateDroppedSpanStats(string destinationServiceResource, Outcome outcome, double duration) + { + _droppedSpanStatsMap ??= new Dictionary<(string, Outcome), DroppedSpanStats>(); + + if (_droppedSpanStatsMap.Keys.Count >= 128) + return; + + if (_droppedSpanStatsMap.ContainsKey((destinationServiceResource, outcome))) + { + _droppedSpanStatsMap[(destinationServiceResource, outcome)].DurationCount++; + _droppedSpanStatsMap[(destinationServiceResource, outcome)].DurationSumUs += duration; + } + else + { + _droppedSpanStatsMap.Add((destinationServiceResource, outcome), + new DroppedSpanStats(destinationServiceResource, outcome, duration)); + } + } + /// public void SetService(string serviceName, string serviceVersion) { diff --git a/test/Elastic.Apm.Tests/DroppedSpansStatsTests.cs b/test/Elastic.Apm.Tests/DroppedSpansStatsTests.cs new file mode 100644 index 000000000..88cda61a4 --- /dev/null +++ b/test/Elastic.Apm.Tests/DroppedSpansStatsTests.cs @@ -0,0 +1,147 @@ +// Licensed to Elasticsearch B.V under +// one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System; +using System.Linq; +using Elastic.Apm.Api; +using Elastic.Apm.Model; +using Elastic.Apm.Tests.Utilities; +using FluentAssertions; +using Xunit; + +namespace Elastic.Apm.Tests +{ + /// + /// Tests + /// + public class DroppedSpansStatsTests + { + [Fact] + public void SingleDroppedSpanTest() + { + var payloadSender = new MockPayloadSender(); + using (var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender, + configuration: new MockConfiguration(transactionMaxSpans: "1")))) + { + var transaction = agent.Tracer.StartTransaction("foo", "test"); + //This is the span which won't be dropped + transaction.CaptureSpan("fooSpan", "test", () => { }); + + //This span will be dropped + var span1 = transaction.StartSpan("foo", "bar"); + span1.Context.Http = new Http { Method = "GET", StatusCode = 200, Url = "https://foo.bar" }; + span1.Duration = 100; + span1.End(); + + transaction.End(); + } + + payloadSender.Spans.Should().HaveCount(1); + + payloadSender.FirstTransaction.DroppedSpanStats.Should().NotBeNullOrEmpty(); + payloadSender.FirstTransaction.DroppedSpanStats.Should().HaveCount(1); + payloadSender.FirstTransaction.DroppedSpanStats.First().DestinationServiceResource.Should().Be("foo.bar:443"); + payloadSender.FirstTransaction.DroppedSpanStats.First().Outcome.Should().Be(Outcome.Success); + payloadSender.FirstTransaction.DroppedSpanStats.First().DurationCount.Should().Be(1); + payloadSender.FirstTransaction.DroppedSpanStats.First().DurationSumUs.Should().Be(100); + } + + [Fact] + public void MultipleDroppedSpanTest() + { + var payloadSender = new MockPayloadSender(); + using (var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender, + configuration: new MockConfiguration(transactionMaxSpans: "1")))) + { + var transaction = agent.Tracer.StartTransaction("foo", "test"); + //This is the span which won't be dropped + transaction.CaptureSpan("fooSpan", "test", () => { }); + + //Next spans will be dropped + var span1 = transaction.StartSpan("foo", "bar"); + span1.Context.Http = new Http { Method = "GET", StatusCode = 200, Url = "https://foo.bar" }; + span1.Duration = 100; + span1.End(); + + var span2 = transaction.StartSpan("foo", "bar"); + span2.Context.Http = new Http { Method = "GET", StatusCode = 200, Url = "https://foo.bar" }; + span2.Duration = 150; + span2.End(); + + var span3 = transaction.StartSpan("foo", "bar"); + span3.Context.Http = new Http { Method = "GET", StatusCode = 400, Url = "https://foo.bar" }; + span3.Outcome = Outcome.Failure; + span3.Duration = 50; + span3.End(); + + var span4 = transaction.StartSpan("foo", "bar"); + span4.Context.Http = new Http { Method = "GET", StatusCode = 400, Url = "https://foo2.bar" }; + span4.Duration = 15; + span4.End(); + + for (var i = 0; i < 50; i++) + { + var span5 = transaction.StartSpan("foo", "bar"); + span5.Context.Destination = new Destination { Service = new Destination.DestinationService { Resource = "mysql" } }; + span5.Context.Db = new Database { Instance = "instance1", Type = "mysql", Statement = "Select Foo From Bar" }; + span5.Duration = 50; + span5.End(); + } + + transaction.End(); + } + + payloadSender.Spans.Should().HaveCount(1); + + payloadSender.FirstTransaction.DroppedSpanStats.Should().NotBeNullOrEmpty(); + payloadSender.FirstTransaction.DroppedSpanStats.Should().HaveCount(4); + + payloadSender.FirstTransaction.DroppedSpanStats.Should() + .Contain(n => n.Outcome == Outcome.Success + && n.DurationCount == 2 && Math.Abs(n.DurationSumUs - 250) < 1 && n.DestinationServiceResource == "foo.bar:443"); + + payloadSender.FirstTransaction.DroppedSpanStats.Should() + .Contain(n => n.Outcome == Outcome.Failure + && n.DurationCount == 1 && Math.Abs(n.DurationSumUs - 50) < 1 && n.DestinationServiceResource == "foo.bar:443"); + + payloadSender.FirstTransaction.DroppedSpanStats.Should() + .Contain(n => n.Outcome == Outcome.Success + && n.DurationCount == 1 && Math.Abs(n.DurationSumUs - 15) < 1 && n.DestinationServiceResource == "foo2.bar:443"); + + payloadSender.FirstTransaction.DroppedSpanStats.Should() + .Contain(n => n.Outcome == Outcome.Success + && n.DurationCount == 50 && Math.Abs(n.DurationSumUs - 50 * 50) < 1 && n.DestinationServiceResource == "mysql"); + } + + /// + /// Tests the fix 128 upper limit + /// + [Fact] + public void MaxDroppedSpanStatsTest() + { + var payloadSender = new MockPayloadSender(); + using (var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender, + configuration: new MockConfiguration(transactionMaxSpans: "1")))) + { + var transaction = agent.Tracer.StartTransaction("foo", "test"); + //This is the span which won't be dropped + transaction.CaptureSpan("fooSpan", "test", () => { }); + + //Next spans will be dropped + for (var i = 0; i < 500; i++) + { + var span1 = transaction.StartSpan("foo", "bar"); + span1.Context.Http = new Http { Method = "GET", StatusCode = 200, Url = $"https://foo{i}.bar" }; + span1.Duration = 100; + span1.End(); + } + + transaction.End(); + } + + payloadSender.FirstTransaction.DroppedSpanStats.Should().HaveCount(128); + } + } +} From 292fd58b70baa76a1ad40802552f10622486a35b Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Wed, 6 Oct 2021 20:26:54 +0200 Subject: [PATCH 02/13] Dropped spans: calculate destination for dropped spans stats --- .../HttpDiagnosticListenerImplBase.cs | 6 ------ src/Elastic.Apm/Model/DbSpanCommon.cs | 15 +++++---------- src/Elastic.Apm/Model/Transaction.cs | 2 +- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/Elastic.Apm/DiagnosticListeners/HttpDiagnosticListenerImplBase.cs b/src/Elastic.Apm/DiagnosticListeners/HttpDiagnosticListenerImplBase.cs index 4c6c453a6..cf7ad80cb 100644 --- a/src/Elastic.Apm/DiagnosticListeners/HttpDiagnosticListenerImplBase.cs +++ b/src/Elastic.Apm/DiagnosticListeners/HttpDiagnosticListenerImplBase.cs @@ -191,12 +191,6 @@ private void ProcessStartEvent(TRequest request, Uri requestUrl) PropagateTraceContext(request, transaction, span); - if (span is Span realSpan) - { - if (!realSpan.ShouldBeSentToApmServer) - return; - } - span.Context.Http = new Http { Method = method }; span.Context.Http.SetUrl(requestUrl); } diff --git a/src/Elastic.Apm/Model/DbSpanCommon.cs b/src/Elastic.Apm/Model/DbSpanCommon.cs index f6a8da177..9a3ec9ee1 100644 --- a/src/Elastic.Apm/Model/DbSpanCommon.cs +++ b/src/Elastic.Apm/Model/DbSpanCommon.cs @@ -46,17 +46,12 @@ internal void EndSpan(ISpan span, IDbCommand dbCommand, Outcome outcome, TimeSpa capturedSpan.Subtype = spanSubtype; capturedSpan.Action = GetSpanAction(dbCommand.CommandType); - if (capturedSpan.ShouldBeSentToApmServer) + capturedSpan.Context.Db = new Database { - capturedSpan.Context.Db = new Database - { - Statement = GetDbSpanName(dbCommand), - Instance = dbCommand.Connection.Database, - Type = Database.TypeSql - }; - - capturedSpan.Context.Destination = GetDestination(dbCommand.Connection?.ConnectionString, defaultPort); - } + Statement = GetDbSpanName(dbCommand), Instance = dbCommand.Connection.Database, Type = Database.TypeSql + }; + + capturedSpan.Context.Destination = GetDestination(dbCommand.Connection?.ConnectionString, defaultPort); capturedSpan.Outcome = outcome; } diff --git a/src/Elastic.Apm/Model/Transaction.cs b/src/Elastic.Apm/Model/Transaction.cs index 20967971a..5ad3a3f2f 100644 --- a/src/Elastic.Apm/Model/Transaction.cs +++ b/src/Elastic.Apm/Model/Transaction.cs @@ -327,7 +327,7 @@ internal Transaction( public Dictionary Custom => Context.Custom; [JsonProperty("dropped_spans_stats")] - public List DroppedSpanStats => _droppedSpanStatsMap?.Values.ToList(); + public IEnumerable DroppedSpanStats => _droppedSpanStatsMap?.Values.ToList(); /// /// From 28f522e3ee40c4c59d020a89613e10fb8fd6d54a Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Mon, 18 Oct 2021 22:50:36 +0200 Subject: [PATCH 03/13] Update Transaction.cs Introduce DroppedSpanStatsKey --- src/Elastic.Apm/Model/Transaction.cs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Elastic.Apm/Model/Transaction.cs b/src/Elastic.Apm/Model/Transaction.cs index 5ad3a3f2f..66deef4ee 100644 --- a/src/Elastic.Apm/Model/Transaction.cs +++ b/src/Elastic.Apm/Model/Transaction.cs @@ -288,7 +288,7 @@ internal Transaction( /// /// Internal dictionary to keep track of and look up dropped span stats. /// - private Dictionary<(string, Outcome), DroppedSpanStats> _droppedSpanStatsMap; + private Dictionary _droppedSpanStatsMap; private bool _isEnded; @@ -453,19 +453,19 @@ private Activity StartActivity() internal void UpdateDroppedSpanStats(string destinationServiceResource, Outcome outcome, double duration) { - _droppedSpanStatsMap ??= new Dictionary<(string, Outcome), DroppedSpanStats>(); + _droppedSpanStatsMap ??= new Dictionary(); if (_droppedSpanStatsMap.Keys.Count >= 128) return; - if (_droppedSpanStatsMap.ContainsKey((destinationServiceResource, outcome))) + if (_droppedSpanStatsMap.TryGetValue(new DroppedSpanStatsKey(destinationServiceResource, outcome), out var item)) { - _droppedSpanStatsMap[(destinationServiceResource, outcome)].DurationCount++; - _droppedSpanStatsMap[(destinationServiceResource, outcome)].DurationSumUs += duration; + item.DurationCount++; + item.DurationSumUs += duration; } else { - _droppedSpanStatsMap.Add((destinationServiceResource, outcome), + _droppedSpanStatsMap.Add(new DroppedSpanStatsKey(destinationServiceResource, outcome), new DroppedSpanStats(destinationServiceResource, outcome, duration)); } } @@ -769,5 +769,19 @@ public void SetLabel(string key, long value) public void SetLabel(string key, decimal value) => _context.Value.InternalLabels.Value.InnerDictionary[key] = value; + + private struct DroppedSpanStatsKey + { + // ReSharper disable once NotAccessedField.Local + private string _destinationServiceResource; + // ReSharper disable once NotAccessedField.Local + private Outcome _outcome; + + public DroppedSpanStatsKey(string destinationServiceResource, Outcome outcome) + { + _destinationServiceResource = destinationServiceResource; + _outcome = outcome; + } + } } } From 471fc12e7ee2867bb0f7aa2a6d031bda3082209f Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Wed, 3 Nov 2021 18:52:49 +0100 Subject: [PATCH 04/13] Update Transaction.cs --- src/Elastic.Apm/Model/Transaction.cs | 55 +++++++++++++++++++++------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/src/Elastic.Apm/Model/Transaction.cs b/src/Elastic.Apm/Model/Transaction.cs index 66deef4ee..1071c59ab 100644 --- a/src/Elastic.Apm/Model/Transaction.cs +++ b/src/Elastic.Apm/Model/Transaction.cs @@ -453,20 +453,31 @@ private Activity StartActivity() internal void UpdateDroppedSpanStats(string destinationServiceResource, Outcome outcome, double duration) { - _droppedSpanStatsMap ??= new Dictionary(); - - if (_droppedSpanStatsMap.Keys.Count >= 128) - return; - - if (_droppedSpanStatsMap.TryGetValue(new DroppedSpanStatsKey(destinationServiceResource, outcome), out var item)) + if (_droppedSpanStatsMap == null) { - item.DurationCount++; - item.DurationSumUs += duration; + _droppedSpanStatsMap = new Dictionary + { + { + new DroppedSpanStatsKey(destinationServiceResource, outcome), + new DroppedSpanStats(destinationServiceResource, outcome, duration) + } + }; } else { - _droppedSpanStatsMap.Add(new DroppedSpanStatsKey(destinationServiceResource, outcome), - new DroppedSpanStats(destinationServiceResource, outcome, duration)); + if (_droppedSpanStatsMap.Count >= 128) + return; + + if (_droppedSpanStatsMap.TryGetValue(new DroppedSpanStatsKey(destinationServiceResource, outcome), out var item)) + { + item.DurationCount++; + item.DurationSumUs += duration; + } + else + { + _droppedSpanStatsMap.Add(new DroppedSpanStatsKey(destinationServiceResource, outcome), + new DroppedSpanStats(destinationServiceResource, outcome, duration)); + } } } @@ -770,18 +781,36 @@ public void SetLabel(string key, long value) public void SetLabel(string key, decimal value) => _context.Value.InternalLabels.Value.InnerDictionary[key] = value; - private struct DroppedSpanStatsKey + private readonly struct DroppedSpanStatsKey : IEquatable { // ReSharper disable once NotAccessedField.Local - private string _destinationServiceResource; + private readonly string _destinationServiceResource; + // ReSharper disable once NotAccessedField.Local - private Outcome _outcome; + private readonly Outcome _outcome; public DroppedSpanStatsKey(string destinationServiceResource, Outcome outcome) { _destinationServiceResource = destinationServiceResource; _outcome = outcome; } + + public bool Equals(DroppedSpanStatsKey other) => + _destinationServiceResource == other._destinationServiceResource && _outcome == other._outcome; + + public override bool Equals(object obj) => obj is DroppedSpanStatsKey other && Equals(other); + + public override int GetHashCode() + { + unchecked + { + return ((_destinationServiceResource != null ? _destinationServiceResource.GetHashCode() : 0) * 397) ^ (int)_outcome; + } + } + + public static bool operator ==(DroppedSpanStatsKey left, DroppedSpanStatsKey right) => left.Equals(right); + + public static bool operator !=(DroppedSpanStatsKey left, DroppedSpanStatsKey right) => !left.Equals(right); } } } From 093e6468808590f2ddd43b5a5f8fc3d8c269fd12 Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Wed, 3 Nov 2021 19:58:17 +0100 Subject: [PATCH 05/13] Update DroppedSpansStatsTests.cs --- test/Elastic.Apm.Tests/DroppedSpansStatsTests.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/Elastic.Apm.Tests/DroppedSpansStatsTests.cs b/test/Elastic.Apm.Tests/DroppedSpansStatsTests.cs index 88cda61a4..15992ab0f 100644 --- a/test/Elastic.Apm.Tests/DroppedSpansStatsTests.cs +++ b/test/Elastic.Apm.Tests/DroppedSpansStatsTests.cs @@ -30,7 +30,7 @@ public void SingleDroppedSpanTest() transaction.CaptureSpan("fooSpan", "test", () => { }); //This span will be dropped - var span1 = transaction.StartSpan("foo", "bar"); + var span1 = transaction.StartSpan("foo", "bar", isExitSpan: true); span1.Context.Http = new Http { Method = "GET", StatusCode = 200, Url = "https://foo.bar" }; span1.Duration = 100; span1.End(); @@ -60,30 +60,30 @@ public void MultipleDroppedSpanTest() transaction.CaptureSpan("fooSpan", "test", () => { }); //Next spans will be dropped - var span1 = transaction.StartSpan("foo", "bar"); + var span1 = transaction.StartSpan("foo", "bar", isExitSpan: true); span1.Context.Http = new Http { Method = "GET", StatusCode = 200, Url = "https://foo.bar" }; span1.Duration = 100; span1.End(); - var span2 = transaction.StartSpan("foo", "bar"); + var span2 = transaction.StartSpan("foo", "bar", isExitSpan: true); span2.Context.Http = new Http { Method = "GET", StatusCode = 200, Url = "https://foo.bar" }; span2.Duration = 150; span2.End(); - var span3 = transaction.StartSpan("foo", "bar"); + var span3 = transaction.StartSpan("foo", "bar", isExitSpan: true); span3.Context.Http = new Http { Method = "GET", StatusCode = 400, Url = "https://foo.bar" }; span3.Outcome = Outcome.Failure; span3.Duration = 50; span3.End(); - var span4 = transaction.StartSpan("foo", "bar"); + var span4 = transaction.StartSpan("foo", "bar", isExitSpan: true); span4.Context.Http = new Http { Method = "GET", StatusCode = 400, Url = "https://foo2.bar" }; span4.Duration = 15; span4.End(); for (var i = 0; i < 50; i++) { - var span5 = transaction.StartSpan("foo", "bar"); + var span5 = transaction.StartSpan("foo", "bar", isExitSpan: true); span5.Context.Destination = new Destination { Service = new Destination.DestinationService { Resource = "mysql" } }; span5.Context.Db = new Database { Instance = "instance1", Type = "mysql", Statement = "Select Foo From Bar" }; span5.Duration = 50; @@ -132,7 +132,7 @@ public void MaxDroppedSpanStatsTest() //Next spans will be dropped for (var i = 0; i < 500; i++) { - var span1 = transaction.StartSpan("foo", "bar"); + var span1 = transaction.StartSpan("foo", "bar", isExitSpan: true); span1.Context.Http = new Http { Method = "GET", StatusCode = 200, Url = $"https://foo{i}.bar" }; span1.Duration = 100; span1.End(); From 176fa5b7558da07202dfed911b535c5d7d9c57a2 Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Mon, 8 Nov 2021 19:43:05 +0100 Subject: [PATCH 06/13] Do not create Span.Context for dropped spans --- .../GrpcClientDiagnosticListener.cs | 2 +- .../HttpDiagnosticListenerImplBase.cs | 9 +++++ src/Elastic.Apm/Helpers/UrlUtils.cs | 3 +- src/Elastic.Apm/Model/DbSpanCommon.cs | 15 +++++--- src/Elastic.Apm/Model/Span.cs | 20 +++++++++-- .../DroppedSpansStats.cs | 34 +++++++++++++++++++ .../DroppedSpansStatsTests.cs | 29 ++++++++++++++++ 7 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 test/Elastic.Apm.Benchmarks/DroppedSpansStats.cs diff --git a/src/Elastic.Apm.GrpcClient/GrpcClientDiagnosticListener.cs b/src/Elastic.Apm.GrpcClient/GrpcClientDiagnosticListener.cs index 987ccffac..93879dade 100644 --- a/src/Elastic.Apm.GrpcClient/GrpcClientDiagnosticListener.cs +++ b/src/Elastic.Apm.GrpcClient/GrpcClientDiagnosticListener.cs @@ -64,7 +64,7 @@ protected override void HandleOnNext(KeyValuePair kv) span.Outcome = GrpcHelper.GrpcClientReturnCodeToOutcome(GrpcHelper.GrpcReturnCodeToString(grpcStatusCode)); span.Context.Destination = UrlUtils.ExtractDestination(requestObject.RequestUri, Logger); - span.Context.Destination.Service = UrlUtils.ExtractService(requestObject.RequestUri, span); + span.Context.Destination.Service = new() { Resource = UrlUtils.ExtractService(requestObject.RequestUri, span) }; span.End(); } diff --git a/src/Elastic.Apm/DiagnosticListeners/HttpDiagnosticListenerImplBase.cs b/src/Elastic.Apm/DiagnosticListeners/HttpDiagnosticListenerImplBase.cs index cf7ad80cb..74263c03f 100644 --- a/src/Elastic.Apm/DiagnosticListeners/HttpDiagnosticListenerImplBase.cs +++ b/src/Elastic.Apm/DiagnosticListeners/HttpDiagnosticListenerImplBase.cs @@ -191,6 +191,15 @@ private void ProcessStartEvent(TRequest request, Uri requestUrl) PropagateTraceContext(request, transaction, span); + if (span is Span realSpan) + { + if (!realSpan.ShouldBeSentToApmServer) + { + realSpan.ServiceResource = UrlUtils.ExtractService(requestUrl, realSpan); + return; + } + } + span.Context.Http = new Http { Method = method }; span.Context.Http.SetUrl(requestUrl); } diff --git a/src/Elastic.Apm/Helpers/UrlUtils.cs b/src/Elastic.Apm/Helpers/UrlUtils.cs index b15babed9..efa1bc9b5 100644 --- a/src/Elastic.Apm/Helpers/UrlUtils.cs +++ b/src/Elastic.Apm/Helpers/UrlUtils.cs @@ -33,7 +33,6 @@ internal static Destination ExtractDestination(Uri url, IApmLogger logger) return new Destination { Address = host, Port = url.Port == -1 ? (int?)null : url.Port }; } - internal static Destination.DestinationService ExtractService(Uri url, ISpan span) => - new() { Resource = $"{url.Host}:{url.Port}" }; + internal static string ExtractService(Uri url, ISpan span) => $"{url.Host}:{url.Port}"; } } diff --git a/src/Elastic.Apm/Model/DbSpanCommon.cs b/src/Elastic.Apm/Model/DbSpanCommon.cs index 9a3ec9ee1..2d993d2f6 100644 --- a/src/Elastic.Apm/Model/DbSpanCommon.cs +++ b/src/Elastic.Apm/Model/DbSpanCommon.cs @@ -46,12 +46,17 @@ internal void EndSpan(ISpan span, IDbCommand dbCommand, Outcome outcome, TimeSpa capturedSpan.Subtype = spanSubtype; capturedSpan.Action = GetSpanAction(dbCommand.CommandType); - capturedSpan.Context.Db = new Database + if (capturedSpan.ShouldBeSentToApmServer) { - Statement = GetDbSpanName(dbCommand), Instance = dbCommand.Connection.Database, Type = Database.TypeSql - }; - - capturedSpan.Context.Destination = GetDestination(dbCommand.Connection?.ConnectionString, defaultPort); + capturedSpan.Context.Db = new Database + { + Statement = GetDbSpanName(dbCommand), Instance = dbCommand.Connection.Database, Type = Database.TypeSql + }; + + capturedSpan.Context.Destination = GetDestination(dbCommand.Connection?.ConnectionString, defaultPort); + } + else + capturedSpan.ServiceResource = !string.IsNullOrEmpty(capturedSpan.Subtype) ? capturedSpan.Subtype : Database.TypeSql + dbCommand.Connection.Database; capturedSpan.Outcome = outcome; } diff --git a/src/Elastic.Apm/Model/Span.cs b/src/Elastic.Apm/Model/Span.cs index 6659f1145..96349c7b9 100644 --- a/src/Elastic.Apm/Model/Span.cs +++ b/src/Elastic.Apm/Model/Span.cs @@ -132,6 +132,14 @@ public Span( [MaxLength] public string Action { get; set; } + /// + /// Stores Context.Destination.Service.Resource on the top level. + /// With this field, we can set Resource for dropped spans without instantiating Context. + /// Only set for dropped spans. + /// + [JsonIgnore] + public string ServiceResource { get; set; } + [JsonIgnore] internal IConfiguration Configuration => _enclosingTransaction.Configuration; @@ -371,8 +379,8 @@ public void End() _logger.Warning()?.LogException(e, "Failed deducing destination fields for span."); } - if (_isDropped && Context?.Destination?.Service?.Resource != null && Duration.HasValue) - _enclosingTransaction.UpdateDroppedSpanStats(Context.Destination.Service.Resource, _outcome, Duration.Value); + if (_isDropped && (!string.IsNullOrEmpty(ServiceResource) || (_context.IsValueCreated && Context?.Destination?.Service?.Resource != null))) + _enclosingTransaction.UpdateDroppedSpanStats(ServiceResource ?? Context?.Destination?.Service?.Resource, _outcome, Duration.Value); if (ShouldBeSentToApmServer && isFirstEndCall) { @@ -458,6 +466,12 @@ private void DeduceDestination() if (!IsExitSpan) return; + if (!_context.IsValueCreated && string.IsNullOrEmpty(ServiceResource)) + { + ServiceResource = !string.IsNullOrEmpty(Subtype) ? Subtype : Type; + return; + } + if (Context.Http != null) { var destination = DeduceHttpDestination(); @@ -492,7 +506,7 @@ void FillDestinationService() else if (Context.Http?.Url != null) { if (!string.IsNullOrEmpty(_context?.Value?.Http?.Url)) - Context.Destination.Service = UrlUtils.ExtractService(_context.Value.Http.OriginalUrl, this); + Context.Destination.Service = new() { Resource = UrlUtils.ExtractService(_context.Value.Http.OriginalUrl, this) }; else Context.Destination.Service.Resource = !string.IsNullOrEmpty(Subtype) ? Subtype : Type; } diff --git a/test/Elastic.Apm.Benchmarks/DroppedSpansStats.cs b/test/Elastic.Apm.Benchmarks/DroppedSpansStats.cs new file mode 100644 index 000000000..49ba75182 --- /dev/null +++ b/test/Elastic.Apm.Benchmarks/DroppedSpansStats.cs @@ -0,0 +1,34 @@ +// Licensed to Elasticsearch B.V under +// one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using BenchmarkDotNet.Attributes; +using Elastic.Apm.Tests.Utilities; + +namespace Elastic.Apm.Benchmarks +{ + [MemoryDiagnoser] + public class DroppedSpansStats + { + private ApmAgent _agent; + + [GlobalSetup] + public void SetupWithLowMaxSpans() + => _agent = new ApmAgent(new AgentComponents(payloadSender: new MockPayloadSender(), + configurationReader: new MockConfiguration(transactionMaxSpans: "1"))); + + [Benchmark] + public void Test10Spans() + { + var noopLogger = new NoopLogger(); + _agent = new ApmAgent(new AgentComponents(payloadSender: new MockPayloadSender(), logger: noopLogger, + configurationReader: new MockConfiguration(noopLogger))); + + _agent.Tracer.CaptureTransaction("foo", "bar", t => + { + for (var i = 0; i < 10; i++) t.CaptureSpan("foo", "bar", () => { }); + }); + } + } +} diff --git a/test/Elastic.Apm.Tests/DroppedSpansStatsTests.cs b/test/Elastic.Apm.Tests/DroppedSpansStatsTests.cs index 15992ab0f..00e06adf7 100644 --- a/test/Elastic.Apm.Tests/DroppedSpansStatsTests.cs +++ b/test/Elastic.Apm.Tests/DroppedSpansStatsTests.cs @@ -143,5 +143,34 @@ public void MaxDroppedSpanStatsTest() payloadSender.FirstTransaction.DroppedSpanStats.Should().HaveCount(128); } + + /// + /// Testing with custom spans without touching Span.Context + /// + [Fact] + public void SimpleDroppedSpans() + { + var payloadSender = new MockPayloadSender(); + using (var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender, + configuration: new MockConfiguration(transactionMaxSpans: "1")))) + { + var transaction = agent.Tracer.StartTransaction("foo", "test"); + //This is the span which won't be dropped + transaction.CaptureSpan("fooSpan", "test", () => { }); + + //Next spans will be dropped + for (var i = 0; i < 500; i++) + { + var span = transaction.StartSpan("foo", "bar", isExitSpan: true); + span.End(); + } + + transaction.End(); + } + payloadSender.FirstTransaction.DroppedSpanStats.Should().HaveCount(1); + + payloadSender.FirstTransaction.DroppedSpanStats.First().DestinationServiceResource.Should().Be("bar"); + payloadSender.FirstTransaction.DroppedSpanStats.First().DurationCount.Should().Be(500); + } } } From ed3cd95fbaabea12728b35a5d01bb98c90fb1c0d Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Mon, 8 Nov 2021 20:27:29 +0100 Subject: [PATCH 07/13] Update ExitSpanTests.cs --- .../ApiTests/ExitSpanTests.cs | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/test/Elastic.Apm.Tests/ApiTests/ExitSpanTests.cs b/test/Elastic.Apm.Tests/ApiTests/ExitSpanTests.cs index a84cf9eb8..529ee66b5 100644 --- a/test/Elastic.Apm.Tests/ApiTests/ExitSpanTests.cs +++ b/test/Elastic.Apm.Tests/ApiTests/ExitSpanTests.cs @@ -3,6 +3,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using Elastic.Apm.Api; using Elastic.Apm.Tests.Utilities; using FluentAssertions; using Xunit; @@ -14,7 +15,6 @@ public class ExitSpanTests [Fact] public void TestNonExitSpan() { - var payloadSender = new MockPayloadSender(); using var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender)); agent.Tracer.CaptureTransaction("foo", "bar", t => @@ -26,7 +26,7 @@ public void TestNonExitSpan() } [Fact] - public void SimpleManualExitSpan() + public void SimpleManualExitSpanWithNoContext() { var payloadSender = new MockPayloadSender(); using var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender)); @@ -35,8 +35,27 @@ public void SimpleManualExitSpan() t.StartSpan("foo", "bar", isExitSpan: true).End(); }); + payloadSender.FirstSpan.Context.Destination.Should().BeNull(); + payloadSender.FirstSpan.ServiceResource.Should().Be("bar"); + } + + [Fact] + public void SimpleManualExitSpanWithContext() + { + var payloadSender = new MockPayloadSender(); + using var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender)); + agent.Tracer.CaptureTransaction("foo", "bar", t => + { + var span = t.StartSpan("foo", "bar", isExitSpan: true); + + span.Context.Http = new Http { Method = "GET", Url = "https://elastic.co", StatusCode = 200 }; + span.End(); + }); + payloadSender.FirstSpan.Context.Destination.Should().NotBeNull(); - payloadSender.FirstSpan.Context.Destination.Service.Resource.Should().Be("bar"); + payloadSender.FirstSpan.Context.Destination.Address.Should().Be("elastic.co"); + payloadSender.FirstSpan.Context.Destination.Port.Should().Be(443); + payloadSender.FirstSpan.Context.Destination.Service.Resource.Should().Be("elastic.co:443"); } } } From 2b6d0c5a2dafb30e87b7a81a9c66536f46710a76 Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Mon, 8 Nov 2021 22:15:23 +0100 Subject: [PATCH 08/13] Update Span.cs --- src/Elastic.Apm/Model/Span.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Elastic.Apm/Model/Span.cs b/src/Elastic.Apm/Model/Span.cs index 96349c7b9..564a673e5 100644 --- a/src/Elastic.Apm/Model/Span.cs +++ b/src/Elastic.Apm/Model/Span.cs @@ -466,9 +466,10 @@ private void DeduceDestination() if (!IsExitSpan) return; - if (!_context.IsValueCreated && string.IsNullOrEmpty(ServiceResource)) + if (!_context.IsValueCreated) { - ServiceResource = !string.IsNullOrEmpty(Subtype) ? Subtype : Type; + if(string.IsNullOrEmpty(ServiceResource)) + ServiceResource = !string.IsNullOrEmpty(Subtype) ? Subtype : Type; return; } From bf4e9cb4220e30baf09d4b2f65c2dfe7931fa9df Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Mon, 8 Nov 2021 22:39:59 +0100 Subject: [PATCH 09/13] Remove obsolete fields --- .../AzureMessagingServiceBusDiagnosticListener.cs | 6 ++---- .../MicrosoftAzureServiceBusDiagnosticListener.cs | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Elastic.Apm.Azure.ServiceBus/AzureMessagingServiceBusDiagnosticListener.cs b/src/Elastic.Apm.Azure.ServiceBus/AzureMessagingServiceBusDiagnosticListener.cs index 1df6d5f4a..e2ae89ab6 100644 --- a/src/Elastic.Apm.Azure.ServiceBus/AzureMessagingServiceBusDiagnosticListener.cs +++ b/src/Elastic.Apm.Azure.ServiceBus/AzureMessagingServiceBusDiagnosticListener.cs @@ -1,4 +1,4 @@ -// Licensed to Elasticsearch B.V under +// Licensed to Elasticsearch B.V under // one or more agreements. // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information @@ -252,9 +252,7 @@ private void OnSendStart(KeyValuePair kv, string action) Address = destinationAddress, Service = new Destination.DestinationService { - Name = ServiceBus.SubType, - Resource = queueName is null ? ServiceBus.SubType : $"{ServiceBus.SubType}/{queueName}", - Type = ApiConstants.TypeMessaging + Resource = queueName is null ? ServiceBus.SubType : $"{ServiceBus.SubType}/{queueName}" } }; diff --git a/src/Elastic.Apm.Azure.ServiceBus/MicrosoftAzureServiceBusDiagnosticListener.cs b/src/Elastic.Apm.Azure.ServiceBus/MicrosoftAzureServiceBusDiagnosticListener.cs index 4457c25aa..c584e0b29 100644 --- a/src/Elastic.Apm.Azure.ServiceBus/MicrosoftAzureServiceBusDiagnosticListener.cs +++ b/src/Elastic.Apm.Azure.ServiceBus/MicrosoftAzureServiceBusDiagnosticListener.cs @@ -1,4 +1,4 @@ -// Licensed to Elasticsearch B.V under +// Licensed to Elasticsearch B.V under // one or more agreements. // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information @@ -236,9 +236,7 @@ private void OnSendStart(KeyValuePair kv, string action, Propert Address = destinationAddress?.AbsoluteUri, Service = new Destination.DestinationService { - Name = ServiceBus.SubType, - Resource = queueName is null ? ServiceBus.SubType : $"{ServiceBus.SubType}/{queueName}", - Type = ApiConstants.TypeMessaging + Resource = queueName is null ? ServiceBus.SubType : $"{ServiceBus.SubType}/{queueName}" } }; From d7b2626d1260efa0d7888c7d65e564d219e9cf5b Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Mon, 8 Nov 2021 23:03:29 +0100 Subject: [PATCH 10/13] Update DroppedSpansStats.cs --- test/Elastic.Apm.Benchmarks/DroppedSpansStats.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/test/Elastic.Apm.Benchmarks/DroppedSpansStats.cs b/test/Elastic.Apm.Benchmarks/DroppedSpansStats.cs index 49ba75182..b4ac0d6ef 100644 --- a/test/Elastic.Apm.Benchmarks/DroppedSpansStats.cs +++ b/test/Elastic.Apm.Benchmarks/DroppedSpansStats.cs @@ -19,16 +19,10 @@ public void SetupWithLowMaxSpans() configurationReader: new MockConfiguration(transactionMaxSpans: "1"))); [Benchmark] - public void Test10Spans() - { - var noopLogger = new NoopLogger(); - _agent = new ApmAgent(new AgentComponents(payloadSender: new MockPayloadSender(), logger: noopLogger, - configurationReader: new MockConfiguration(noopLogger))); - - _agent.Tracer.CaptureTransaction("foo", "bar", t => - { - for (var i = 0; i < 10; i++) t.CaptureSpan("foo", "bar", () => { }); - }); - } + public void Test10Spans() => _agent.Tracer.CaptureTransaction("foo", "bar", t => + { + for (var i = 0; i < 10; i++) + t.CaptureSpan("foo", "bar", () => { }); + }); } } From 23db266f9b57bbaeaa6d213f758037ee04096613 Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Tue, 9 Nov 2021 00:11:45 +0100 Subject: [PATCH 11/13] Generate less spans in Transaction_And_Spans_Captured_When_Large_Request --- .../Controllers/DatabaseController.cs | 6 +++--- .../HttpContextCurrentExecutionSegmentsContainerTests.cs | 4 ---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/sample/AspNetFullFrameworkSampleApp/Controllers/DatabaseController.cs b/sample/AspNetFullFrameworkSampleApp/Controllers/DatabaseController.cs index 0f2b0da86..cba726b9a 100644 --- a/sample/AspNetFullFrameworkSampleApp/Controllers/DatabaseController.cs +++ b/sample/AspNetFullFrameworkSampleApp/Controllers/DatabaseController.cs @@ -107,16 +107,16 @@ public async Task Bulk(IEnumerable mode return JsonBadRequest(new { success = false, message = "Invalid samples" }); var sampleData = model.Select(m => new SampleData { Name = m.Name }); + + var aggregatedModel = new SampleData { Name = model.Aggregate("", (current, next) => current + ", " + next.Name) }; int changes; using (var context = new SampleDataDbContext()) - using (var transaction = context.Database.BeginTransaction()) { context.Configuration.AutoDetectChangesEnabled = false; context.Configuration.ValidateOnSaveEnabled = false; - context.Set().AddRange(sampleData); + context.Set().Add(aggregatedModel); changes = await context.SaveChangesAsync(); - transaction.Commit(); } return Json(new { success = true, changes }); diff --git a/test/Elastic.Apm.AspNetFullFramework.Tests/HttpContextCurrentExecutionSegmentsContainerTests.cs b/test/Elastic.Apm.AspNetFullFramework.Tests/HttpContextCurrentExecutionSegmentsContainerTests.cs index 6120feab4..b11160d3b 100644 --- a/test/Elastic.Apm.AspNetFullFramework.Tests/HttpContextCurrentExecutionSegmentsContainerTests.cs +++ b/test/Elastic.Apm.AspNetFullFramework.Tests/HttpContextCurrentExecutionSegmentsContainerTests.cs @@ -48,10 +48,6 @@ await WaitAndCustomVerifyReceivedData(received => { received.Transactions.Count.Should().Be(1); var transaction = received.Transactions.Single(); - - transaction.SpanCount.Started.Should().Be(500); - transaction.SpanCount.Dropped.Should().Be(501); - received.Spans.Count.Should().Be(500); }); } From c0d5ec39d3e856c9e9c8ff3970136a4658709dbd Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Tue, 9 Nov 2021 15:35:41 +0100 Subject: [PATCH 12/13] Update HttpContextCurrentExecutionSegmentsContainerTests.cs --- .../HttpContextCurrentExecutionSegmentsContainerTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Elastic.Apm.AspNetFullFramework.Tests/HttpContextCurrentExecutionSegmentsContainerTests.cs b/test/Elastic.Apm.AspNetFullFramework.Tests/HttpContextCurrentExecutionSegmentsContainerTests.cs index b11160d3b..66b87db8f 100644 --- a/test/Elastic.Apm.AspNetFullFramework.Tests/HttpContextCurrentExecutionSegmentsContainerTests.cs +++ b/test/Elastic.Apm.AspNetFullFramework.Tests/HttpContextCurrentExecutionSegmentsContainerTests.cs @@ -72,13 +72,13 @@ await WaitAndCustomVerifyReceivedData(received => var firstTransaction = transactions.First(); firstTransaction.Name.Should().EndWith("Bulk"); - firstTransaction.SpanCount.Started.Should().Be(100); + firstTransaction.SpanCount.Started.Should().Be(1); var secondTransaction = transactions.Last(); secondTransaction.Name.Should().EndWith("Generate"); secondTransaction.SpanCount.Started.Should().Be(3); - received.Spans.Count.Should().Be(103); + received.Spans.Count.Should().Be(4); }); } From dc5dd496ceb4b47833163fb17f66a3262787c3c2 Mon Sep 17 00:00:00 2001 From: Greg Kalapos Date: Tue, 9 Nov 2021 17:47:03 +0100 Subject: [PATCH 13/13] Adapt test - remove unused fields --- .../AzureMessagingServiceBusDiagnosticListenerTests.cs | 8 -------- .../MicrosoftAzureServiceBusDiagnosticListenerTests.cs | 8 -------- 2 files changed, 16 deletions(-) diff --git a/test/Elastic.Apm.Azure.ServiceBus.Tests/AzureMessagingServiceBusDiagnosticListenerTests.cs b/test/Elastic.Apm.Azure.ServiceBus.Tests/AzureMessagingServiceBusDiagnosticListenerTests.cs index 0d6c4583e..c70369a98 100644 --- a/test/Elastic.Apm.Azure.ServiceBus.Tests/AzureMessagingServiceBusDiagnosticListenerTests.cs +++ b/test/Elastic.Apm.Azure.ServiceBus.Tests/AzureMessagingServiceBusDiagnosticListenerTests.cs @@ -61,9 +61,7 @@ await _agent.Tracer.CaptureTransaction("Send AzureServiceBus Message", "message" var destination = span.Context.Destination; destination.Address.Should().Be(_environment.ServiceBusConnectionStringProperties.FullyQualifiedNamespace); - destination.Service.Name.Should().Be(ServiceBus.SubType); destination.Service.Resource.Should().Be($"{ServiceBus.SubType}/{scope.QueueName}"); - destination.Service.Type.Should().Be(ApiConstants.TypeMessaging); span.Context.Message.Should().NotBeNull(); span.Context.Message.Queue.Should().NotBeNull(); @@ -94,9 +92,7 @@ await _agent.Tracer.CaptureTransaction("Send AzureServiceBus Message", "message" var destination = span.Context.Destination; destination.Address.Should().Be(_environment.ServiceBusConnectionStringProperties.FullyQualifiedNamespace); - destination.Service.Name.Should().Be(ServiceBus.SubType); destination.Service.Resource.Should().Be($"{ServiceBus.SubType}/{scope.TopicName}"); - destination.Service.Type.Should().Be(ApiConstants.TypeMessaging); span.Context.Message.Should().NotBeNull(); span.Context.Message.Queue.Should().NotBeNull(); @@ -129,9 +125,7 @@ await sender.ScheduleMessageAsync( var destination = span.Context.Destination; destination.Address.Should().Be(_environment.ServiceBusConnectionStringProperties.FullyQualifiedNamespace); - destination.Service.Name.Should().Be(ServiceBus.SubType); destination.Service.Resource.Should().Be($"{ServiceBus.SubType}/{scope.QueueName}"); - destination.Service.Type.Should().Be(ApiConstants.TypeMessaging); span.Context.Message.Should().NotBeNull(); span.Context.Message.Queue.Should().NotBeNull(); @@ -164,9 +158,7 @@ await sender.ScheduleMessageAsync( var destination = span.Context.Destination; destination.Address.Should().Be(_environment.ServiceBusConnectionStringProperties.FullyQualifiedNamespace); - destination.Service.Name.Should().Be(ServiceBus.SubType); destination.Service.Resource.Should().Be($"{ServiceBus.SubType}/{scope.TopicName}"); - destination.Service.Type.Should().Be(ApiConstants.TypeMessaging); span.Context.Message.Should().NotBeNull(); span.Context.Message.Queue.Should().NotBeNull(); diff --git a/test/Elastic.Apm.Azure.ServiceBus.Tests/MicrosoftAzureServiceBusDiagnosticListenerTests.cs b/test/Elastic.Apm.Azure.ServiceBus.Tests/MicrosoftAzureServiceBusDiagnosticListenerTests.cs index b6567ce59..519c16f2d 100644 --- a/test/Elastic.Apm.Azure.ServiceBus.Tests/MicrosoftAzureServiceBusDiagnosticListenerTests.cs +++ b/test/Elastic.Apm.Azure.ServiceBus.Tests/MicrosoftAzureServiceBusDiagnosticListenerTests.cs @@ -62,9 +62,7 @@ await _agent.Tracer.CaptureTransaction("Send AzureServiceBus Message", "message" var destination = span.Context.Destination; destination.Address.Should().Be($"sb://{_environment.ServiceBusConnectionStringProperties.FullyQualifiedNamespace}/"); - destination.Service.Name.Should().Be(ServiceBus.SubType); destination.Service.Resource.Should().Be($"{ServiceBus.SubType}/{scope.QueueName}"); - destination.Service.Type.Should().Be(ApiConstants.TypeMessaging); span.Context.Message.Should().NotBeNull(); span.Context.Message.Queue.Should().NotBeNull(); @@ -95,9 +93,7 @@ await _agent.Tracer.CaptureTransaction("Send AzureServiceBus Message", "message" var destination = span.Context.Destination; destination.Address.Should().Be($"sb://{_environment.ServiceBusConnectionStringProperties.FullyQualifiedNamespace}/"); - destination.Service.Name.Should().Be(ServiceBus.SubType); destination.Service.Resource.Should().Be($"{ServiceBus.SubType}/{scope.TopicName}"); - destination.Service.Type.Should().Be(ApiConstants.TypeMessaging); span.Context.Message.Should().NotBeNull(); span.Context.Message.Queue.Should().NotBeNull(); @@ -130,9 +126,7 @@ await sender.ScheduleMessageAsync( var destination = span.Context.Destination; destination.Address.Should().Be($"sb://{_environment.ServiceBusConnectionStringProperties.FullyQualifiedNamespace}/"); - destination.Service.Name.Should().Be(ServiceBus.SubType); destination.Service.Resource.Should().Be($"{ServiceBus.SubType}/{scope.QueueName}"); - destination.Service.Type.Should().Be(ApiConstants.TypeMessaging); span.Context.Message.Should().NotBeNull(); span.Context.Message.Queue.Should().NotBeNull(); @@ -165,9 +159,7 @@ await sender.ScheduleMessageAsync( var destination = span.Context.Destination; destination.Address.Should().Be($"sb://{_environment.ServiceBusConnectionStringProperties.FullyQualifiedNamespace}/"); - destination.Service.Name.Should().Be(ServiceBus.SubType); destination.Service.Resource.Should().Be($"{ServiceBus.SubType}/{scope.TopicName}"); - destination.Service.Type.Should().Be(ApiConstants.TypeMessaging); span.Context.Message.Should().NotBeNull(); span.Context.Message.Queue.Should().NotBeNull();