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:
parent
d57a931b21
commit
a3f1c98bc5
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue