643 lines
23 KiB
C#
643 lines
23 KiB
C#
// <copyright file="MetricTests.cs" company="OpenTelemetry Authors">
|
|
// Copyright The OpenTelemetry Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
// </copyright>
|
|
|
|
#if NET8_0_OR_GREATER
|
|
using System.Threading.RateLimiting;
|
|
using Microsoft.AspNetCore.Builder;
|
|
#endif
|
|
using Microsoft.AspNetCore.Hosting;
|
|
#if NET8_0_OR_GREATER
|
|
using Microsoft.AspNetCore.Http;
|
|
#endif
|
|
using Microsoft.AspNetCore.Mvc.Testing;
|
|
#if NET8_0_OR_GREATER
|
|
using Microsoft.AspNetCore.RateLimiting;
|
|
#endif
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
using OpenTelemetry.Metrics;
|
|
using OpenTelemetry.Trace;
|
|
using Xunit;
|
|
|
|
namespace OpenTelemetry.Instrumentation.AspNetCore.Tests;
|
|
|
|
public class MetricTests
|
|
: IClassFixture<WebApplicationFactory<Program>>, IDisposable
|
|
{
|
|
public const string SemanticConventionOptInKeyName = "OTEL_SEMCONV_STABILITY_OPT_IN";
|
|
|
|
private const int StandardTagsCount = 6;
|
|
|
|
private readonly WebApplicationFactory<Program> factory;
|
|
private MeterProvider meterProvider;
|
|
|
|
public MetricTests(WebApplicationFactory<Program> factory)
|
|
{
|
|
this.factory = factory;
|
|
}
|
|
|
|
[Fact]
|
|
public void AddAspNetCoreInstrumentation_BadArgs()
|
|
{
|
|
MeterProviderBuilder builder = null;
|
|
Assert.Throws<ArgumentNullException>(() => builder.AddAspNetCoreInstrumentation());
|
|
}
|
|
|
|
#if NET8_0_OR_GREATER
|
|
[Fact]
|
|
public async Task ValidateNet8MetricsAsync()
|
|
{
|
|
var metricItems = new List<Metric>();
|
|
|
|
this.meterProvider = Sdk.CreateMeterProviderBuilder()
|
|
.AddAspNetCoreInstrumentation()
|
|
.AddInMemoryExporter(metricItems)
|
|
.Build();
|
|
|
|
var builder = WebApplication.CreateBuilder();
|
|
builder.Logging.ClearProviders();
|
|
var app = builder.Build();
|
|
|
|
app.MapGet("/", () => "Hello");
|
|
|
|
_ = app.RunAsync();
|
|
|
|
using var client = new HttpClient();
|
|
var res = await client.GetStringAsync("http://localhost:5000/").ConfigureAwait(false);
|
|
Assert.NotNull(res);
|
|
|
|
// We need to let metric callback execute as it is executed AFTER response was returned.
|
|
// In unit tests environment there may be a lot of parallel unit tests executed, so
|
|
// giving some breezing room for the callbacks to complete
|
|
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
|
|
|
this.meterProvider.Dispose();
|
|
|
|
var requestDurationMetric = metricItems
|
|
.Count(item => item.Name == "http.server.request.duration");
|
|
|
|
var activeRequestsMetric = metricItems.
|
|
Count(item => item.Name == "http.server.active_requests");
|
|
|
|
var routeMatchingMetric = metricItems.
|
|
Count(item => item.Name == "aspnetcore.routing.match_attempts");
|
|
|
|
var kestrelActiveConnectionsMetric = metricItems.
|
|
Count(item => item.Name == "kestrel.active_connections");
|
|
|
|
var kestrelQueuedConnectionMetric = metricItems.
|
|
Count(item => item.Name == "kestrel.queued_connections");
|
|
|
|
Assert.Equal(1, requestDurationMetric);
|
|
Assert.Equal(1, activeRequestsMetric);
|
|
Assert.Equal(1, routeMatchingMetric);
|
|
Assert.Equal(1, kestrelActiveConnectionsMetric);
|
|
Assert.Equal(1, kestrelQueuedConnectionMetric);
|
|
|
|
// TODO
|
|
// kestrel.queued_requests
|
|
// kestrel.upgraded_connections
|
|
// kestrel.rejected_connections
|
|
// kestrel.tls_handshake.duration
|
|
// kestrel.active_tls_handshakes
|
|
|
|
await app.DisposeAsync();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ValidateNet8RateLimitingMetricsAsync()
|
|
{
|
|
var metricItems = new List<Metric>();
|
|
|
|
this.meterProvider = Sdk.CreateMeterProviderBuilder()
|
|
.AddAspNetCoreInstrumentation()
|
|
.AddInMemoryExporter(metricItems)
|
|
.Build();
|
|
|
|
var builder = WebApplication.CreateBuilder();
|
|
builder.Services.AddRateLimiter(_ => _
|
|
.AddFixedWindowLimiter(policyName: "fixed", options =>
|
|
{
|
|
options.PermitLimit = 4;
|
|
options.Window = TimeSpan.FromSeconds(12);
|
|
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
|
|
options.QueueLimit = 2;
|
|
}));
|
|
|
|
builder.Logging.ClearProviders();
|
|
var app = builder.Build();
|
|
|
|
app.UseRateLimiter();
|
|
|
|
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
|
|
|
|
app.MapGet("/", () => Results.Ok($"Hello {GetTicks()}"))
|
|
.RequireRateLimiting("fixed");
|
|
|
|
_ = app.RunAsync();
|
|
|
|
using var client = new HttpClient();
|
|
var res = await client.GetStringAsync("http://localhost:5000/").ConfigureAwait(false);
|
|
Assert.NotNull(res);
|
|
|
|
// We need to let metric callback execute as it is executed AFTER response was returned.
|
|
// In unit tests environment there may be a lot of parallel unit tests executed, so
|
|
// giving some breezing room for the callbacks to complete
|
|
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
|
|
|
this.meterProvider.Dispose();
|
|
|
|
var activeRequestleasesMetric = metricItems
|
|
.Where(item => item.Name == "aspnetcore.rate_limiting.active_request_leases")
|
|
.ToArray();
|
|
|
|
var requestLeaseDurationMetric = metricItems.
|
|
Where(item => item.Name == "aspnetcore.rate_limiting.request_lease.duration")
|
|
.ToArray();
|
|
|
|
var limitingRequestsMetric = metricItems.
|
|
Where(item => item.Name == "aspnetcore.rate_limiting.requests")
|
|
.ToArray();
|
|
|
|
Assert.Single(activeRequestleasesMetric);
|
|
Assert.Single(requestLeaseDurationMetric);
|
|
Assert.Single(limitingRequestsMetric);
|
|
|
|
// TODO
|
|
// aspnetcore.rate_limiting.request.time_in_queue
|
|
// aspnetcore.rate_limiting.queued_requests
|
|
|
|
await app.DisposeAsync();
|
|
}
|
|
#endif
|
|
|
|
[Theory]
|
|
[InlineData("/api/values/2", "api/Values/{id}", null, 200)]
|
|
[InlineData("/api/Error", "api/Error", "System.Exception", 500)]
|
|
public async Task RequestMetricIsCaptured_New(string api, string expectedRoute, string expectedErrorType, int expectedStatusCode)
|
|
{
|
|
var configuration = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(new Dictionary<string, string> { [SemanticConventionOptInKeyName] = "http" })
|
|
.Build();
|
|
|
|
var metricItems = new List<Metric>();
|
|
|
|
this.meterProvider = Sdk.CreateMeterProviderBuilder()
|
|
.ConfigureServices(services => services.AddSingleton<IConfiguration>(configuration))
|
|
.AddAspNetCoreInstrumentation()
|
|
.AddInMemoryExporter(metricItems)
|
|
.Build();
|
|
|
|
using (var client = this.factory
|
|
.WithWebHostBuilder(builder =>
|
|
{
|
|
builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
|
|
})
|
|
.CreateClient())
|
|
{
|
|
try
|
|
{
|
|
using var response = await client.GetAsync(api).ConfigureAwait(false);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
catch
|
|
{
|
|
// ignore error.
|
|
}
|
|
}
|
|
|
|
// We need to let End callback execute as it is executed AFTER response was returned.
|
|
// In unit tests environment there may be a lot of parallel unit tests executed, so
|
|
// giving some breezing room for the End callback to complete
|
|
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
|
|
|
this.meterProvider.Dispose();
|
|
|
|
var requestMetrics = metricItems
|
|
.Where(item => item.Name == "http.server.request.duration")
|
|
.ToArray();
|
|
|
|
var metric = Assert.Single(requestMetrics);
|
|
|
|
Assert.Equal("s", metric.Unit);
|
|
var metricPoints = GetMetricPoints(metric);
|
|
Assert.Single(metricPoints);
|
|
|
|
AssertMetricPoints_New(
|
|
metricPoints: metricPoints,
|
|
expectedRoutes: new List<string> { expectedRoute },
|
|
expectedErrorType,
|
|
expectedStatusCode,
|
|
expectedTagsCount: expectedErrorType == null ? 5 : 6);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("CONNECT", "CONNECT")]
|
|
[InlineData("DELETE", "DELETE")]
|
|
[InlineData("GET", "GET")]
|
|
[InlineData("PUT", "PUT")]
|
|
[InlineData("HEAD", "HEAD")]
|
|
[InlineData("OPTIONS", "OPTIONS")]
|
|
[InlineData("PATCH", "PATCH")]
|
|
[InlineData("Get", "GET")]
|
|
[InlineData("POST", "POST")]
|
|
[InlineData("TRACE", "TRACE")]
|
|
[InlineData("CUSTOM", "_OTHER")]
|
|
public async Task HttpRequestMethodIsCapturedAsPerSpec(string originalMethod, string expectedMethod)
|
|
{
|
|
var configuration = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(new Dictionary<string, string> { [SemanticConventionOptInKeyName] = "http" })
|
|
.Build();
|
|
|
|
var metricItems = new List<Metric>();
|
|
|
|
this.meterProvider = Sdk.CreateMeterProviderBuilder()
|
|
.ConfigureServices(services => services.AddSingleton<IConfiguration>(configuration))
|
|
.AddAspNetCoreInstrumentation()
|
|
.AddInMemoryExporter(metricItems)
|
|
.Build();
|
|
|
|
using var client = this.factory
|
|
.WithWebHostBuilder(builder =>
|
|
{
|
|
builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
|
|
})
|
|
.CreateClient();
|
|
|
|
var message = new HttpRequestMessage();
|
|
message.Method = new HttpMethod(originalMethod);
|
|
|
|
try
|
|
{
|
|
using var response = await client.SendAsync(message).ConfigureAwait(false);
|
|
}
|
|
catch
|
|
{
|
|
// ignore error.
|
|
}
|
|
|
|
// We need to let End callback execute as it is executed AFTER response was returned.
|
|
// In unit tests environment there may be a lot of parallel unit tests executed, so
|
|
// giving some breezing room for the End callback to complete
|
|
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
|
|
|
this.meterProvider.Dispose();
|
|
|
|
var requestMetrics = metricItems
|
|
.Where(item => item.Name == "http.server.request.duration")
|
|
.ToArray();
|
|
|
|
var metric = Assert.Single(requestMetrics);
|
|
|
|
Assert.Equal("s", metric.Unit);
|
|
var metricPoints = GetMetricPoints(metric);
|
|
Assert.Single(metricPoints);
|
|
|
|
var mp = metricPoints[0];
|
|
|
|
// Inspect Metric Attributes
|
|
var attributes = new Dictionary<string, object>();
|
|
foreach (var tag in mp.Tags)
|
|
{
|
|
attributes[tag.Key] = tag.Value;
|
|
}
|
|
|
|
Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeHttpRequestMethod && kvp.Value.ToString() == expectedMethod);
|
|
|
|
Assert.DoesNotContain(attributes, t => t.Key == SemanticConventions.AttributeHttpRequestMethodOriginal);
|
|
}
|
|
|
|
#if !NET8_0_OR_GREATER
|
|
[Fact]
|
|
public async Task RequestMetricIsCaptured_Old()
|
|
{
|
|
var configuration = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(new Dictionary<string, string> { [SemanticConventionOptInKeyName] = null })
|
|
.Build();
|
|
|
|
var metricItems = new List<Metric>();
|
|
|
|
this.meterProvider = Sdk.CreateMeterProviderBuilder()
|
|
.ConfigureServices(services => services.AddSingleton<IConfiguration>(configuration))
|
|
.AddAspNetCoreInstrumentation()
|
|
.AddInMemoryExporter(metricItems)
|
|
.Build();
|
|
|
|
using (var client = this.factory
|
|
.WithWebHostBuilder(builder =>
|
|
{
|
|
builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
|
|
})
|
|
.CreateClient())
|
|
{
|
|
using var response1 = await client.GetAsync("/api/values").ConfigureAwait(false);
|
|
using var response2 = await client.GetAsync("/api/values/2").ConfigureAwait(false);
|
|
|
|
response1.EnsureSuccessStatusCode();
|
|
response2.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
// We need to let End callback execute as it is executed AFTER response was returned.
|
|
// In unit tests environment there may be a lot of parallel unit tests executed, so
|
|
// giving some breezing room for the End callback to complete
|
|
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
|
|
|
this.meterProvider.Dispose();
|
|
|
|
var requestMetrics = metricItems
|
|
.Where(item => item.Name == "http.server.duration")
|
|
.ToArray();
|
|
|
|
var metric = Assert.Single(requestMetrics);
|
|
Assert.Equal("ms", metric.Unit);
|
|
var metricPoints = GetMetricPoints(metric);
|
|
Assert.Equal(2, metricPoints.Count);
|
|
|
|
AssertMetricPoints_Old(
|
|
metricPoints: metricPoints,
|
|
expectedRoutes: new List<string> { "api/Values", "api/Values/{id}" },
|
|
expectedTagsCount: 6);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RequestMetricIsCaptured_Dup()
|
|
{
|
|
var configuration = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(new Dictionary<string, string> { [SemanticConventionOptInKeyName] = "http/dup" })
|
|
.Build();
|
|
|
|
var metricItems = new List<Metric>();
|
|
|
|
this.meterProvider = Sdk.CreateMeterProviderBuilder()
|
|
.ConfigureServices(services => services.AddSingleton<IConfiguration>(configuration))
|
|
.AddAspNetCoreInstrumentation()
|
|
.AddInMemoryExporter(metricItems)
|
|
.Build();
|
|
|
|
using (var client = this.factory
|
|
.WithWebHostBuilder(builder =>
|
|
{
|
|
builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
|
|
})
|
|
.CreateClient())
|
|
{
|
|
using var response1 = await client.GetAsync("/api/values").ConfigureAwait(false);
|
|
using var response2 = await client.GetAsync("/api/values/2").ConfigureAwait(false);
|
|
|
|
response1.EnsureSuccessStatusCode();
|
|
response2.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
// We need to let End callback execute as it is executed AFTER response was returned.
|
|
// In unit tests environment there may be a lot of parallel unit tests executed, so
|
|
// giving some breezing room for the End callback to complete
|
|
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
|
|
|
this.meterProvider.Dispose();
|
|
|
|
// Validate Old Semantic Convention
|
|
var requestMetrics = metricItems
|
|
.Where(item => item.Name == "http.server.duration")
|
|
.ToArray();
|
|
|
|
var metric = Assert.Single(requestMetrics);
|
|
Assert.Equal("ms", metric.Unit);
|
|
var metricPoints = GetMetricPoints(metric);
|
|
Assert.Equal(2, metricPoints.Count);
|
|
|
|
AssertMetricPoints_Old(
|
|
metricPoints: metricPoints,
|
|
expectedRoutes: new List<string> { "api/Values", "api/Values/{id}" },
|
|
expectedTagsCount: 6);
|
|
|
|
// Validate New Semantic Convention
|
|
requestMetrics = metricItems
|
|
.Where(item => item.Name == "http.server.request.duration")
|
|
.ToArray();
|
|
|
|
metric = Assert.Single(requestMetrics);
|
|
|
|
Assert.Equal("s", metric.Unit);
|
|
metricPoints = GetMetricPoints(metric);
|
|
Assert.Equal(2, metricPoints.Count);
|
|
|
|
AssertMetricPoints_New(
|
|
metricPoints: metricPoints,
|
|
expectedRoutes: new List<string> { "api/Values", "api/Values/{id}" },
|
|
null,
|
|
200,
|
|
expectedTagsCount: 5);
|
|
}
|
|
#endif
|
|
|
|
public void Dispose()
|
|
{
|
|
this.meterProvider?.Dispose();
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
private static List<MetricPoint> GetMetricPoints(Metric metric)
|
|
{
|
|
Assert.NotNull(metric);
|
|
Assert.True(metric.MetricType == MetricType.Histogram);
|
|
var metricPoints = new List<MetricPoint>();
|
|
foreach (var p in metric.GetMetricPoints())
|
|
{
|
|
metricPoints.Add(p);
|
|
}
|
|
|
|
return metricPoints;
|
|
}
|
|
|
|
private static void AssertMetricPoints_New(
|
|
List<MetricPoint> metricPoints,
|
|
List<string> expectedRoutes,
|
|
string expectedErrorType,
|
|
int expectedStatusCode,
|
|
int expectedTagsCount)
|
|
{
|
|
// Assert that one MetricPoint exists for each ExpectedRoute
|
|
foreach (var expectedRoute in expectedRoutes)
|
|
{
|
|
MetricPoint? metricPoint = null;
|
|
|
|
foreach (var mp in metricPoints)
|
|
{
|
|
foreach (var tag in mp.Tags)
|
|
{
|
|
if (tag.Key == SemanticConventions.AttributeHttpRoute && tag.Value.ToString() == expectedRoute)
|
|
{
|
|
metricPoint = mp;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (metricPoint.HasValue)
|
|
{
|
|
AssertMetricPoint_New(metricPoint.Value, expectedStatusCode, expectedRoute, expectedErrorType, expectedTagsCount);
|
|
}
|
|
else
|
|
{
|
|
Assert.Fail($"A metric for route '{expectedRoute}' was not found");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void AssertMetricPoints_Old(
|
|
List<MetricPoint> metricPoints,
|
|
List<string> expectedRoutes,
|
|
int expectedTagsCount)
|
|
{
|
|
// Assert that one MetricPoint exists for each ExpectedRoute
|
|
foreach (var expectedRoute in expectedRoutes)
|
|
{
|
|
MetricPoint? metricPoint = null;
|
|
|
|
foreach (var mp in metricPoints)
|
|
{
|
|
foreach (var tag in mp.Tags)
|
|
{
|
|
if (tag.Key == SemanticConventions.AttributeHttpRoute && tag.Value.ToString() == expectedRoute)
|
|
{
|
|
metricPoint = mp;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (metricPoint.HasValue)
|
|
{
|
|
AssertMetricPoint_Old(metricPoint.Value, expectedRoute, expectedTagsCount);
|
|
}
|
|
else
|
|
{
|
|
Assert.Fail($"A metric for route '{expectedRoute}' was not found");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static KeyValuePair<string, object>[] AssertMetricPoint_New(
|
|
MetricPoint metricPoint,
|
|
int expectedStatusCode,
|
|
string expectedRoute,
|
|
string expectedErrorType,
|
|
int expectedTagsCount)
|
|
{
|
|
var count = metricPoint.GetHistogramCount();
|
|
var sum = metricPoint.GetHistogramSum();
|
|
|
|
Assert.Equal(1L, count);
|
|
Assert.True(sum > 0);
|
|
|
|
var attributes = new KeyValuePair<string, object>[metricPoint.Tags.Count];
|
|
int i = 0;
|
|
foreach (var tag in metricPoint.Tags)
|
|
{
|
|
attributes[i++] = tag;
|
|
}
|
|
|
|
// Inspect Attributes
|
|
Assert.Equal(expectedTagsCount, attributes.Length);
|
|
|
|
var method = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpRequestMethod, "GET");
|
|
var scheme = new KeyValuePair<string, object>(SemanticConventions.AttributeUrlScheme, "http");
|
|
var statusCode = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpResponseStatusCode, expectedStatusCode);
|
|
var flavor = new KeyValuePair<string, object>(SemanticConventions.AttributeNetworkProtocolVersion, "1.1");
|
|
var route = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpRoute, expectedRoute);
|
|
Assert.Contains(method, attributes);
|
|
Assert.Contains(scheme, attributes);
|
|
Assert.Contains(statusCode, attributes);
|
|
Assert.Contains(flavor, attributes);
|
|
Assert.Contains(route, attributes);
|
|
|
|
if (expectedErrorType != null)
|
|
{
|
|
var errorType = new KeyValuePair<string, object>(SemanticConventions.AttributeErrorType, expectedErrorType);
|
|
|
|
Assert.Contains(errorType, attributes);
|
|
}
|
|
|
|
// Inspect Histogram Bounds
|
|
var histogramBuckets = metricPoint.GetHistogramBuckets();
|
|
var histogramBounds = new List<double>();
|
|
foreach (var t in histogramBuckets)
|
|
{
|
|
histogramBounds.Add(t.ExplicitBound);
|
|
}
|
|
|
|
// TODO: Remove the check for the older bounds once 1.7.0 is released. This is a temporary fix for instrumentation libraries CI workflow.
|
|
|
|
var expectedHistogramBoundsOld = new List<double> { 0, 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, double.PositiveInfinity };
|
|
var expectedHistogramBoundsNew = new List<double> { 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, double.PositiveInfinity };
|
|
|
|
var histogramBoundsMatchCorrectly = Enumerable.SequenceEqual(expectedHistogramBoundsOld, histogramBounds) ||
|
|
Enumerable.SequenceEqual(expectedHistogramBoundsNew, histogramBounds);
|
|
|
|
Assert.True(histogramBoundsMatchCorrectly);
|
|
|
|
return attributes;
|
|
}
|
|
|
|
private static KeyValuePair<string, object>[] AssertMetricPoint_Old(
|
|
MetricPoint metricPoint,
|
|
string expectedRoute = "api/Values",
|
|
int expectedTagsCount = StandardTagsCount)
|
|
{
|
|
var count = metricPoint.GetHistogramCount();
|
|
var sum = metricPoint.GetHistogramSum();
|
|
|
|
Assert.Equal(1L, count);
|
|
Assert.True(sum > 0);
|
|
|
|
var attributes = new KeyValuePair<string, object>[metricPoint.Tags.Count];
|
|
int i = 0;
|
|
foreach (var tag in metricPoint.Tags)
|
|
{
|
|
attributes[i++] = tag;
|
|
}
|
|
|
|
// Inspect Attributes
|
|
Assert.Equal(expectedTagsCount, attributes.Length);
|
|
|
|
var method = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpMethod, "GET");
|
|
var scheme = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpScheme, "http");
|
|
var statusCode = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpStatusCode, 200);
|
|
var flavor = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpFlavor, "1.1");
|
|
var host = new KeyValuePair<string, object>(SemanticConventions.AttributeNetHostName, "localhost");
|
|
var route = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpRoute, expectedRoute);
|
|
Assert.Contains(method, attributes);
|
|
Assert.Contains(scheme, attributes);
|
|
Assert.Contains(statusCode, attributes);
|
|
Assert.Contains(flavor, attributes);
|
|
Assert.Contains(host, attributes);
|
|
Assert.Contains(route, attributes);
|
|
|
|
// Inspect Histogram Bounds
|
|
var histogramBuckets = metricPoint.GetHistogramBuckets();
|
|
var histogramBounds = new List<double>();
|
|
foreach (var t in histogramBuckets)
|
|
{
|
|
histogramBounds.Add(t.ExplicitBound);
|
|
}
|
|
|
|
Assert.Equal(
|
|
expected: new List<double> { 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000, double.PositiveInfinity },
|
|
actual: histogramBounds);
|
|
|
|
return attributes;
|
|
}
|
|
}
|