1
0
Fork 0
mirror of https://github.com/VSadov/Satori.git synced 2025-06-10 18:11:04 +09:00

[System.Diagnostics.DiagnosticSource] Tracing out of proc sampling (#104134)

* WIP to implement out-of-proc sampling spec for tracing.

* Clean up.

* Adjust sampling precedence rules.

* Code review and refactor.

* Drop support for specifying wild card activity source with specific activity name. Improve internal logging.

* Remove unsupported scenario from code comments.

* Discard additional rules once a match has been established.

* Refactor away DiagnosticSourceEventSourceListener class.

* Code review.

* Revert partial addition.

* Skip lookup using activity name if no rules were defined.

* Remove static coupling.

* Cleanup.

* Fix up trim warnings.
This commit is contained in:
Mikel Blanchard 2024-07-18 15:47:03 -07:00 committed by GitHub
parent d123560a23
commit d9268eed3e
Signed by: github
GPG key ID: B5690EEEBB952194
8 changed files with 1698 additions and 1254 deletions

View file

@ -44,7 +44,11 @@ System.Diagnostics.DiagnosticSource</PackageDescription>
<Compile Include="System\Diagnostics\DiagnosticListener.cs" />
<Compile Include="System\Diagnostics\DiagnosticSourceEventSource.cs" />
<Compile Include="System\Diagnostics\DiagLinkedList.cs" />
<Compile Include="System\Diagnostics\DsesActivityEvents.cs" />
<Compile Include="System\Diagnostics\DsesActivitySourceListener.cs" />
<Compile Include="System\Diagnostics\DsesSamplerBuilder.cs" />
<Compile Include="System\Diagnostics\DistributedContextPropagator.cs" />
<Compile Include="System\Diagnostics\DsesFilterAndTransform.cs" />
<Compile Include="System\Diagnostics\LegacyPropagator.cs" />
<Compile Include="System\Diagnostics\NoOutputPropagator.cs" />
<Compile Include="System\Diagnostics\PassThroughPropagator.cs" />

View file

@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Diagnostics;
[Flags]
internal enum DsesActivityEvents
{
None = 0x00,
ActivityStart = 0x01,
ActivityStop = 0x02,
All = ActivityStart | ActivityStop,
}

View file

@ -0,0 +1,251 @@
// 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.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
namespace System.Diagnostics;
internal sealed class DsesActivitySourceListener : IDisposable
{
private readonly DiagnosticSourceEventSource _eventSource;
private DsesFilterAndTransform? _wildcardSpec;
private Dictionary<SpecLookupKey, DsesFilterAndTransform>? _specsBySourceNameAndActivityName;
private HashSet<string>? _listenToActivitySourceNames;
private bool _hasActivityNameSpecDefined;
private ActivityListener? _activityListener;
public static DsesActivitySourceListener Create(
DiagnosticSourceEventSource eventSource,
DsesFilterAndTransform activitySourceSpecs)
{
var listener = new DsesActivitySourceListener(eventSource);
listener.NormalizeActivitySourceSpecsList(activitySourceSpecs);
listener.CreateActivityListener();
return listener;
}
private DsesActivitySourceListener(DiagnosticSourceEventSource eventSource)
{
_eventSource = eventSource;
}
public void Dispose()
{
_activityListener?.Dispose();
_activityListener = null;
_wildcardSpec = null;
_specsBySourceNameAndActivityName = null;
_listenToActivitySourceNames = null;
}
private void NormalizeActivitySourceSpecsList(
DsesFilterAndTransform? activitySourceSpecs)
{
Debug.Assert(activitySourceSpecs != null);
while (activitySourceSpecs != null)
{
DsesFilterAndTransform? currentActivitySourceSpec = activitySourceSpecs;
Debug.Assert(currentActivitySourceSpec.SourceName != null);
Debug.Assert(currentActivitySourceSpec.SampleFunc != null);
activitySourceSpecs = activitySourceSpecs.Next;
if (currentActivitySourceSpec.SourceName == "*")
{
if (_wildcardSpec != null)
{
if (_eventSource.IsEnabled(EventLevel.Warning, DiagnosticSourceEventSource.Keywords.Messages))
_eventSource.Message("DiagnosticSource: Ignoring wildcard activity source filterAndPayloadSpec rule because a previous rule was defined");
continue;
}
_wildcardSpec = currentActivitySourceSpec;
}
else
{
var specs = _specsBySourceNameAndActivityName ??= new(SpecLookupKeyComparer.Instance);
var allSources = _listenToActivitySourceNames ??= new(StringComparer.OrdinalIgnoreCase);
SpecLookupKey key = new(currentActivitySourceSpec.SourceName, currentActivitySourceSpec.ActivityName);
#if NETFRAMEWORK || NETSTANDARD2_0
if (specs.ContainsKey(key))
{
LogIgnoredSpecRule(currentActivitySourceSpec.SourceName, currentActivitySourceSpec.ActivityName);
continue;
}
specs[key] = currentActivitySourceSpec;
#else
if (!specs.TryAdd(key, currentActivitySourceSpec))
{
LogIgnoredSpecRule(currentActivitySourceSpec.SourceName, currentActivitySourceSpec.ActivityName);
continue;
}
#endif
allSources.Add(key.activitySourceName);
if (key.activityName != null)
{
_hasActivityNameSpecDefined = true;
}
}
}
Debug.Assert(_wildcardSpec != null || _specsBySourceNameAndActivityName != null);
void LogIgnoredSpecRule(string activitySourceName, string? activityName)
{
if (_eventSource.IsEnabled(EventLevel.Warning, DiagnosticSourceEventSource.Keywords.Messages))
{
if (activityName == null)
{
_eventSource.Message($"DiagnosticSource: Ignoring filterAndPayloadSpec rule for '[AS]{activitySourceName}' because a previous rule was defined");
}
else
{
_eventSource.Message($"DiagnosticSource: Ignoring filterAndPayloadSpec rule for '[AS]{activitySourceName}+{activityName}' because a previous rule was defined");
}
}
}
}
private void CreateActivityListener()
{
Debug.Assert(_activityListener == null);
Debug.Assert(_wildcardSpec != null
|| _specsBySourceNameAndActivityName != null);
_activityListener = new ActivityListener();
_activityListener.SampleUsingParentId = OnSampleUsingParentId;
_activityListener.Sample = OnSample;
_activityListener.ShouldListenTo = (activitySource) =>
{
return _wildcardSpec != null
|| (_listenToActivitySourceNames != null
&& _listenToActivitySourceNames.Contains(activitySource.Name));
};
_activityListener.ActivityStarted = OnActivityStarted;
_activityListener.ActivityStopped = OnActivityStopped;
ActivitySource.AddActivityListener(_activityListener);
}
private bool TryFindSpecForActivity(
string activitySourceName,
string activityName,
[NotNullWhen(true)] out DsesFilterAndTransform? spec)
{
if (_specsBySourceNameAndActivityName != null)
{
if (_hasActivityNameSpecDefined &&
_specsBySourceNameAndActivityName.TryGetValue(new(activitySourceName, activityName), out spec))
{
return true;
}
if (_specsBySourceNameAndActivityName.TryGetValue(new(activitySourceName, null), out spec))
{
return true;
}
}
return (spec = _wildcardSpec) != null;
}
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(Activity))]
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(ActivityContext))]
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(ActivityEvent))]
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(ActivityLink))]
[DynamicDependency(nameof(DateTime.Ticks), typeof(DateTime))]
[DynamicDependency(nameof(TimeSpan.Ticks), typeof(TimeSpan))]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Activity's properties are being preserved with the DynamicDependencies on OnActivityStarted.")]
private void OnActivityStarted(Activity activity)
{
if (TryFindSpecForActivity(activity.Source.Name, activity.OperationName, out var spec)
&& (spec.Events & DsesActivityEvents.ActivityStart) != 0)
{
_eventSource.ActivityStart(activity.Source.Name, activity.OperationName, spec.Morph(activity));
}
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Activity's properties are being preserved with the DynamicDependencies on OnActivityStarted.")]
private void OnActivityStopped(Activity activity)
{
if (TryFindSpecForActivity(activity.Source.Name, activity.OperationName, out var spec)
&& (spec.Events & DsesActivityEvents.ActivityStop) != 0)
{
_eventSource.ActivityStop(activity.Source.Name, activity.OperationName, spec.Morph(activity));
}
}
private ActivitySamplingResult OnSampleUsingParentId(ref ActivityCreationOptions<string> options)
{
ActivityCreationOptions<ActivityContext> activityContextOptions = default;
return OnSample(options.Source.Name, options.Name, hasActivityContext: false, ref activityContextOptions);
}
private ActivitySamplingResult OnSample(ref ActivityCreationOptions<ActivityContext> options)
{
return OnSample(options.Source.Name, options.Name, hasActivityContext: true, ref options);
}
private ActivitySamplingResult OnSample(
string activitySourceName,
string activityName,
bool hasActivityContext,
ref ActivityCreationOptions<ActivityContext> options)
{
return TryFindSpecForActivity(activitySourceName, activityName, out var spec)
? spec.SampleFunc!(hasActivityContext, ref options)
: ActivitySamplingResult.None;
}
private readonly struct SpecLookupKey
{
public SpecLookupKey(
string activitySourceName,
string? activityName)
{
Debug.Assert(activitySourceName != null);
this.activitySourceName = activitySourceName;
this.activityName = activityName;
}
public readonly string activitySourceName;
public readonly string? activityName;
}
private sealed class SpecLookupKeyComparer : IEqualityComparer<SpecLookupKey>
{
public static readonly SpecLookupKeyComparer Instance = new();
public bool Equals(SpecLookupKey x, SpecLookupKey y)
=> string.Equals(x.activitySourceName, y.activitySourceName, StringComparison.OrdinalIgnoreCase)
&& string.Equals(x.activityName, y.activityName, StringComparison.OrdinalIgnoreCase);
public int GetHashCode(SpecLookupKey obj)
{
// HashCode.Combine would be the best but we need to compile for the full framework which require adding dependency
// on the extensions package. Considering this simple type and hashing is not expected to be used much, we are implementing
// the hashing manually.
int hash = 5381;
hash = ((hash << 5) + hash) + StringComparer.OrdinalIgnoreCase.GetHashCode(obj.activitySourceName);
hash = ((hash << 5) + hash) + (obj.activityName == null ? 0 : StringComparer.OrdinalIgnoreCase.GetHashCode(obj.activityName));
return hash;
}
}
}

