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:
parent
d123560a23
commit
d9268eed3e
8 changed files with 1698 additions and 1254 deletions
|
@ -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" />
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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,
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue