Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<PackageVersion Include="Npgsql" Version="8.0.7" />
<PackageVersion Include="OpenTelemetry" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Api" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Exporter.InMemory" Version="1.15.3" />
<PackageVersion Include="Polly" Version="8.6.5" />
<PackageVersion Include="ProjNET" Version="2.0.0" />
<PackageVersion Include="RabbitMQ.Client" Version="7.0.0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Diagnostics;
using System.Text.RegularExpressions;
using HotChocolate.Utilities;
using Newtonsoft.Json;
using OpenTelemetry;
using OpenTelemetry.Trace;

namespace HotChocolate.Diagnostics;

Expand All @@ -13,94 +15,45 @@ public static partial class ActivityTestHelper

public static IDisposable CaptureActivities(out object activities)
{
var sync = new object();
var listener = new ActivityListener();
var root = new OrderedDictionary<string, object?>();
var lookup = new Dictionary<Activity, OrderedDictionary<string, object?>>();
var spanLookup = new Dictionary<ActivitySpanId, OrderedDictionary<string, object?>>();
Activity rootActivity = null!;

listener.ShouldListenTo = source => source.Name.EqualsOrdinal("HotChocolate.Diagnostics");
listener.ActivityStarted = a =>
{
lock (sync)
{
if (a.Parent is null
&& a.OperationName.EqualsOrdinal("ExecuteHttpRequest")
&& lookup.TryGetValue(rootActivity, out var parentData))
{
RegisterActivity(a, parentData);
lookup[a] = (OrderedDictionary<string, object?>)a.GetCustomProperty("test.data")!;
}
var exported = new List<Activity>();

if (a.Parent is not null
&& lookup.TryGetValue(a.Parent, out parentData))
{
RegisterActivity(a, parentData);
lookup[a] = (OrderedDictionary<string, object?>)a.GetCustomProperty("test.data")!;
spanLookup[a.SpanId] = (OrderedDictionary<string, object?>)a.GetCustomProperty("test.data")!;
return;
}

if (a.Parent is null
&& a.ParentSpanId != default
&& spanLookup.TryGetValue(a.ParentSpanId, out parentData))
{
RegisterActivity(a, parentData);
lookup[a] = (OrderedDictionary<string, object?>)a.GetCustomProperty("test.data")!;
spanLookup[a.SpanId] = (OrderedDictionary<string, object?>)a.GetCustomProperty("test.data")!;
}
}
};
listener.ActivityStopped = SerializeActivity;
listener.Sample = (ref ActivityCreationOptions<ActivityContext> _) =>
ActivitySamplingResult.AllData;
ActivitySource.AddActivityListener(listener);

rootActivity = HotChocolateActivitySource.Source.StartActivity()!;
rootActivity.SetCustomProperty("test.data", root);
lookup[rootActivity] = root;
spanLookup[rootActivity.SpanId] = root;

activities = root;
return new Session(rootActivity, listener);
var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddHotChocolateInstrumentation()
.SetSampler(new AlwaysOnSampler())
.AddInMemoryExporter(exported)
.Build()!;

var capture = new Capture(tracerProvider, exported);
activities = capture;
return capture;
}

private static void RegisterActivity(
private static OrderedDictionary<string, object?> SerializeActivity(
Activity activity,
OrderedDictionary<string, object?> parent)
IReadOnlyDictionary<ActivitySpanId, List<Activity>> byParent)
{
if (!(parent.TryGetValue("activities", out var value) && value is List<object> children))
var data = new OrderedDictionary<string, object?>
{
children = [];
parent["activities"] = children;
}

var data = new OrderedDictionary<string, object?>();
activity.SetCustomProperty("test.data", data);
SerializeActivity(activity);
children.Add(data);
}

private static void SerializeActivity(Activity activity)
{
var data = (OrderedDictionary<string, object?>?)activity.GetCustomProperty("test.data");
["OperationName"] = activity.OperationName,
["DisplayName"] = activity.DisplayName,
["Kind"] = activity.Kind,
["Status"] = activity.Status,
["tags"] = activity.TagObjects,
["event"] = activity.Events.Select(e => new
{
e.Name,
Tags = ScrubEventTags(e.Tags)
})
};

if (data is null)
if (byParent.TryGetValue(activity.SpanId, out var children) && children.Count > 0)
{
return;
data["activities"] = children
.Select(c => SerializeActivity(c, byParent))
.ToList();
}

data["OperationName"] = activity.OperationName;
data["DisplayName"] = activity.DisplayName;
data["Kind"] = activity.Kind;
data["Status"] = activity.Status;
data["tags"] = activity.TagObjects;
data["event"] = activity.Events.Select(t => new
{
t.Name,
Tags = ScrubEventTags(t.Tags)
});
return data;
}

private static IEnumerable<KeyValuePair<string, object?>> ScrubEventTags(
Expand Down Expand Up @@ -134,21 +87,42 @@ private static void SerializeActivity(Activity activity)
}
}

private sealed class Session : IDisposable
private sealed class Capture : IDisposable
{
private readonly Activity _activity;
private readonly ActivityListener _listener;
private readonly TracerProvider _tracerProvider;
private readonly List<Activity> _exported;

public Session(Activity activity, ActivityListener listener)
public Capture(TracerProvider tracerProvider, List<Activity> exported)
{
_activity = activity;
_listener = listener;
_tracerProvider = tracerProvider;
_exported = exported;
}

public void Dispose()
[JsonProperty("source", Order = 0)]
public OrderedDictionary<string, object?> Source
=> new()
{
["name"] = _exported.FirstOrDefault()?.Source.Name
};

[JsonProperty("activities", Order = 1)]
public IReadOnlyList<OrderedDictionary<string, object?>> Activities
{
_activity.Dispose();
_listener.Dispose();
get
{
var spanIds = new HashSet<ActivitySpanId>(_exported.Select(a => a.SpanId));
var byParent = _exported
.GroupBy(a => a.ParentSpanId)
.ToDictionary(g => g.Key, g => g.OrderBy(a => a.StartTimeUtc).ToList());

return _exported
.Where(a => a.ParentSpanId == default || !spanIds.Contains(a.ParentSpanId))
.OrderBy(a => a.StartTimeUtc)
.Select(root => SerializeActivity(root, byParent))
.ToList();
}
}

public void Dispose() => _tracerProvider.Dispose();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
<ProjectReference Include="..\..\..\CostAnalysis\src\CostAnalysis\HotChocolate.CostAnalysis.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="OpenTelemetry" />
<PackageReference Include="OpenTelemetry.Exporter.InMemory" />
</ItemGroup>

<ItemGroup>
<None Update="__resources__\IntrospectionSchema.graphql">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL DataLoader Dispatch",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL DataLoader Dispatch",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Document Validation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Document Validation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Document Validation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Operation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"source": {
"name": "HotChocolate.Diagnostics"
},
"activities": [
{
"OperationName": "GraphQL Document Validation",
Expand Down
Loading
Loading