Skip to content

Commit 42cbf91

Browse files
committed
Add metrics-only and trace-only integration tests
1 parent efa14dc commit 42cbf91

File tree

4 files changed

+166
-65
lines changed

4 files changed

+166
-65
lines changed

src/OpenTelemetry.Instrumentation.Kusto/Implementation/KustoTraceRecordListener.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,12 @@ private void HandleActivityComplete(KustoUtils.TraceRecord record)
180180
var result = TraceRecordParser.ParseActivityComplete(record.Message.AsSpan());
181181
if (result.HowEnded.Equals("Success".AsSpan(), StringComparison.Ordinal))
182182
{
183-
activity.SetStatus(ActivityStatusCode.Ok);
183+
activity?.SetStatus(ActivityStatusCode.Ok);
184184
}
185185

186-
activity.AddTags(context.Value.Tags);
186+
activity?.AddTags(context.Value.Tags);
187187
this.CallEnrichment(record);
188-
activity.Stop();
188+
activity?.Stop();
189189

190190
var duration = activity?.Duration.TotalSeconds ?? GetElapsedTime(context.Value.BeginTimestamp);
191191
KustoActivitySourceHelper.OperationDurationHistogram.Record(duration, context.Value.Tags);
@@ -203,6 +203,9 @@ private void HandleActivityComplete(KustoUtils.TraceRecord record)
203203
return null;
204204
}
205205

206+
/// <summary>
207+
/// Holds context data for an ongoing operation.
208+
/// </summary>
206209
private readonly struct ContextData
207210
{
208211
public ContextData(long beginTimestamp, TagList tags, Activity activity)
@@ -212,10 +215,23 @@ public ContextData(long beginTimestamp, TagList tags, Activity activity)
212215
this.Activity = activity;
213216
}
214217

218+
/// <summary>
219+
/// Gets the timestamp when the operation began. Used to compute duration if the <see cref="Activity"/>
220+
/// is not available (i.e. in a metrics-only scenario).
221+
/// </summary>
215222
public long BeginTimestamp { get; }
216223

224+
/// <summary>
225+
/// Gets the collection of tags associated with the operation that should be shared between the span and metrics.
226+
/// </summary>
217227
public TagList Tags { get; }
218228

219-
public Activity Activity { get; }
229+
/// <summary>
230+
/// Gets the current activity associated with the instance, if any.
231+
/// </summary>
232+
/// <remarks>
233+
/// Will be <see langword="null"/> in a metrics-only scenario.
234+
/// </remarks>
235+
public Activity? Activity { get; }
220236
}
221237
}

test/OpenTelemetry.Instrumentation.Kusto.Tests/KustoIntegrationTests.cs

Lines changed: 102 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -61,40 +61,88 @@ public async Task SuccessfulQueryTest(string query, bool processQuery)
6161
tracerProvider.ForceFlush();
6262
meterProvider.ForceFlush();
6363

64-
var activitySnapshots = activities
65-
.Where(activity => activity.Source == KustoActivitySourceHelper.ActivitySource)
66-
.Select(activity => new
64+
await Verify(
65+
new
6766
{
68-
activity.DisplayName,
69-
activity.Source.Name,
70-
activity.Status,
71-
activity.StatusDescription,
72-
activity.TagObjects,
73-
activity.OperationName,
74-
activity.IdFormat,
75-
});
67+
Activities = FilterActivites(activities),
68+
Metrics = FilterMetrics(metrics),
69+
})
70+
.ScrubHostname(kcsb.Hostname)
71+
.ScrubPort(this.fixture.DatabaseContainer.GetMappedPublicPort())
72+
.UseDirectory("Snapshots")
73+
.UseParameters(query, processQuery);
74+
}
7675

77-
var metricSnapshots = metrics
78-
.Where(metric => metric.MeterName == KustoActivitySourceHelper.MeterName)
79-
.Select(metric => new
76+
[EnabledOnDockerPlatformTheory(DockerPlatform.Linux)]
77+
[InlineData("print number=42")]
78+
public async Task TraceOnlyTest(string query)
79+
{
80+
var activities = new List<Activity>();
81+
82+
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
83+
.AddInMemoryExporter(activities)
84+
.AddKustoInstrumentation()
85+
.Build();
86+
87+
var kcsb = this.fixture.ConnectionStringBuilder;
88+
using var queryProvider = KustoClientFactory.CreateCslQueryProvider(kcsb);
89+
90+
var crp = new ClientRequestProperties()
91+
{
92+
// Ensure a stable client ID for snapshots
93+
ClientRequestId = Convert.ToBase64String(Encoding.UTF8.GetBytes(query)),
94+
};
95+
96+
using var reader = queryProvider.ExecuteQuery("NetDefaultDB", query, crp);
97+
reader.Consume();
98+
99+
tracerProvider.ForceFlush();
100+
101+
await Verify(
102+
new
80103
{
81-
metric.Name,
82-
metric.Description,
83-
metric.MeterTags,
84-
metric.Unit,
85-
metric.Temporality,
86-
});
104+
Activities = FilterActivites(activities),
105+
})
106+
.ScrubHostname(kcsb.Hostname)
107+
.ScrubPort(this.fixture.DatabaseContainer.GetMappedPublicPort())
108+
.UseDirectory("Snapshots")
109+
.UseParameters(query);
110+
}
111+
112+
[EnabledOnDockerPlatformTheory(DockerPlatform.Linux)]
113+
[InlineData("print number=42")]
114+
public async Task MetricsOnlyTest(string query)
115+
{
116+
var metrics = new List<Metric>();
117+
118+
using var meterProvider = Sdk.CreateMeterProviderBuilder()
119+
.AddInMemoryExporter(metrics)
120+
.AddKustoInstrumentation()
121+
.Build();
122+
123+
var kcsb = this.fixture.ConnectionStringBuilder;
124+
using var queryProvider = KustoClientFactory.CreateCslQueryProvider(kcsb);
125+
126+
var crp = new ClientRequestProperties()
127+
{
128+
// Ensure a stable client ID for snapshots
129+
ClientRequestId = Convert.ToBase64String(Encoding.UTF8.GetBytes(query)),
130+
};
131+
132+
using var reader = queryProvider.ExecuteQuery("NetDefaultDB", query, crp);
133+
reader.Consume();
134+
135+
meterProvider.ForceFlush();
87136

88137
await Verify(
89138
new
90139
{
91-
Activities = activitySnapshots,
92-
Metrics = metricSnapshots,
140+
Metrics = FilterMetrics(metrics),
93141
})
94142
.ScrubHostname(kcsb.Hostname)
95143
.ScrubPort(this.fixture.DatabaseContainer.GetMappedPublicPort())
96144
.UseDirectory("Snapshots")
97-
.UseParameters(query, processQuery);
145+
.UseParameters(query);
98146
}
99147

100148
[EnabledOnDockerPlatformTheory(DockerPlatform.Linux)]
@@ -140,35 +188,11 @@ public async Task FailedQueryTest(string query, bool processQuery)
140188
tracerProvider.ForceFlush();
141189
meterProvider.ForceFlush();
142190

