From 4a61d1083a9d0edd34a5a6624e3f3d7a63bda23e Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Mon, 10 Aug 2020 09:27:24 -0700 Subject: [PATCH] System.Diagnostics Tracing APIs additions (#40534) --- .../System.Diagnostics.DiagnosticSourceActivity.cs | 11 +- .../src/System.Diagnostics.DiagnosticSource.csproj | 2 +- .../src/System/Diagnostics/Activity.cs | 46 +++- .../System/Diagnostics/ActivityCreationOptions.cs | 59 +++++ .../src/System/Diagnostics/ActivityListener.cs | 14 +- ...ityDataRequest.cs => ActivitySamplingResult.cs} | 2 +- .../src/System/Diagnostics/ActivitySource.cs | 133 +++++----- .../tests/ActivitySourceTests.cs | 277 ++++++++++++++++++--- 8 files changed, 419 insertions(+), 125 deletions(-) rename src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/{ActivityDataRequest.cs => ActivitySamplingResult.cs} (97%) diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs index 8e0f944..962b663 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs @@ -179,7 +179,7 @@ namespace System.Diagnostics public System.Diagnostics.Activity StartActivity(System.Diagnostics.Activity activity, object? args) { throw null; } public void StopActivity(System.Diagnostics.Activity activity, object? args) { } } - public enum ActivityDataRequest + public enum ActivitySamplingResult { None, PropagationData, @@ -237,17 +237,18 @@ namespace System.Diagnostics public T Parent { get { throw null; } } public System.Collections.Generic.IEnumerable> Tags { get { throw null; } } public System.Collections.Generic.IEnumerable Links { get { throw null; } } + public System.Diagnostics.ActivityTagsCollection SamplingTags { get { throw null; } } + public System.Diagnostics.ActivityTraceId TraceId { get { throw null; } } } - public delegate System.Diagnostics.ActivityDataRequest GetRequestedData(ref System.Diagnostics.ActivityCreationOptions options); + public delegate System.Diagnostics.ActivitySamplingResult SampleActivity(ref System.Diagnostics.ActivityCreationOptions options); public sealed class ActivityListener : IDisposable { public ActivityListener() { throw null; } public System.Action? ActivityStarted { get { throw null; } set { throw null; } } public System.Action? ActivityStopped { get { throw null; } set { throw null; } } public System.Func? ShouldListenTo { get { throw null; } set { throw null; } } - public System.Diagnostics.GetRequestedData? GetRequestedDataUsingParentId { get { throw null; } set { throw null; } } - public System.Diagnostics.GetRequestedData? GetRequestedDataUsingContext { get { throw null; } set { throw null; } } - public bool AutoGenerateRootContextTraceId { get { throw null; } set { throw null; } } + public System.Diagnostics.SampleActivity? SampleUsingParentId { get { throw null; } set { throw null; } } + public System.Diagnostics.SampleActivity? Sample { get { throw null; } set { throw null; } } public void Dispose() { throw null; } } } diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj index 275bebe..d8eaff0 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj @@ -38,7 +38,7 @@ - + diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs index 2383495..a97c222 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs @@ -929,7 +929,7 @@ namespace System.Diagnostics internal static Activity CreateAndStart(ActivitySource source, string name, ActivityKind kind, string? parentId, ActivityContext parentContext, IEnumerable>? tags, IEnumerable? links, - DateTimeOffset startTime, ActivityDataRequest request) + DateTimeOffset startTime, ActivityTagsCollection? samplerTags, ActivitySamplingResult request) { Activity activity = new Activity(name); @@ -994,11 +994,23 @@ namespace System.Diagnostics } } + if (samplerTags != null) + { + if (activity._tags == null) + { + activity._tags = new TagsLinkedList(samplerTags!); + } + else + { + activity._tags.Add(samplerTags!); + } + } + activity.StartTimeUtc = startTime == default ? DateTime.UtcNow : startTime.UtcDateTime; - activity.IsAllDataRequested = request == ActivityDataRequest.AllData || request == ActivityDataRequest.AllDataAndRecorded; + activity.IsAllDataRequested = request == ActivitySamplingResult.AllData || request == ActivitySamplingResult.AllDataAndRecorded; - if (request == ActivityDataRequest.AllDataAndRecorded) + if (request == ActivitySamplingResult.AllDataAndRecorded) { activity.ActivityTraceFlags |= ActivityTraceFlags.Recorded; } @@ -1295,6 +1307,34 @@ namespace System.Diagnostics } } + public TagsLinkedList(IEnumerable> list) => Add(list); + + // Add doesn't take the lock because it is called from the Activity creation before sharing the activity object to the caller. + public void Add(IEnumerable> list) + { + IEnumerator> e = list.GetEnumerator(); + if (!e.MoveNext()) + { + return; + } + + if (_first == null) + { + _last = _first = new LinkedListNode>(e.Current); + } + else + { + _last!.Next = new LinkedListNode>(e.Current); + _last = _last.Next; + } + + while (e.MoveNext()) + { + _last.Next = new LinkedListNode>(e.Current); + _last = _last.Next; + } + } + public void Add(KeyValuePair value) { LinkedListNode> newNode = new LinkedListNode>(value); diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityCreationOptions.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityCreationOptions.cs index 5d48459..a578542 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityCreationOptions.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityCreationOptions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace System.Diagnostics { @@ -10,6 +11,9 @@ namespace System.Diagnostics /// public readonly struct ActivityCreationOptions { + private readonly ActivityTagsCollection? _samplerTags; + private readonly ActivityContext _context; + /// /// Construct a new object. /// @@ -27,6 +31,22 @@ namespace System.Diagnostics Parent = parent; Tags = tags; Links = links; + + _samplerTags = null; + + if (parent is ActivityContext ac) + { + _context = ac; + } + else if (parent is string p && p != null) + { + // We don't care about the return value. we care if _context is initialized accordingly. + ActivityContext.TryParse(p, null, out _context); + } + else + { + _context = default; + } } /// @@ -58,5 +78,44 @@ namespace System.Diagnostics /// Retrieve the list of which requested to create the Activity object with. /// public IEnumerable? Links { get; } + + public ActivityTagsCollection SamplingTags + { +#if ALLOW_PARTIALLY_TRUSTED_CALLERS + [System.Security.SecuritySafeCriticalAttribute] +#endif + get + { + if (_samplerTags == null) + { + // Because the struct is readonly, we cannot directly assign _samplerTags. We have to workaround it by calling Unsafe.AsRef + Unsafe.AsRef(in _samplerTags) = new ActivityTagsCollection(); + } + + return _samplerTags!; + } + } + + public ActivityTraceId TraceId + { +#if ALLOW_PARTIALLY_TRUSTED_CALLERS + [System.Security.SecuritySafeCriticalAttribute] +#endif + get + { + if (Parent is ActivityContext && _context == default) + { + // Because the struct is readonly, we cannot directly assign _context. We have to workaround it by calling Unsafe.AsRef + Unsafe.AsRef(in _context) = new ActivityContext(ActivityTraceId.CreateRandom(), default, ActivityTraceFlags.None); + } + + return _context.TraceId; + } + } + + // Helper to access the sampling tags. The SamplingTags Getter can allocate when not necessary. + internal ActivityTagsCollection? GetSamplingTags() => _samplerTags; + + internal ActivityContext GetContext() => _context; } } diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityListener.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityListener.cs index 72ff9b2..2ad3850 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityListener.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityListener.cs @@ -8,7 +8,7 @@ namespace System.Diagnostics /// /// Define the callback that can be used in to allow deciding to create the Activity objects and with what data state. /// - public delegate ActivityDataRequest GetRequestedData(ref ActivityCreationOptions options); + public delegate ActivitySamplingResult SampleActivity(ref ActivityCreationOptions options); /// /// ActivityListener allows listening to the start and stop Activity events and give the oppertunity to decide creating the Activity for sampling scenarios. @@ -40,20 +40,12 @@ namespace System.Diagnostics /// /// Set or get the callback used to decide allowing creating objects with specific data state. /// - public GetRequestedData? GetRequestedDataUsingParentId { get; set; } + public SampleActivity? SampleUsingParentId { get; set; } /// /// Set or get the callback used to decide allowing creating objects with specific data state. /// - public GetRequestedData? GetRequestedDataUsingContext { get; set; } - - /// - /// Determine if the listener automatically generates a new trace Id before sampling when there is no parent context. - /// - /// - /// If this property is set to true and caused generating a new trace Id, the created object from such call will have the same generated trace Id. - /// - public bool AutoGenerateRootContextTraceId { get; set;} + public SampleActivity? Sample { get; set; } /// /// Dispose will unregister this object from listeneing to events. diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityDataRequest.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySamplingResult.cs similarity index 97% rename from src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityDataRequest.cs rename to src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySamplingResult.cs index a4c03ff..19bee5c 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityDataRequest.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySamplingResult.cs @@ -7,7 +7,7 @@ namespace System.Diagnostics /// Used by ActivityListener to indicate what amount of data should be collected for this Activity /// Requesting more data causes greater performance overhead to collect it. /// - public enum ActivityDataRequest + public enum ActivitySamplingResult { /// /// The Activity object doesn't need to be created diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs index ac110fd..d1b4d63 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs @@ -113,96 +113,96 @@ namespace System.Diagnostics } Activity? activity = null; + ActivityTagsCollection? samplerTags; - ActivityDataRequest dataRequest = ActivityDataRequest.None; - bool? useContext = default; - ActivityCreationOptions optionsWithContext = default; + ActivitySamplingResult samplingResult = ActivitySamplingResult.None; if (parentId != null) { var aco = new ActivityCreationOptions(this, name, parentId, kind, tags, links); - listeners.EnumWithFunc((ActivityListener listener, ref ActivityCreationOptions data, ref ActivityDataRequest request, ref bool? canUseContext, ref ActivityCreationOptions dataWithContext) => { - GetRequestedData? getRequestedDataUsingParentId = listener.GetRequestedDataUsingParentId; - if (getRequestedDataUsingParentId != null) + var acoContext = new ActivityCreationOptions(this, name, aco.GetContext(), kind, tags, links); + + listeners.EnumWithFunc((ActivityListener listener, ref ActivityCreationOptions data, ref ActivitySamplingResult result, ref ActivityCreationOptions dataWithContext) => { + SampleActivity? sampleUsingParentId = listener.SampleUsingParentId; + if (sampleUsingParentId != null) { - ActivityDataRequest dr = getRequestedDataUsingParentId(ref data); - if (dr > request) + ActivitySamplingResult sr = sampleUsingParentId(ref data); + if (sr > result) { - request = dr; + result = sr; } - - // Stop the enumeration if we get the max value RecordingAndSampling. - return request != ActivityDataRequest.AllDataAndRecorded; } else { - // In case we have a parent Id and the listener not providing the GetRequestedDataUsingParentId, we'll try to find out if the following conditions are true: - // - The listener is providing the GetRequestedDataUsingContext callback - // - Can convert the parent Id to a Context - // Then we can call the listener GetRequestedDataUsingContext callback with the constructed context. - GetRequestedData? getRequestedDataUsingContext = listener.GetRequestedDataUsingContext; - if (getRequestedDataUsingContext != null) + // In case we have a parent Id and the listener not providing the SampleUsingParentId, we'll try to find out if the following conditions are true: + // - The listener is providing the Sample callback + // - Can convert the parent Id to a Context. ActivityCreationOptions.TraceId != default means parent id converted to a valid context. + // Then we can call the listener Sample callback with the constructed context. + SampleActivity? sample = listener.Sample; + if (sample != null && data.GetContext() != default) // data.GetContext() != default means parent Id parsed correctly to a context { - if (!canUseContext.HasValue) + ActivitySamplingResult sr = sample(ref dataWithContext); + if (sr > result) { - canUseContext = Activity.TryConvertIdToContext(parentId, traceState: null, out ActivityContext ctx); - if (canUseContext.Value) - { - dataWithContext = new ActivityCreationOptions(data.Source, data.Name, ctx, data.Kind, data.Tags, data.Links); - } + result = sr; } + } + } + }, ref aco, ref samplingResult, ref acoContext); - if (canUseContext.Value) - { - ActivityDataRequest dr = getRequestedDataUsingContext(ref dataWithContext); - if (dr > request) - { - request = dr; - } - // Stop the enumeration if we get the max value RecordingAndSampling. - return request != ActivityDataRequest.AllDataAndRecorded; - } + if (context == default && aco.GetContext() != default) + { + context = aco.GetContext(); + parentId = null; + } + + samplerTags = aco.GetSamplingTags(); + ActivityTagsCollection? atc = acoContext.GetSamplingTags(); + if (atc != null) + { + if (samplerTags == null) + { + samplerTags = atc; + } + else + { + foreach (KeyValuePair tag in atc) + { + samplerTags.Add(tag); } } - return true; - }, ref aco, ref dataRequest, ref useContext, ref optionsWithContext); + } } else { - ActivityContext initializedContext = context == default && Activity.Current != null ? Activity.Current.Context : context; - optionsWithContext = new ActivityCreationOptions(this, name, initializedContext, kind, tags, links); - listeners.EnumWithFunc((ActivityListener listener, ref ActivityCreationOptions data, ref ActivityDataRequest request, ref bool? canUseContext, ref ActivityCreationOptions dataWithContext) => { - GetRequestedData? getRequestedDataUsingContext = listener.GetRequestedDataUsingContext; - if (getRequestedDataUsingContext != null) + bool useCurrentActivityContext = context == default && Activity.Current != null; + var aco = new ActivityCreationOptions(this, name, useCurrentActivityContext ? Activity.Current!.Context : context, kind, tags, links); + listeners.EnumWithFunc((ActivityListener listener, ref ActivityCreationOptions data, ref ActivitySamplingResult result, ref ActivityCreationOptions unused) => { + SampleActivity? sample = listener.Sample; + if (sample != null) { - if (listener.AutoGenerateRootContextTraceId && !canUseContext.HasValue && data.Parent == default) - { - ActivityContext ctx = new ActivityContext(ActivityTraceId.CreateRandom(), default, default); - dataWithContext = new ActivityCreationOptions(data.Source, data.Name, ctx, data.Kind, data.Tags, data.Links); - canUseContext = true; - } - ActivityDataRequest dr = getRequestedDataUsingContext(ref data); - if (dr > request) + ActivitySamplingResult dr = sample(ref data); + if (dr > result) { - request = dr; + result = dr; } - - // Stop the enumeration if we get the max value RecordingAndSampling. - return request != ActivityDataRequest.AllDataAndRecorded; } - return true; - }, ref optionsWithContext, ref dataRequest, ref useContext, ref optionsWithContext); - } + }, ref aco, ref samplingResult, ref aco); - if (dataRequest != ActivityDataRequest.None) - { - if (useContext.HasValue && useContext.Value) + if (!useCurrentActivityContext) { - context = optionsWithContext.Parent; - parentId = null; + // We use the context stored inside ActivityCreationOptions as it is possible the trace id get automatically generated during the sampling. + // We don't use the context stored inside ActivityCreationOptions only in case if we used Activity.Current context, the reason is we need to + // create the new child activity with Parent set to Activity.Current. + context = aco.GetContext(); } - activity = Activity.CreateAndStart(this, name, kind, parentId, context, tags, links, startTime, dataRequest); + samplerTags = aco.GetSamplingTags(); + } + + if (samplingResult != ActivitySamplingResult.None) + { + activity = Activity.CreateAndStart(this, name, kind, parentId, context, tags, links, startTime, samplerTags, samplingResult); listeners.EnumWithAction((listener, obj) => listener.ActivityStarted?.Invoke((Activity) obj), activity); } @@ -241,7 +241,7 @@ namespace System.Diagnostics } } - internal delegate bool Function(T item, ref ActivityCreationOptions data, ref ActivityDataRequest dataRequest, ref bool? ctxInitialized, ref ActivityCreationOptions dataWithContext); + internal delegate void Function(T item, ref ActivityCreationOptions data, ref ActivitySamplingResult samplingResult, ref ActivityCreationOptions dataWithContext); internal void AddListener(ActivityListener listener) { @@ -333,7 +333,7 @@ namespace System.Diagnostics public int Count => _list.Count; - public void EnumWithFunc(ActivitySource.Function func, ref ActivityCreationOptions data, ref ActivityDataRequest dataRequest, ref bool? ctxInitialized, ref ActivityCreationOptions dataWithContext) + public void EnumWithFunc(ActivitySource.Function func, ref ActivityCreationOptions data, ref ActivitySamplingResult samplingResult, ref ActivityCreationOptions dataWithContext) { uint version = _version; int index = 0; @@ -356,10 +356,7 @@ namespace System.Diagnostics // Important to call the func outside the lock. // This is the whole point we are having this wrapper class. - if (!func(item, ref data, ref dataRequest, ref ctxInitialized, ref dataWithContext)) - { - break; - } + func(item, ref data, ref samplingResult, ref dataWithContext); } } diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivitySourceTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivitySourceTests.cs index 99bafcd..84d3935 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivitySourceTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivitySourceTests.cs @@ -60,8 +60,8 @@ namespace System.Diagnostics.Tests listener.ActivityStarted = activity => Assert.NotNull(activity); listener.ActivityStopped = activity => Assert.NotNull(activity); listener.ShouldListenTo = (activitySource) => object.ReferenceEquals(aSource, activitySource); - listener.GetRequestedDataUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.None; - listener.GetRequestedDataUsingContext = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.None; + listener.SampleUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.None; + listener.Sample = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.None; ActivitySource.AddActivityListener(listener); Assert.True(aSource.HasListeners()); @@ -84,8 +84,8 @@ namespace System.Diagnostics.Tests listener.ActivityStarted = activity => counter++; listener.ActivityStopped = activity => counter--; listener.ShouldListenTo = (activitySource) => object.ReferenceEquals(aSource, activitySource); - listener.GetRequestedDataUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.AllDataAndRecorded; - listener.GetRequestedDataUsingContext = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.AllDataAndRecorded; + listener.SampleUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.AllDataAndRecorded; + listener.Sample = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.AllDataAndRecorded; ActivitySource.AddActivityListener(listener); @@ -141,8 +141,8 @@ namespace System.Diagnostics.Tests listener.ActivityStarted = activity => Assert.NotNull(activity); listener.ActivityStopped = activity => Assert.NotNull(activity); listener.ShouldListenTo = (activitySource) => object.ReferenceEquals(aSource, activitySource); - listener.GetRequestedDataUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.AllData; - listener.GetRequestedDataUsingContext = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.AllData; + listener.SampleUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.AllData; + listener.Sample = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.AllData; ActivitySource.AddActivityListener(listener); @@ -163,8 +163,8 @@ namespace System.Diagnostics.Tests listener.ActivityStarted = activity => activityStartCount++; listener.ActivityStopped = activity => activityStopCount++; listener.ShouldListenTo = (activitySource) => activitySource.Name == "" && activitySource.Version == ""; - listener.GetRequestedDataUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.AllData; - listener.GetRequestedDataUsingContext = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.AllData; + listener.SampleUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.AllData; + listener.Sample = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.AllData; ActivitySource.AddActivityListener(listener); @@ -213,8 +213,8 @@ namespace System.Diagnostics.Tests ActivityStarted = (activity) => { activityStartCount++; Assert.NotNull(activity); }, ActivityStopped = (activity) => { activityStopCount++; Assert.NotNull(activity); }, ShouldListenTo = (activitySource) => true, - GetRequestedDataUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.None, - GetRequestedDataUsingContext = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.None + SampleUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.None, + Sample = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.None }; ActivitySource.AddActivityListener(listeners[0]); @@ -228,8 +228,8 @@ namespace System.Diagnostics.Tests ActivityStarted = (activity) => { activityStartCount++; Assert.NotNull(activity); }, ActivityStopped = (activity) => { activityStopCount++; Assert.NotNull(activity); }, ShouldListenTo = (activitySource) => true, - GetRequestedDataUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.PropagationData, - GetRequestedDataUsingContext = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.PropagationData + SampleUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.PropagationData, + Sample = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.PropagationData }; ActivitySource.AddActivityListener(listeners[1]); @@ -250,8 +250,8 @@ namespace System.Diagnostics.Tests ActivityStarted = (activity) => { activityStartCount++; Assert.NotNull(activity); }, ActivityStopped = (activity) => { activityStopCount++; Assert.NotNull(activity); }, ShouldListenTo = (activitySource) => true, - GetRequestedDataUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.AllData, - GetRequestedDataUsingContext = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.AllData + SampleUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.AllData, + Sample = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.AllData }; ActivitySource.AddActivityListener(listeners[2]); @@ -272,8 +272,8 @@ namespace System.Diagnostics.Tests ActivityStarted = (activity) => { activityStartCount++; Assert.NotNull(activity); }, ActivityStopped = (activity) => { activityStopCount++; Assert.NotNull(activity); }, ShouldListenTo = (activitySource) => true, - GetRequestedDataUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.AllDataAndRecorded, - GetRequestedDataUsingContext = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.AllDataAndRecorded + SampleUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.AllDataAndRecorded, + Sample = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.AllDataAndRecorded }; ActivitySource.AddActivityListener(listeners[3]); @@ -307,8 +307,8 @@ namespace System.Diagnostics.Tests listener.ActivityStarted = activity => Assert.NotNull(activity); listener.ActivityStopped = activity => Assert.NotNull(activity); listener.ShouldListenTo = (activitySource) => true; - listener.GetRequestedDataUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.AllData; - listener.GetRequestedDataUsingContext = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.AllData; + listener.SampleUsingParentId = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.AllData; + listener.Sample = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.AllData; ActivitySource.AddActivityListener(listener); @@ -363,7 +363,7 @@ namespace System.Diagnostics.Tests using ActivityListener listener = new ActivityListener(); listener.ShouldListenTo = (activitySource) => activitySource.Name == "ParentContext"; - listener.GetRequestedDataUsingContext = (ref ActivityCreationOptions activityOptions) => + listener.Sample = (ref ActivityCreationOptions activityOptions) => { Activity c = Activity.Current; if (c != null) @@ -371,11 +371,10 @@ namespace System.Diagnostics.Tests Assert.Equal(c.Context, activityOptions.Parent); } - return ActivityDataRequest.AllData; + return ActivitySamplingResult.AllData; }; ActivitySource.AddActivityListener(listener); - using Activity a = aSource.StartActivity("a", ActivityKind.Server, new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), 0)); using Activity b = aSource.StartActivity("b"); Assert.Equal(a.Context, b.Parent.Context); @@ -426,19 +425,19 @@ namespace System.Diagnostics.Tests using ActivityListener listener3 = new ActivityListener(); // will have both context and parent Id callbacks listener1.ShouldListenTo = listener2.ShouldListenTo = listener3.ShouldListenTo = (activitySource) => activitySource.Name == "ParentIdsTest"; - listener1.GetRequestedDataUsingContext = listener3.GetRequestedDataUsingContext = (ref ActivityCreationOptions activityOptions) => + listener1.Sample = listener3.Sample = (ref ActivityCreationOptions activityOptions) => { callingByContext++; Assert.Equal(new ActivityContext(ActivityTraceId.CreateFromString(w3cId.AsSpan(3, 32)), ActivitySpanId.CreateFromString(w3cId.AsSpan(36, 16)), ActivityTraceFlags.Recorded), activityOptions.Parent); - return ActivityDataRequest.AllData; + return ActivitySamplingResult.AllData; }; - listener2.GetRequestedDataUsingParentId = listener3.GetRequestedDataUsingParentId = (ref ActivityCreationOptions activityOptions) => + listener2.SampleUsingParentId = listener3.SampleUsingParentId = (ref ActivityCreationOptions activityOptions) => { callingByParentId++; - return ActivityDataRequest.AllData; + return ActivitySamplingResult.AllData; }; ActivitySource.AddActivityListener(listener1); @@ -476,10 +475,10 @@ namespace System.Diagnostics.Tests using ActivityListener listener = new ActivityListener(); listener.ShouldListenTo = (activitySource) => activitySource.Name == "RemoteContext"; - listener.GetRequestedDataUsingContext = (ref ActivityCreationOptions activityOptions) => + listener.Sample = (ref ActivityCreationOptions activityOptions) => { isRemote = activityOptions.Parent.IsRemote; - return ActivityDataRequest.AllData; + return ActivitySamplingResult.AllData; }; ActivitySource.AddActivityListener(listener); @@ -506,10 +505,12 @@ namespace System.Diagnostics.Tests ActivityContext ctx = default; - listener.GetRequestedDataUsingContext = (ref ActivityCreationOptions activityOptions) => + bool forceGenerateTraceId = false; + + listener.Sample = (ref ActivityCreationOptions activityOptions) => { - ctx = activityOptions.Parent; - return ActivityDataRequest.AllData; + ctx = forceGenerateTraceId ? new ActivityContext(activityOptions.TraceId, default, default) : activityOptions.Parent; + return ActivitySamplingResult.AllData; }; ActivitySource.AddActivityListener(listener); @@ -519,7 +520,7 @@ namespace System.Diagnostics.Tests Assert.Equal(default, ctx); } - listener.AutoGenerateRootContextTraceId = true; + forceGenerateTraceId = true; Activity activity = aSource.StartActivity("a2", default, ctx); @@ -542,13 +543,12 @@ namespace System.Diagnostics.Tests ActivityContext ctx = default; - listener.GetRequestedDataUsingContext = (ref ActivityCreationOptions activityOptions) => + listener.Sample = (ref ActivityCreationOptions activityOptions) => { - ctx = activityOptions.Parent; - return ActivityDataRequest.AllData; + ctx = new ActivityContext(activityOptions.TraceId, default, default); + return ActivitySamplingResult.AllData; }; - listener.AutoGenerateRootContextTraceId = true; ActivitySource.AddActivityListener(listener); Activity activity = aSource.StartActivity("a2", default, null); @@ -573,7 +573,7 @@ namespace System.Diagnostics.Tests using ActivityListener listener = new ActivityListener(); listener.ShouldListenTo = (activitySource) => activitySource.Name == "EventNotificationOrder"; - listener.GetRequestedDataUsingContext = (ref ActivityCreationOptions activityOptions) => ActivityDataRequest.AllData; + listener.Sample = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.AllData; listener.ActivityStopped = a => Assert.Equal(child, Activity.Current); ActivitySource.AddActivityListener(listener); @@ -590,6 +590,211 @@ namespace System.Diagnostics.Tests }).Dispose(); } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void TestAddingSamplerTags() + { + RemoteExecutor.Invoke(() => { + + using ActivitySource aSource = new ActivitySource("SamplerTags1"); + using ActivityListener listener = new ActivityListener(); + listener.ShouldListenTo = (activitySource) => activitySource.Name == "SamplerTags1"; + + listener.Sample = (ref ActivityCreationOptions activityOptions) => + { + activityOptions.SamplingTags.Add("tag1", "value1"); + activityOptions.SamplingTags.Add("tag2", "value2"); + return ActivitySamplingResult.AllData; + }; + + ActivitySource.AddActivityListener(listener); + + using Activity activity = aSource.StartActivity("a1"); + + Assert.NotNull(activity); + Assert.Equal(2, activity.TagObjects.Count()); + Assert.Equal(new KeyValuePair("tag1", "value1"), activity.TagObjects.ElementAt(0)); + Assert.Equal(new KeyValuePair("tag2", "value2"), activity.TagObjects.ElementAt(1)); + }).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void TestAddingSamplerTagsUsingParentId() + { + RemoteExecutor.Invoke(() => { + + using ActivitySource aSource = new ActivitySource("SamplerTags2"); + using ActivityListener listener = new ActivityListener(); + listener.ShouldListenTo = (activitySource) => activitySource.Name == "SamplerTags2"; + + listener.SampleUsingParentId = (ref ActivityCreationOptions activityOptions) => + { + activityOptions.SamplingTags.Add("tag1", "value1"); + activityOptions.SamplingTags.Add("tag2", "value2"); + return ActivitySamplingResult.AllData; + }; + + ActivitySource.AddActivityListener(listener); + + Activity activity = aSource.StartActivity("a1", ActivityKind.Client, "SomeParent"); + + Assert.NotNull(activity); + Assert.Equal(2, activity.TagObjects.Count()); + Assert.Equal(new KeyValuePair("tag1", "value1"), activity.TagObjects.ElementAt(0)); + Assert.Equal(new KeyValuePair("tag2", "value2"), activity.TagObjects.ElementAt(1)); + }).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void TestNotAddedSamplerTags() + { + RemoteExecutor.Invoke(() => { + + using ActivitySource aSource = new ActivitySource("SamplerTags3"); + using ActivityListener listener = new ActivityListener(); + listener.ShouldListenTo = (activitySource) => activitySource.Name == "SamplerTags3"; + + listener.Sample = (ref ActivityCreationOptions activityOptions) => + { + activityOptions.SamplingTags.Add("tag1", "value1"); + Assert.False(true, "This callback shouldn't be called at all."); + return ActivitySamplingResult.AllData; + }; + + ActivitySource.AddActivityListener(listener); + + // activity should be null as no listener asked for creation. + using Activity activity = aSource.StartActivity("a1", ActivityKind.Client, "NonW3CParentId"); + + Assert.Null(activity); + }).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void TestDefaultTraceIdWithHierarchicalParentId() + { + RemoteExecutor.Invoke(() => { + + using ActivitySource aSource = new ActivitySource("SamplerTags4"); + using ActivityListener listener = new ActivityListener(); + listener.ShouldListenTo = (activitySource) => activitySource.Name == "SamplerTags4"; + + listener.SampleUsingParentId = (ref ActivityCreationOptions activityOptions) => + { + Assert.Equal(default, activityOptions.TraceId); + return ActivitySamplingResult.AllData; + }; + + ActivitySource.AddActivityListener(listener); + + Activity activity = aSource.StartActivity("a1", ActivityKind.Client, "HierarchicalParentId"); + + Assert.NotNull(activity); + // activity has no parent. expected default trace Id. + Assert.Equal(default, activity.TraceId); + }).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void TestAddingSamplerTagsFromMultipleListeners() + { + RemoteExecutor.Invoke(() => { + using ActivitySource aSource = new ActivitySource("SamplerTags5"); + using ActivityListener listener1 = new ActivityListener(); + using ActivityListener listener2 = new ActivityListener(); + listener1.ShouldListenTo = (activitySource) => activitySource.Name == "SamplerTags5"; + listener2.ShouldListenTo = (activitySource) => activitySource.Name == "SamplerTags5"; + + listener1.Sample = listener2.Sample = (ref ActivityCreationOptions activityOptions) => + { + if (activityOptions.SamplingTags.Count == 0) + activityOptions.SamplingTags.Add("tag1", "value1"); + else + activityOptions.SamplingTags.Add("tag2", "value2"); + + return ActivitySamplingResult.AllData; + }; + + ActivitySource.AddActivityListener(listener1); + ActivitySource.AddActivityListener(listener2); + + using Activity activity = aSource.StartActivity("a1"); + + Assert.NotNull(activity); + Assert.Equal(2, activity.TagObjects.Count()); + Assert.Equal(new KeyValuePair("tag1", "value1"), activity.TagObjects.ElementAt(0)); + Assert.Equal(new KeyValuePair("tag2", "value2"), activity.TagObjects.ElementAt(1)); + }).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void TestSamplerTagsWithMixedListenerModels() + { + RemoteExecutor.Invoke(() => { + const string w3cId = "00-99d43cb30a4cdb4fbeee3a19c29201b0-e82825765f051b47-01"; + + using ActivitySource aSource = new ActivitySource("SamplerTags6"); + using ActivityListener listener1 = new ActivityListener(); + using ActivityListener listener2 = new ActivityListener(); + listener1.ShouldListenTo = (activitySource) => activitySource.Name == "SamplerTags6"; + listener2.ShouldListenTo = (activitySource) => activitySource.Name == "SamplerTags6"; + + // listener1 provide SampleUsingParentId callback and doesn't provide Sample + listener1.SampleUsingParentId = (ref ActivityCreationOptions activityOptions) => + { + activityOptions.SamplingTags.Add("tag1", "value1"); + return ActivitySamplingResult.AllData; + }; + + // listener2 provide Sample callback and doesn't provide SampleUsingParentId + // Sample should get called as we convert the w3c id to a context. + listener2.Sample = (ref ActivityCreationOptions activityOptions) => + { + activityOptions.SamplingTags.Add("tag2", "value2"); + return ActivitySamplingResult.AllData; + }; + + ActivitySource.AddActivityListener(listener1); + ActivitySource.AddActivityListener(listener2); + + using Activity activity = aSource.StartActivity("a1", ActivityKind.Client, w3cId); + + Assert.NotNull(activity); + Assert.Equal(2, activity.TagObjects.Count()); + Assert.Equal(new KeyValuePair("tag1", "value1"), activity.TagObjects.ElementAt(0)); + Assert.Equal(new KeyValuePair("tag2", "value2"), activity.TagObjects.ElementAt(1)); + }).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void TestAddSamplerAndActivityCreationTags() + { + RemoteExecutor.Invoke(() => { + using ActivitySource aSource = new ActivitySource("SamplerTags7"); + using ActivityListener listener = new ActivityListener(); + listener.ShouldListenTo = (activitySource) => activitySource.Name == "SamplerTags7"; + + listener.Sample = (ref ActivityCreationOptions activityOptions) => + { + activityOptions.SamplingTags.Add("SamplerTag1", "SamplerValue1"); + return ActivitySamplingResult.AllData; + }; + + ActivitySource.AddActivityListener(listener); + + ActivityTagsCollection tags = new ActivityTagsCollection(); + tags["tag1"] = "value1"; + tags["tag2"] = "value2"; + + using Activity activity = aSource.StartActivity("a1", ActivityKind.Client, default(ActivityContext), tags); + + Assert.NotNull(activity); + Assert.Equal(3, activity.TagObjects.Count()); + Assert.Equal(new KeyValuePair("tag1", "value1"), activity.TagObjects.ElementAt(0)); + Assert.Equal(new KeyValuePair("tag2", "value2"), activity.TagObjects.ElementAt(1)); + Assert.Equal(new KeyValuePair("SamplerTag1", "SamplerValue1"), activity.TagObjects.ElementAt(2)); + }).Dispose(); + } + public void Dispose() => Activity.Current = null; } } -- 2.7.4