Support multiple spans (activities) within the same Trace for StackExchangeRedis (#1153)

* - Add support multiple spans (activities) within the same TraceId.
- Use C# value tupples in cache keys and values to reduce heap allocations and ensure proper key comparison.
- Value names in C# tupples to make code slightly easier to read.

* Fix code formatting error

Fixing the code formatting issue being reported by code scanner.

* Added Unit test for multi-span in Redis

* fixed formatting that stylecop didn't like

Co-authored-by: Cijo Thomas <cithomas@microsoft.com>
This commit is contained in:
Kelly Birr 2020-09-03 11:56:59 -07:00 committed by GitHub
parent d57a931b21
commit a3f1c98bc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 65 additions and 8 deletions

View File

@ -41,7 +41,9 @@ namespace OpenTelemetry.Instrumentation.StackExchangeRedis
private readonly Thread drainThread;
private readonly ProfilingSession defaultSession = new ProfilingSession();
private readonly ConcurrentDictionary<ActivityTraceId, Tuple<Activity, ProfilingSession>> cache = new ConcurrentDictionary<ActivityTraceId, Tuple<Activity, ProfilingSession>>();
private readonly ConcurrentDictionary<(ActivityTraceId TraceId, ActivitySpanId SpanId), (Activity Activity, ProfilingSession Session)> cache
= new ConcurrentDictionary<(ActivityTraceId, ActivitySpanId), (Activity, ProfilingSession)>();
/// <summary>
/// Initializes a new instance of the <see cref="StackExchangeRedisCallsInstrumentation"/> class.
@ -87,14 +89,15 @@ namespace OpenTelemetry.Instrumentation.StackExchangeRedis
return this.defaultSession;
}
// Try to reuse a session for all activities created under the same TraceId.
if (!this.cache.TryGetValue(parent.TraceId, out var session))
// Try to reuse a session for all activities created under the same TraceId+SpanId.
var cacheKey = (parent.TraceId, parent.SpanId);
if (!this.cache.TryGetValue(cacheKey, out var session))
{
session = new Tuple<Activity, ProfilingSession>(parent, new ProfilingSession());
this.cache.TryAdd(parent.TraceId, session);
session = (parent, new ProfilingSession());
this.cache.TryAdd(cacheKey, session);
}
return session.Item2;
return session.Session;
};
}
@ -128,14 +131,15 @@ namespace OpenTelemetry.Instrumentation.StackExchangeRedis
foreach (var entry in this.cache)
{
var parent = entry.Value.Item1;
var parent = entry.Value.Activity;
if (parent.Duration == TimeSpan.Zero)
{
// Activity is still running, don't drain.
continue;
}
RedisProfilerEntryToActivityConverter.DrainSession(parent, entry.Value.Item2.FinishProfiling());
ProfilingSession session = entry.Value.Session;
RedisProfilerEntryToActivityConverter.DrainSession(parent, session.FinishProfiling());
}
}
}

View File

@ -101,6 +101,59 @@ namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Tests
Assert.Equal(second, third);
}
[Fact]
public async Task ProfilerSessionsHandleMultipleSpans()
{
var connectionOptions = new ConfigurationOptions
{
AbortOnConnectFail = false,
};
connectionOptions.EndPoints.Add("localhost:6379");
var connection = ConnectionMultiplexer.Connect(connectionOptions);
using var instrumentation = new StackExchangeRedisCallsInstrumentation(connection, new StackExchangeRedisCallsInstrumentationOptions());
var profilerFactory = instrumentation.GetProfilerSessionsFactory();
// start a root level activity
using Activity rootActivity = new Activity("Parent")
.SetParentId(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded)
.Start();
Assert.NotNull(rootActivity.Id);
// get an initial profiler from root activity
Activity.Current = rootActivity;
ProfilingSession profiler0 = profilerFactory();
// expect different result from synchronous child activity
ProfilingSession profiler1;
using (Activity.Current = new Activity("Child-Span-1").SetParentId(rootActivity.Id).Start())
{
profiler1 = profilerFactory();
Assert.NotSame(profiler0, profiler1);
}
Activity.Current = rootActivity;
// expect different result from asynchronous child activity
using (Activity.Current = new Activity("Child-Span-2").SetParentId(rootActivity.Id).Start())
{
// lose async context on purpose
await Task.Delay(100).ConfigureAwait(false);
ProfilingSession profiler2 = profilerFactory();
Assert.NotSame(profiler0, profiler2);
Assert.NotSame(profiler1, profiler2);
}
Activity.Current = rootActivity;
// ensure same result back in root activity
ProfilingSession profiles3 = profilerFactory();
Assert.Same(profiler0, profiles3);
}
[Fact]
public void StackExchangeRedis_BadArgs()
{