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

[ContinuousProfiler] Context Tracking Test - switch to OTLP #3917

Merged
merged 2 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 22 additions & 45 deletions test/IntegrationTests/ContinuousProfilerContextTrackingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@

#if NET

using System.Text.Json;
using FluentAssertions;
using IntegrationTests.Helpers;
using OpenTelemetry.Proto.Collector.Profiles.V1Development;
using OpenTelemetry.Proto.Profiles.V1Development;
using Xunit.Abstractions;

namespace IntegrationTests;
Expand All @@ -22,69 +23,45 @@ public ContinuousProfilerContextTrackingTests(ITestOutputHelper output)
public void TraceContextIsCorrectlyAssociatedWithThreadSamples()
{
EnableBytecodeInstrumentation();
using var collector = new MockProfilesCollector(Output);
SetExporter(collector);
SetEnvironmentVariable("OTEL_DOTNET_AUTO_PLUGINS", "TestApplication.ContinuousProfiler.ContextTracking.TestPlugin, TestApplication.ContinuousProfiler.ContextTracking, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_ADDITIONAL_SOURCES", "TestApplication.ContinuousProfiler.ContextTracking");

var (standardOutput, _, _) = RunTestApplication();
collector.ExpectCollected(AssertAllProfiles, $"{nameof(AssertAllProfiles)} failed");

var batchSeparator = $"{Environment.NewLine}{Environment.NewLine}";
RunTestApplication();

var totalSamplesWithTraceContextCount = 0;
var managedThreadsWithTraceContext = new HashSet<string>();
collector.AssertCollected();
}

var exportedSampleBatches = standardOutput.TrimEnd().Split(batchSeparator);
private bool AssertAllProfiles(ICollection<ExportProfilesServiceRequest> profilesServiceRequests)
{
var totalSamplesWithTraceContextCount = 0;
var managedThreadsWithTraceContext = new HashSet<int>();

foreach (var sampleBatch in exportedSampleBatches)
foreach (var batch in profilesServiceRequests)
{
var batch = JsonDocument.Parse(sampleBatch.TrimStart());
IEnumerable<Sample> samplesInBatch = batch
.ResourceProfiles
.SelectMany(rp => rp.ScopeProfiles)
.SelectMany(sp => sp.Profiles)
.SelectMany(p => p.Sample);

var samplesWithTraceContext = samplesInBatch.Where(s => s.HasLinkIndex).ToList();

var samplesWithTraceContext = batch
.RootElement
.EnumerateArray()
.Select(
sample =>
ConvertToPropertyList(sample))
.Where(
sampleProperties =>
HasTraceContextAssociated(sampleProperties))
.ToList();
samplesWithTraceContext.Count.Should().BeLessOrEqualTo(1, "at most one sample in a batch should have trace context associated.");

totalSamplesWithTraceContextCount += samplesWithTraceContext.Count;
if (samplesWithTraceContext.FirstOrDefault() is { } sampleWithTraceContext)
{
managedThreadsWithTraceContext.Add(GetPropertyValue("ThreadName", sampleWithTraceContext).GetString()!);
managedThreadsWithTraceContext.Add(sampleWithTraceContext.AttributeIndices.Single());
}
}

managedThreadsWithTraceContext.Should().HaveCountGreaterThan(1, "at least 2 distinct threads should have trace context associated.");
totalSamplesWithTraceContextCount.Should().BeGreaterOrEqualTo(3, "there should be sample with trace context in most of the batches.");
}

private static bool HasTraceContextAssociated(List<JsonProperty> sample)
{
const int defaultTraceContextValue = 0;

return GetPropertyValue("SpanId", sample).GetInt64() != defaultTraceContextValue &&
GetPropertyValue("TraceIdHigh", sample).GetInt64() != defaultTraceContextValue &&
GetPropertyValue("TraceIdLow", sample).GetInt64() != defaultTraceContextValue &&
!string.IsNullOrWhiteSpace(GetPropertyValue("ThreadName", sample).GetString());
}

private static JsonElement GetPropertyValue(string propertyName, List<JsonProperty> jsonProperties)
{
return jsonProperties
.Single(
property =>
property.Name == propertyName)
.Value;
}

private static List<JsonProperty> ConvertToPropertyList(JsonElement threadSampleDocument)
{
return threadSampleDocument
.EnumerateObject()
.ToList();
return true;
}
}
#endif
47 changes: 47 additions & 0 deletions test/IntegrationTests/Helpers/MockProfilesCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class MockProfilesCollector : IDisposable

private readonly List<Expectation> _expectations = new();
private readonly BlockingCollection<Collected> _profilesSnapshots = new(10); // bounded to avoid memory leak; contains protobuf type
private CollectedExpectation? _collectedExpectation;

