Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement DroppedSpanStats #1511

Merged
merged 16 commits into from
Nov 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,16 @@ public async Task<ActionResult> Bulk(IEnumerable<CreateSampleDataViewModel> 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<SampleData>().AddRange(sampleData);
context.Set<SampleData>().Add(aggregatedModel);
changes = await context.SaveChangesAsync();
transaction.Commit();
}

return Json(new { success = true, changes });
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -252,9 +252,7 @@ private void OnSendStart(KeyValuePair<string, object> 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}"
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -236,9 +236,7 @@ private void OnSendStart(KeyValuePair<string, object> 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}"
}
};

Expand Down
2 changes: 1 addition & 1 deletion src/Elastic.Apm.GrpcClient/GrpcClientDiagnosticListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ protected override void HandleOnNext(KeyValuePair<string, object> 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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,10 @@ private void ProcessStartEvent(TRequest request, Uri requestUrl)
if (span is Span realSpan)
{
if (!realSpan.ShouldBeSentToApmServer)
{
realSpan.ServiceResource = UrlUtils.ExtractService(requestUrl, realSpan);
return;
}
}

span.Context.Http = new Http { Method = method };
Expand Down
3 changes: 1 addition & 2 deletions src/Elastic.Apm/Helpers/UrlUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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}";
}
}
6 changes: 3 additions & 3 deletions src/Elastic.Apm/Model/DbSpanCommon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ internal void EndSpan(ISpan span, IDbCommand dbCommand, Outcome outcome, TimeSpa
{
capturedSpan.Context.Db = new Database
{
Statement = GetDbSpanName(dbCommand),
Instance = dbCommand.Connection.Database,
Type = Database.TypeSql
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;
}
Expand Down
52 changes: 52 additions & 0 deletions src/Elastic.Apm/Model/DroppedSpanStats.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// DroppedSpanStats holds information about spans that were dropped (for example due to transaction_max_spans or
/// exit_span_min_duration).
/// </summary>
internal class DroppedSpanStats
{
public DroppedSpanStats(string destinationServiceResource, Outcome outcome, double durationSumUs)
{
DurationCount = 1;
DestinationServiceResource = destinationServiceResource;
Outcome = outcome;
DurationSumUs = durationSumUs;
}

/// <summary>
/// DestinationServiceResource identifies the destination service resource being operated on. e.g. 'http://elastic.co:80',
/// 'elasticsearch', 'rabbitmq/queue_name'.
/// </summary>
[JsonProperty("destination_service_resource")]
public string DestinationServiceResource { get; }

/// <summary>
/// Duration holds duration aggregations about the dropped span.
/// Count holds the number of times the dropped span happened.
/// </summary>
[JsonProperty("duration.count")]
public int DurationCount { get; set; }


/// <summary>
/// Duration holds duration aggregations about the dropped span.
/// Sum holds dimensions about the dropped span's duration.
/// </summary>
[JsonProperty("duration.sum.us")]
public double DurationSumUs { get; set; }

/// <summary>
/// Outcome of the aggregated spans.
/// </summary>
public Outcome Outcome { get; }
}
}
38 changes: 28 additions & 10 deletions src/Elastic.Apm/Model/Span.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ public Span(
[MaxLength]
public string Action { get; set; }

/// <summary>
/// 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.
/// </summary>
[JsonIgnore]
public string ServiceResource { get; set; }

[JsonIgnore]
internal IConfiguration Configuration => _enclosingTransaction.Configuration;

Expand Down Expand Up @@ -362,17 +370,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 && (!string.IsNullOrEmpty(ServiceResource) || (_context.IsValueCreated && Context?.Destination?.Service?.Resource != null)))
_enclosingTransaction.UpdateDroppedSpanStats(ServiceResource ?? 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
Expand Down Expand Up @@ -455,6 +466,13 @@ private void DeduceDestination()
if (!IsExitSpan)
return;

if (!_context.IsValueCreated)
{
if(string.IsNullOrEmpty(ServiceResource))
ServiceResource = !string.IsNullOrEmpty(Subtype) ? Subtype : Type;
return;
}

if (Context.Http != null)
{
var destination = DeduceHttpDestination();
Expand Down Expand Up @@ -489,7 +507,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;
}
Expand Down
Loading