Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Distributed tracing support #5078

Merged
merged 15 commits into from
Feb 26, 2025
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Diagnostics.Tracing;

namespace Microsoft.Diagnostics.Monitoring.EventPipe
{
public sealed class ActivitySourceConfiguration : MonitoringSourceConfiguration
{
private readonly double _SamplingRatio;
private readonly string[] _ActivitySourceNames;

public ActivitySourceConfiguration(
DiagnosticsClient client,
double samplingRatio,
IEnumerable<string>? activitySourceNames)
{
_SamplingRatio = samplingRatio;
_ActivitySourceNames = activitySourceNames?.ToArray() ?? Array.Empty<string>();

if (_SamplingRatio < 1D)
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be doing something this heavyweight and async in the ctor. If we want to validate this only works in version 9, we can fail the pipeline later or have the validation done upfront in a helper async method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved it to a spot that felt more natural. Check it out, LMK.

int majorVersion = 0;

using CancellationTokenSource cancellationTokenSource = new();

DiagnosticsEventPipeProcessor processor = new(
new ActivitySourceConfiguration(client, 1D, activitySourceNames: null),
async (EventPipeEventSource eventSource, Func<Task> stopSessionAsync, CancellationToken token) => {
eventSource.Dynamic.All += traceEvent => {
try
{
if ("Version".Equals(traceEvent.EventName))
{
majorVersion = (int)traceEvent.PayloadValue(0);
}

if (!cancellationTokenSource.IsCancellationRequested)
{
// Note: Version should be the first message
// written so cancel once we have received a
// message.
cancellationTokenSource.Cancel();
}
}
catch (Exception)
{
}
};

using EventTaskSource<Action> sourceCompletedTaskSource = new(
taskComplete => taskComplete,
handler => eventSource.Completed += handler,
handler => eventSource.Completed -= handler,
token);

await sourceCompletedTaskSource.Task.ConfigureAwait(false);
});

try
{
processor.Process(client, TimeSpan.FromSeconds(10), resumeRuntime: false, token: cancellationTokenSource.Token).GetAwaiter().GetResult();
}
catch (OperationCanceledException)
{
}

processor.DisposeAsync().AsTask().GetAwaiter().GetResult();

if (majorVersion < 9)
{
throw new NotSupportedException("Sampling ratio can only be set when listening to processes running System.Diagnostics.DiagnosticSource 9 or greater");
}
}
}

public override IList<EventPipeProvider> GetProviders()
{
StringBuilder filterAndPayloadSpecs = new();
foreach (string activitySource in _ActivitySourceNames)
{
if (string.IsNullOrEmpty(activitySource))
{
continue;
}

// Note: It isn't currently possible to get Events or Links off
// of Activity using this mechanism:
// Events=Events.*Enumerate;Links=Links.*Enumerate; See:
// https://github.com/dotnet/runtime/issues/102924

string sampler = string.Empty;

if (_SamplingRatio < 1D)
{
sampler = $"-ParentRatioSampler({_SamplingRatio})";
}

filterAndPayloadSpecs.AppendLine($"[AS]{activitySource}/Stop{sampler}:-TraceId;SpanId;ParentSpanId;ActivityTraceFlags;TraceStateString;Kind;DisplayName;StartTimeTicks=StartTimeUtc.Ticks;DurationTicks=Duration.Ticks;Status;StatusDescription;Tags=TagObjects.*Enumerate;ActivitySourceVersion=Source.Version");
}

return new[] {
new EventPipeProvider(
DiagnosticSourceEventSource,
keywords: DiagnosticSourceEventSourceEvents | DiagnosticSourceEventSourceMessages,
eventLevel: EventLevel.Verbose,
arguments: new Dictionary<string, string>()
{
{ "FilterAndPayloadSpecs", filterAndPayloadSpecs.ToString() },
})
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public CounterConfiguration(CounterFilter filter)

internal record struct ProviderAndCounter(string ProviderName, string CounterName);

internal static class TraceEventExtensions
internal static partial class TraceEventExtensions
{
private static Dictionary<ProviderAndCounter, CounterMetadata> counterMetadataByName = new();
private static Dictionary<int, CounterMetadata> counterMetadataById = new();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System;
using System.Diagnostics;

namespace Microsoft.Diagnostics.Monitoring.EventPipe
{
internal readonly struct ActivityData
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very large struct object. Perhaps a class or record is more suitable? I have not looked at usage yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a super big struct! But that doesn't concern me. It is handled correctly, passed by reference (out or in in our cases). What this code is trying to do is not create unneeded GC pressure in the monitoring app. It doesn't have to do that, but that was my thinking 😄

{
public ActivityData(
ActivitySourceData source,
string operationName,
string? displayName,
ActivityKind kind,
ActivityTraceId traceId,
ActivitySpanId spanId,
ActivitySpanId parentSpanId,
ActivityTraceFlags traceFlags,
string? traceState,
DateTime startTimeUtc,
DateTime endTimeUtc,
ActivityStatusCode status,
string? statusDescription)
{
if (string.IsNullOrEmpty(operationName))
{
throw new ArgumentNullException(nameof(operationName));
}

Source = source;
OperationName = operationName;
DisplayName = displayName;
Kind = kind;
TraceId = traceId;
SpanId = spanId;
ParentSpanId = parentSpanId;
TraceFlags = traceFlags;
TraceState = traceState;
StartTimeUtc = startTimeUtc;
EndTimeUtc = endTimeUtc;
Status = status;
StatusDescription = statusDescription;
}

public readonly ActivitySourceData Source;

public readonly string OperationName;

public readonly string? DisplayName;

public readonly ActivityKind Kind;

public readonly ActivityTraceId TraceId;

public readonly ActivitySpanId SpanId;

public readonly ActivitySpanId ParentSpanId;

public readonly ActivityTraceFlags TraceFlags;

public readonly string? TraceState;

public readonly DateTime StartTimeUtc;

public readonly DateTime EndTimeUtc;

public readonly ActivityStatusCode Status;

public readonly string? StatusDescription;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System;
using System.Collections.Generic;

namespace Microsoft.Diagnostics.Monitoring.EventPipe
{
internal ref struct ActivityPayload
{
public ActivityData ActivityData;

public ReadOnlySpan<KeyValuePair<string, object?>> Tags;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

namespace Microsoft.Diagnostics.Monitoring.EventPipe
{
internal sealed class ActivitySourceData
{
public ActivitySourceData(
string name,
string? version)
{
Name = name;
Version = version;
}

public string Name { get; }
public string? Version { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Diagnostics.Monitoring.EventPipe
{
internal interface IActivityLogger
{
void Log(
in ActivityData activity,
ReadOnlySpan<KeyValuePair<string, object?>> tags);

Task PipelineStarted(CancellationToken token);
Task PipelineStopped(CancellationToken token);
}
}
Loading
Loading