View file

@ -0,0 +1,69 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Diagnostics;
internal delegate ActivitySamplingResult DsesSampleActivityFunc(
bool hasActivityContext,
ref ActivityCreationOptions<ActivityContext> options);
internal static class DsesSamplerBuilder
{
public static DsesSampleActivityFunc CreateParentRatioSampler(double ratio)
{
long idUpperBound = ratio <= 0.0
? long.MinValue
: ratio >= 1.0
? long.MaxValue
: (long)(ratio * long.MaxValue);
return (bool hasActivityContext, ref ActivityCreationOptions<ActivityContext> options) =>
{
if (hasActivityContext && options.TraceId != default)
{
ActivityContext parentContext = options.Parent;
ActivitySamplingResult samplingDecision = ParentRatioSampler(idUpperBound, in parentContext, options.TraceId);
return samplingDecision == ActivitySamplingResult.None
&& (parentContext == default || parentContext.IsRemote)
? ActivitySamplingResult.PropagationData // If it is the root span or the parent is remote select PropagationData so the trace ID is preserved
// even if no activity of the trace is recorded
: samplingDecision;
}
return ActivitySamplingResult.None;
};
}
public static ActivitySamplingResult ParentRatioSampler(long idUpperBound, in ActivityContext parentContext, ActivityTraceId traceId)
{
if (parentContext.TraceId != default)
{
return parentContext.TraceFlags.HasFlag(ActivityTraceFlags.Recorded)
? ActivitySamplingResult.AllDataAndRecorded
: ActivitySamplingResult.None;
}
Span<byte> traceIdBytes = stackalloc byte[16];
traceId.CopyTo(traceIdBytes);
return Math.Abs(GetLowerLong(traceIdBytes)) < idUpperBound
? ActivitySamplingResult.AllDataAndRecorded
: ActivitySamplingResult.None;
static long GetLowerLong(ReadOnlySpan<byte> bytes)
{
long result = 0;
for (int i = 0; i < 8; i++)
{
result <<= 8;
#pragma warning disable CS0675 // Bitwise-or operator used on a sign-extended operand
result |= bytes[i] & 0xff;
#pragma warning restore CS0675 // Bitwise-or operator used on a sign-extended operand
}
return result;
}
}
}