public MockProfilesCollector(ITestOutputHelper output)
{
Expand Down Expand Up @@ -48,6 +49,26 @@ public void Expect(Func<ExportProfilesServiceRequest, bool>? predicate = null, s
_expectations.Add(new Expectation(predicate, description));
}

public void ExpectCollected(Func<ICollection<ExportProfilesServiceRequest>, bool> collectedExpectation, string description)
{
_collectedExpectation = new(collectedExpectation, description);
}

public void AssertCollected()
{
if (_collectedExpectation == null)
{
throw new InvalidOperationException("Expectation for collected profiling snapshot was not set");
}

var collected = _profilesSnapshots.Select(collected => collected.ExportProfilesServiceRequest).ToArray();

if (!_collectedExpectation.Predicate(collected))
{
FailCollectedExpectation(_collectedExpectation.Description, collected);
}
}

public void AssertExpectations(TimeSpan? timeout = null)
{
if (_expectations.Count == 0)
Expand Down Expand Up @@ -133,6 +154,19 @@ private static void FailExpectations(
Assert.Fail(message.ToString());
}

private static void FailCollectedExpectation(string? collectedExpectationDescription, ExportProfilesServiceRequest[] collectedExportProfilesServiceRequests)
{
var message = new StringBuilder();
message.AppendLine($"Collected logs expectation failed: {collectedExpectationDescription}");
message.AppendLine("Collected logs:");
foreach (var logRecord in collectedExportProfilesServiceRequests)
{
message.AppendLine($" \"{logRecord}\"");
}

Assert.Fail(message.ToString());
}

private async Task HandleHttpRequests(HttpContext ctx)
{
using var bodyStream = await ctx.ReadBodyToMemoryAsync();
Expand Down Expand Up @@ -185,6 +219,19 @@ public Expectation(Func<ExportProfilesServiceRequest, bool> predicate, string? d

public string? Description { get; }
}

private class CollectedExpectation
{
public CollectedExpectation(Func<ICollection<ExportProfilesServiceRequest>, bool> predicate, string? description)
{
Predicate = predicate;
Description = description;
}

public Func<ICollection<ExportProfilesServiceRequest>, bool> Predicate { get; }

public string? Description { get; }
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ private static async Task DoSomethingAsync()
{
// timeout aligned with thread sampling interval
var timeout = TimeSpan.FromSeconds(1);
Thread.Sleep(timeout);

// extended first time sleep to ensure that stack will be fetched from the main thread
Thread.Sleep(TimeSpan.FromSeconds(5));
// continue on thread pool thread
await Task.Yield();
Thread.Sleep(timeout);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,30 @@
<TargetFrameworks>net9.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- SA1518 needed for files generated by Grpc.Tools -->
<NoWarn>SA1518;$(NoWarn)</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Diagnostics.DiagnosticSource" />
<PackageReference Include="Grpc.Net.Client" />
<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Grpc.Tools" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\TestApplication.ContinuousProfiler\Exporter\AllocationSample.cs">
<Link>Exporter\AllocationSample.cs</Link>
</Compile>
<Compile Include="..\TestApplication.ContinuousProfiler\Exporter\ExtendedPprofBuilder.cs">
<Link>Exporter\ExtendedPprofBuilder.cs</Link>
</Compile>
<Compile Include="..\TestApplication.ContinuousProfiler\Exporter\OtlpOverHttpExporter.cs">
<Link>Exporter\OtlpOverHttpExporter.cs</Link>
</Compile>
<Compile Include="..\TestApplication.ContinuousProfiler\Exporter\SampleBuilder.cs">
<Link>Exporter\SampleBuilder.cs</Link>
</Compile>
<Compile Include="..\TestApplication.ContinuousProfiler\Exporter\SampleNativeFormatParser.cs">
<Link>Exporter\SampleNativeFormatParser.cs</Link>
</Compile>
Expand All @@ -23,4 +37,10 @@
</Compile>
</ItemGroup>

<ItemGroup>
<Protobuf Include="..\..\..\IntegrationTests\opentelemetry\proto\common\v1\common.proto" Link="opentelemetry\proto\common\v1\common.proto" ProtoRoot="..\..\..\IntegrationTests" Access="internal" />
<Protobuf Include="..\..\..\IntegrationTests\opentelemetry\proto\resource\v1\resource.proto" Link="opentelemetry\proto\resource\v1\resource.proto" ProtoRoot="..\..\..\IntegrationTests" Access="internal" />
<Protobuf Include="..\..\..\IntegrationTests\opentelemetry\proto\profiles\v1development\profiles.proto" Link="opentelemetry\proto\profiles\v1development\profiles.proto" ProtoRoot="..\..\..\IntegrationTests" Access="internal" />
<Protobuf Include="..\..\..\IntegrationTests\opentelemetry\proto\collector\profiles\v1development\profiles_service.proto" Link="opentelemetry\proto\collector\profiles\v1development\profiles_service.proto" ProtoRoot="..\..\..\IntegrationTests" Access="internal" />
</ItemGroup>
</Project>

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public Tuple<bool, uint, bool, uint, TimeSpan, TimeSpan, object> GetContinuousPr
var maxMemorySamplesPerMinute = 200u;
var exportInterval = TimeSpan.FromMilliseconds(500);
var exportTimeout = TimeSpan.FromMilliseconds(500);
object continuousProfilerExporter = new TestExporter();
object continuousProfilerExporter = new OtlpOverHttpExporter();

return Tuple.Create(threadSamplingEnabled, threadSamplingInterval, allocationSamplingEnabled, maxMemorySamplesPerMinute, exportInterval, exportTimeout, continuousProfilerExporter);
}
Expand Down
Loading