143-
var activitySnapshots = activities
144-
.Where(activity => activity.Source == KustoActivitySourceHelper.ActivitySource)
145-
.Select(activity => new
146-
{
147-
activity.DisplayName,
148-
activity.Source.Name,
149-
activity.Status,
150-
activity.StatusDescription,
151-
activity.TagObjects,
152-
activity.OperationName,
153-
activity.IdFormat,
154-
});
155-
156-
var metricSnapshots = metrics
157-
.Where(metric => metric.MeterName == KustoActivitySourceHelper.MeterName)
158-
.Select(metric => new
159-
{
160-
metric.Name,
161-
metric.Description,
162-
metric.MeterTags,
163-
metric.Unit,
164-
metric.Temporality,
165-
});
166-
167191
await Verify(
168192
new
169193
{
170-
Activities = activitySnapshots,
171-
Metrics = metricSnapshots,
194+
Activities = FilterActivites(activities),
195+
Metrics = FilterMetrics(metrics),
172196
Exception = new
173197
{
174198
Type = exception.GetType().FullName,
@@ -212,17 +236,8 @@ public void NoInstrumentationRegistered_NoEventsEmitted()
212236
tracerProvider.ForceFlush();
213237
meterProvider.ForceFlush();
214238

215-
// Assert - No Kusto activities or metrics should be emitted
216-
var kustoActivities = activities
217-
.Where(activity => activity.Source == KustoActivitySourceHelper.ActivitySource)
218-
.ToList();
219-
220-
var kustoMetrics = metrics
221-
.Where(metric => metric.MeterName == KustoActivitySourceHelper.MeterName)
222-
.ToList();
223-
224-
Assert.Empty(kustoActivities);
225-
Assert.Empty(kustoMetrics);
239+
Assert.Empty(FilterActivites(activities));
240+
Assert.Empty(FilterMetrics(metrics));
226241
}
227242

228243
[EnabledOnDockerPlatformFact(DockerPlatform.Linux)]
@@ -293,11 +308,37 @@ public void EnrichCallbackTest()
293308
var activity = kustoActivities[0];
294309

295310
// Verify the custom summary was set by the Enrich callback
296-
var querySummaryTag = activity.Tags.SingleOrDefault(t => t.Key == SemanticConventions.AttributeDbQuerySummary);
311+
var querySummaryTag = activity.TagObjects.SingleOrDefault(t => t.Key == SemanticConventions.AttributeDbQuerySummary);
297312
Assert.NotNull(querySummaryTag.Key);
298313
Assert.Equal(summary, querySummaryTag.Value);
299314

300315
// Verify the display name was set to the custom summary
301316
Assert.Equal(summary, activity.DisplayName);
302317
}
318+
319+
private static dynamic FilterActivites(IEnumerable<Activity> activities) =>
320+
activities
321+
.Where(activity => activity.Source == KustoActivitySourceHelper.ActivitySource)
322+
.Select(activity => new
323+
{
324+
activity.DisplayName,
325+
activity.Source.Name,
326+
activity.Status,
327+
activity.StatusDescription,
328+
activity.TagObjects,
329+
activity.OperationName,
330+
activity.IdFormat,
331+
});
332+
333+
private static dynamic FilterMetrics(IEnumerable<Metric> metrics) =>
334+
metrics
335+
.Where(metric => metric.MeterName == KustoActivitySourceHelper.MeterName)
336+
.Select(metric => new
337+
{
338+
metric.Name,
339+
metric.Description,
340+
metric.MeterTags,
341+
metric.Unit,
342+
metric.Temporality,
343+
});
303344
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
Metrics: [
3+
{
4+
Name: db.client.operation.duration,
5+
Description: Duration of database client operations,
6+
Unit: s,
7+
Temporality: Cumulative
8+
}
9+
]
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
Activities: [
3+
{
4+
DisplayName: KD.RestClient.ExecuteQuery,
5+
Name: Kusto.Client,
6+
Status: Ok,
7+
TagObjects: [
8+
{
9+
kusto.client_request_id: cHJpbnQgbnVtYmVyPTQy
10+
},
11+
{
12+
db.system.name: azure.kusto
13+
},
14+
{
15+
db.operation.name: KD.RestClient.ExecuteQuery
16+
},
17+
{
18+
server.address: Scrubbed
19+
},
20+
{
21+
server.port: Scrubbed
22+
},
23+
{
24+
db.namespace: NetDefaultDB
25+
},
26+
{
27+
db.query.text: print number=?
28+
}
29+
],
30+
OperationName: KD.RestClient.ExecuteQuery,
31+
IdFormat: W3C
32+
}
33+
]
34+
}

0 commit comments

Comments
 (0)