View file

@ -37,7 +37,7 @@ namespace System.Diagnostics.Tests
RemoteExecutor.Invoke(() =>
{
using TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener();
ActivitySource [] sources = new ActivitySource[10];
ActivitySource[] sources = new ActivitySource[10];
for (int i = 0; i < 10; i++)
{
sources[i] = new ActivitySource($"Source{i}");
@ -47,7 +47,7 @@ namespace System.Diagnostics.Tests
Assert.Equal(eventsCount, eventSourceListener.EventCount);
eventSourceListener.Enable(" [AS]* \r\n"); // All Sources + All Events
Activity [] activities = new Activity[10];
Activity[] activities = new Activity[10];
for (int i = 0; i < 10; i++)
{
@ -80,7 +80,7 @@ namespace System.Diagnostics.Tests
RemoteExecutor.Invoke((eventname) =>
{
using TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener();
ActivitySource [] sources = new ActivitySource[10];
ActivitySource[] sources = new ActivitySource[10];
for (int i = 0; i < 10; i++)
{
sources[i] = new ActivitySource($"Source{i}");
@ -90,7 +90,7 @@ namespace System.Diagnostics.Tests
Assert.Equal(eventsCount, eventSourceListener.EventCount);
eventSourceListener.Enable($" [AS]* / {eventname} \r\n"); // All Sources + one Event
Activity [] activities = new Activity[10];
Activity[] activities = new Activity[10];
for (int i = 0; i < 10; i++)
{
@ -126,9 +126,9 @@ namespace System.Diagnostics.Tests
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[InlineData("Propagate", false, ActivityTraceFlags.None)]
[InlineData("PROPAGATE", false, ActivityTraceFlags.None)]
[InlineData("Record", true, ActivityTraceFlags.None)]
[InlineData("recorD", true, ActivityTraceFlags.None)]
[InlineData("", true, ActivityTraceFlags.Recorded)]
[InlineData("Record", true, ActivityTraceFlags.None)]
[InlineData("recorD", true, ActivityTraceFlags.None)]
[InlineData("", true, ActivityTraceFlags.Recorded)]
public void TestEnableAllActivitySourcesWithSpeciifcSamplingResult(string samplingResult, bool alldataRequested, ActivityTraceFlags activityTraceFlags)
{
RemoteExecutor.Invoke((result, dataRequested, traceFlags) =>
@ -156,33 +156,35 @@ namespace System.Diagnostics.Tests
}
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[InlineData("", "", true)]
[InlineData("Start", "", true)]
[InlineData("stop", "", true)]
[InlineData("", "Propagate", false)]
[InlineData("", "Record", true)]
[InlineData("", "", true)]
[InlineData("Start", "", true)]
[InlineData("stop", "", true)]
[InlineData("", "Propagate", false)]
[InlineData("", "Record", true)]
[InlineData("Start", "Propagate", false)]
[InlineData("Stop", "Record", true)]
public void TestDefaultActivitySource(string eventName, string samplingResult, bool alldataRequested)
[InlineData("Stop", "Record", true)]
public void TestDefaultActivitySource(string eventName, string samplingResult, bool allDataRequested)
{
RemoteExecutor.Invoke((eventname, result, dataRequested) =>
RemoteExecutor.Invoke((eventName, samplingResult, allDataRequested) =>
{
using TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener();
Activity a = new Activity(""); // we need this to ensure DiagnosticSourceEventSource.Logger creation.
Assert.Equal(0, eventSourceListener.EventCount);
eventSourceListener.Enable($" [AS]/{eventname}-{result}\r\n");
eventSourceListener.Enable($" [AS]/{eventName}-{samplingResult}\r\n");
Assert.Equal("", a.Source.Name);
a = a.Source.StartActivity("newOne");
Assert.Equal(bool.Parse(dataRequested), a.IsAllDataRequested);
Assert.NotNull(a);
Assert.Equal(bool.Parse(allDataRequested), a.IsAllDataRequested);
// All Activities created with "new Activity(...)" will have ActivityTraceFlags is `None`;
Assert.Equal(result.Length == 0 ? ActivityTraceFlags.Recorded : ActivityTraceFlags.None, a.ActivityTraceFlags);
Assert.Equal(samplingResult.Length == 0 ? ActivityTraceFlags.Recorded : ActivityTraceFlags.None, a.ActivityTraceFlags);
a.Dispose();
int eCount = eventname.Length == 0 ? 2 : 1;
int eCount = eventName.Length == 0 ? 2 : 1;
Assert.Equal(eCount, eventSourceListener.EventCount);
// None Default Source
@ -190,19 +192,19 @@ namespace System.Diagnostics.Tests
Activity activity = source.StartActivity($"ActivityFromNoneDefault"); // Shouldn't fire any event
Assert.Equal(eCount, eventSourceListener.EventCount);
Assert.Null(activity);
}, eventName, samplingResult, alldataRequested.ToString()).Dispose();
}, eventName, samplingResult, allDataRequested.ToString()).Dispose();
}
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[InlineData("[AS]*\r\n [AS]Specific/-Propagate\r\n [AS]*/-Propagate\r\n", false, true)]
[InlineData("[AS]AnotherSource/-Propagate\r\n [AS]*/-Propagate\r\n [AS]Specific/-Record\r\n [AS]*\r\n", true, true)]
[InlineData("[AS]*/-Propagate\r\n [AS]*/-Propagate\r\n [AS]Specific/-Propagate\r\n[AS]Specific/-Record\r\n", true, false)]
[InlineData("[AS]*\r\n [AS]Specific/-Propagate\r\n", false, true)]
[InlineData("[AS]AnotherSource/-Propagate\r\n [AS]Specific/-Record\r\n [AS]*\r\n", true, true)]
[InlineData("[AS]*/-Propagate\r\n [AS]Specific/-Record\r\n", true, false)]
[InlineData("[AS]*/-Propagate\r\n", false, false)]
[InlineData("[AS]*/-Record\r\n", true, true)]
[InlineData("[AS]Specific/-Propagate\r\n [AS]NoneSpecific/-Record\r\n", false, true)]
[InlineData("[AS]Specific/-Record\r\n [AS]NoneSpecific/-Propagate\r\n", true, false)]
[InlineData("[AS]Specific\r\n [AS]NoneSpecific\r\n", true, true)]
public void TestMultipleSpecs(string spec, bool isAlldataRequestedFromSpecif, bool alldataRequestedFromNoneSpecific)
public void TestMultipleSpecs(string spec, bool isAllDataRequestedFromSpecific, bool isAllDataRequestedFromNoneSpecific)
{
RemoteExecutor.Invoke((specString, specificAllData, noneSpecificAllData) =>
{
@ -227,7 +229,7 @@ namespace System.Diagnostics.Tests
a2.Dispose();
Assert.Equal(4, eventSourceListener.EventCount);
}, spec, isAlldataRequestedFromSpecif.ToString(), alldataRequestedFromNoneSpecific.ToString()).Dispose();
}, spec, isAllDataRequestedFromSpecific.ToString(), isAllDataRequestedFromNoneSpecific.ToString()).Dispose();
}
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
@ -303,65 +305,44 @@ namespace System.Diagnostics.Tests
using (TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener())
{
using ActivitySource aSource1 = new ActivitySource("Source1");
using ActivitySource aSource2 = new ActivitySource("Source2");
eventSourceListener.Enable("[AS]*+MyActivity");
Assert.Equal(0, eventSourceListener.EventCount);
Activity a = aSource1.StartActivity("NotMyActivity"); // Shouldn't get created because nt matching MyActivity
Assert.Null(a);
Assert.Equal(0, eventSourceListener.EventCount);
a = aSource1.StartActivity("MyActivity");
Assert.NotNull(a);
Assert.Equal(1, eventSourceListener.EventCount);
Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
a.Stop();
Assert.Equal(2, eventSourceListener.EventCount);
Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
a = aSource2.StartActivity("MyActivity");
Assert.NotNull(a);
Assert.Equal(3, eventSourceListener.EventCount);
Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
a.Stop();
Assert.Equal(4, eventSourceListener.EventCount);
Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
using ActivitySource aSource2 = new ActivitySource("MySource");
//
// Now test with specific ActivitySource and ActivityName
// test with specific ActivitySource and ActivityName
//
eventSourceListener.Enable("[AS]MySource+MyActivity");
a = aSource1.StartActivity("MyActivity"); // Not from MySource
Assert.Null(a);
Assert.Equal(4, eventSourceListener.EventCount);
using ActivitySource aSource3 = new ActivitySource("MySource");
a = aSource3.StartActivity("NotMyActivity"); // from MySource but NoMyActivity
Assert.Null(a);
Assert.Equal(4, eventSourceListener.EventCount);
Assert.Equal(0, eventSourceListener.EventCount);
a = aSource3.StartActivity("MyActivity"); // from MySource and MyActivity
Activity a = aSource1.StartActivity("MyActivity"); // Not from MySource
Assert.Null(a);
Assert.Equal(0, eventSourceListener.EventCount);
a = aSource2.StartActivity("NotMyActivity"); // from MySource but NoMyActivity
Assert.Null(a);
Assert.Equal(0, eventSourceListener.EventCount);
a = aSource2.StartActivity("MyActivity"); // from MySource and MyActivity
Assert.NotNull(a);
Assert.Equal(5, eventSourceListener.EventCount);
Assert.Equal(1, eventSourceListener.EventCount);
Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
a.Stop();
Assert.Equal(6, eventSourceListener.EventCount);
Assert.Equal(2, eventSourceListener.EventCount);
Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
//
// test with full query
//
eventSourceListener.Enable("[AS]MySource+MyActivity/Start-Propagate");
a = aSource3.StartActivity("MyActivity"); // from MySource and MyActivity
a = aSource2.StartActivity("MyActivity"); // from MySource and MyActivity
Assert.NotNull(a);
Assert.Equal(7, eventSourceListener.EventCount);
Assert.Equal(3, eventSourceListener.EventCount);
Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
Assert.False(a.IsAllDataRequested);
a.Stop(); // shouldn't fire
Assert.Equal(7, eventSourceListener.EventCount);
Assert.Equal(3, eventSourceListener.EventCount);
//
// test with default Source
@ -369,17 +350,17 @@ namespace System.Diagnostics.Tests
eventSourceListener.Enable("[AS]+MyActivity");
a = new Activity("MyActivity");
a.Start();
Assert.Equal(8, eventSourceListener.EventCount);
Assert.Equal(4, eventSourceListener.EventCount);
Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
Assert.True(a.IsAllDataRequested);
a.Stop();
Assert.Equal(9, eventSourceListener.EventCount);
Assert.Equal(5, eventSourceListener.EventCount);
Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
a = new Activity("NotMyActivity");
a.Start(); // nothing fire
Assert.Equal(9, eventSourceListener.EventCount);
Assert.Equal(5, eventSourceListener.EventCount);
//
// Test with Empty ActivityName
@ -387,11 +368,11 @@ namespace System.Diagnostics.Tests
eventSourceListener.Enable("[AS]+");
a = new Activity("");
a.Start();
Assert.Equal(10, eventSourceListener.EventCount);
Assert.Equal(6, eventSourceListener.EventCount);
a.Stop();
Assert.Equal(11, eventSourceListener.EventCount);
Assert.Equal(7, eventSourceListener.EventCount);
a = aSource3.StartActivity("");
a = aSource2.StartActivity("");
Assert.Null(a);
}
@ -1326,7 +1307,7 @@ namespace System.Diagnostics.Tests
Assert.Equal(a.TraceId.ToString(), e.Arguments["ActivityTraceId"]);
Assert.Equal(a.SpanId.ToString(), e.Arguments["ActivitySpanId"]);
Assert.Equal(a.TraceStateString, e.Arguments["ActivityTraceStateString"]);
if(a.ParentSpanId != default)
if (a.ParentSpanId != default)
{
Assert.Equal(a.ParentSpanId.ToString(), e.Arguments["ActivityParentSpanId"]);
}
@ -1367,6 +1348,246 @@ namespace System.Diagnostics.Tests
Assert.Equal(1, eventListener.EventCount);
}
}
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public void TestRuleWithWildcardSourceAndActivityNameIsIgnored()
{
RemoteExecutor.Invoke(() =>
{
using TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener();
Activity a = new Activity(""); // we need this to ensure DiagnosticSourceEventSource.Logger creation.
Assert.Equal("", a.Source.Name);
eventSourceListener.Enable("[AS]*+TestName"); // Rule with wildcard source and activity name is ignored
Assert.NotNull(eventSourceListener.LastOtherEvent);
Assert.Equal("Message", eventSourceListener.LastOtherEvent.EventName);
Assert.Equal("DiagnosticSource: Ignoring filterAndPayloadSpec '[AS]*+TestName' because activity name cannot be specified for wildcard activity sources", eventSourceListener.LastOtherEvent.Payload[0] as string);
using var root = a.Source.StartActivity("TestName");
Assert.Null(root);
}).Dispose();
}
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[InlineData("[AS]*/Stop\r\n [AS]*/Start", "DiagnosticSource: Ignoring wildcard activity source filterAndPayloadSpec rule because a previous rule was defined")]
[InlineData("[AS]TestSource/Stop\r\n [AS]TestSource/Start", "DiagnosticSource: Ignoring filterAndPayloadSpec rule for '[AS]TestSource' because a previous rule was defined")]
[InlineData("[AS]TestSource+TestActivity/Stop\r\n [AS]TestSource+TestActivity/Start", "DiagnosticSource: Ignoring filterAndPayloadSpec rule for '[AS]TestSource+TestActivity' because a previous rule was defined")]
public void TestMultipleRulesOnlyFirstTaken(string spec, string errorMessage)
{
RemoteExecutor.Invoke((string spec, string errorMessage) =>
{
using TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener();
Activity a = new Activity(""); // we need this to ensure DiagnosticSourceEventSource.Logger creation.
Assert.Equal("", a.Source.Name);
eventSourceListener.Enable(spec); // Rule with wildcard source and activity name is ignored
Assert.NotNull(eventSourceListener.LastOtherEvent);
Assert.Equal("Message", eventSourceListener.LastOtherEvent.EventName);
Assert.Equal(errorMessage, eventSourceListener.LastOtherEvent.Payload[0] as string);
using var source = new ActivitySource("TestSource");
using var root = source.StartActivity("TestActivity");
Assert.NotNull(root);
Assert.Equal(0, eventSourceListener.EventCount);
root.Stop();
Assert.Equal(1, eventSourceListener.EventCount);
}, spec, errorMessage).Dispose();
}
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public void TestParentRatioSampler()
{
RemoteExecutor.Invoke(() =>
{
// Test ParentRatioSampler behavior with 0% ratio
using TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener();
Activity a = new Activity(""); // we need this to ensure DiagnosticSourceEventSource.Logger creation.
Assert.Equal(0, eventSourceListener.EventCount);
eventSourceListener.Enable("[AS]*/-ParentRatioSampler(0.0)");
Assert.Equal("", a.Source.Name);
using var root = a.Source.StartActivity("root");
// Note: Because this is a root it gets created even though it was not sampled...
Assert.NotNull(root);
// ...but it is not marked as recorded.
Assert.False(root.Recorded);
using var child = a.Source.StartActivity("child");
Assert.Null(child);
}).Dispose();
RemoteExecutor.Invoke(() =>
{
// Test ParentRatioSampler behavior with 100% ratio
using TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener();
Activity a = new Activity(""); // we need this to ensure DiagnosticSourceEventSource.Logger creation.
Assert.Equal(0, eventSourceListener.EventCount);
eventSourceListener.Enable("[AS]*/-ParentRatioSampler(1.0)");
Assert.Equal("", a.Source.Name);
using var root = a.Source.StartActivity("root");
Assert.NotNull(root);
Assert.True(root.Recorded);
using var child = a.Source.StartActivity("child");
Assert.NotNull(child);
Assert.True(child.Recorded);
}).Dispose();
RemoteExecutor.Invoke(() =>
{
// This traceId will not be sampled by the ParentRatioSampler because the first 8 bytes as long
// is not less than probability * Long.MAX_VALUE;
var notSampledtraceId =
ActivityTraceId.CreateFromBytes(
new byte[]
{
0x8F,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0,
0,
0,
0,
0,
0,
0,
0,
});
Assert.Equal(
ActivitySamplingResult.None,
DsesSamplerBuilder.ParentRatioSampler(
(long)(0.0001D * long.MaxValue),
parentContext: default,
notSampledtraceId));
}).Dispose();
RemoteExecutor.Invoke(() =>
{
// This traceId will be sampled by the ParentRatioSampler because the first 8 bytes as long
// is less than probability * Long.MAX_VALUE;
var sampledtraceId =
ActivityTraceId.CreateFromBytes(
new byte[]
{
0x00,
0x00,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0,
0,
0,
0,
0,
0,
0,
0,
});
Assert.Equal(
ActivitySamplingResult.AllDataAndRecorded,
DsesSamplerBuilder.ParentRatioSampler(
(long)(0.0001D * long.MaxValue),
parentContext: default,
sampledtraceId));
}).Dispose();
}
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public void TestSamplingPrecedence()
{
RemoteExecutor.Invoke(() =>
{
// Match on source + name wins
using ActivitySource source = new("TestActivitySource");
using TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener();
eventSourceListener.Enable(
@"[AS]TestActivitySource+TestActivity/-ParentRatioSampler(0.0)
[AS]TestActivitySource/-
[AS]*/-
");
using (Activity? a = source.StartActivity("TestActivity"))
{
Assert.NotNull(a);
Assert.False(a.Recorded);
}
}).Dispose();
RemoteExecutor.Invoke(() =>
{
// Match on source wins
using ActivitySource source = new("TestActivitySource");
using TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener();
eventSourceListener.Enable(
@"[AS]TestActivitySource+TestActivity/-
[AS]TestActivitySource/-ParentRatioSampler(0.0)
[AS]*/-
");
using (Activity? a = source.StartActivity("OtherActivity"))
{
Assert.NotNull(a);
Assert.False(a.Recorded);
}
}).Dispose();
RemoteExecutor.Invoke(() =>
{
// Wildcard match wins
using ActivitySource source = new("OtherActivitySource");
using TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener();
eventSourceListener.Enable(
@"[AS]TestActivitySource+TestActivity/-
[AS]TestActivitySource/-
[AS]*/-ParentRatioSampler(0.0)
");
using (Activity? a = source.StartActivity("OtherActivity"))
{
Assert.NotNull(a);
Assert.False(a.Recorded);
}
}).Dispose();
}
}
/****************************************************************************/
@ -1430,6 +1651,7 @@ namespace System.Diagnostics.Tests
public TestDiagnosticSourceEventListener()
{
EventWritten += UpdateLastEvent;
OtherEventWritten += UpdateLastOtherEvent;
}
public int EventCount;
@ -1439,6 +1661,8 @@ namespace System.Diagnostics.Tests
public DiagnosticSourceEvent SecondLast;
public DiagnosticSourceEvent ThirdLast;
public EventWrittenEventArgs LastOtherEvent;
/// <summary>
/// Sets the EventCount to 0 and LastEvents to null
/// </summary>
@ -1467,6 +1691,11 @@ namespace System.Diagnostics.Tests
EventCount++;
LastEvent = anEvent;
}
private void UpdateLastOtherEvent(EventWrittenEventArgs anEvent)
{
LastOtherEvent = anEvent;
}
#endregion
}

View file

@ -14,6 +14,7 @@
<Compile Include="TestNotSupported.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\src\System\Diagnostics\DsesSamplerBuilder.cs" Link="DsesSamplerBuilder.cs" />
<Compile Include="..\src\System\Diagnostics\Metrics\Aggregator.cs" Link="Aggregator.cs" />
<Compile Include="..\src\System\Diagnostics\Metrics\AggregatorStore.cs" Link="AggregatorStore.cs" />
<Compile Include="..\src\System\Diagnostics\Metrics\ExponentialHistogramAggregator.cs" Link="ExponentialHistogramAggregator.cs" />