From 1d5426729a890ba30f5593cf17ee0d6a1cf0f0da Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Mon, 30 Sep 2024 11:18:28 +0200 Subject: [PATCH] Remove metrics actuator (breaking changes in AggregationManager) --- .../ActuatorServiceCollectionExtensions.cs | 2 - .../ConfigureMetricsEndpointOptions.cs | 14 - .../ConfigureMetricsObserverOptions.cs | 46 -- .../EndpointServiceCollectionExtensions.cs | 60 -- .../Metrics/IMetricsEndpointHandler.cs | 7 - .../Metrics/MetricCollectionHostedService.cs | 30 - .../Metrics/MetricLabelExtensions.cs | 15 - .../Actuators/Metrics/MetricSample.cs | 24 - .../Actuators/Metrics/MetricStatistic.cs | 23 - .../Endpoint/Actuators/Metrics/MetricTag.cs | 27 - .../Actuators/Metrics/MetricsCollection.cs | 19 - .../Metrics/MetricsEndpointHandler.cs | 146 ----- .../Metrics/MetricsEndpointMiddleware.cs | 117 ---- .../Metrics/MetricsEndpointOptions.cs | 36 -- .../Actuators/Metrics/MetricsExporter.cs | 131 ---- .../Metrics/MetricsExporterOptions.cs | 13 - .../Actuators/Metrics/MetricsRequest.cs | 21 - .../Actuators/Metrics/MetricsResponse.cs | 48 -- .../Observers/AspNetCoreHostingObserver.cs | 113 ---- .../Metrics/Observers/ClrRuntimeObserver.cs | 114 ---- .../Metrics/Observers/ClrRuntimeSource.cs | 44 -- .../Metrics/Observers/EventCounterListener.cs | 226 ------- .../Metrics/Observers/HttpClientObserver.cs | 140 ----- .../Metrics/Observers/MetricsObserver.cs | 30 - .../Metrics/ServiceCollectionExtensions.cs | 114 ---- .../Actuators/Metrics/SteeltoeMetrics.cs | 17 - .../AggregationManager.cs | 393 ------------ .../SystemDiagnosticsMetrics/Aggregator.cs | 68 --- .../AggregatorStore.cs | 520 ---------------- .../ExponentialHistogramAggregator.cs | 266 --------- .../InstrumentState.cs | 46 -- .../LastValueAggregator.cs | 38 -- .../ObjectSequence.cs | 125 ---- .../ObjectSequence.netcore.cs | 63 -- .../SystemDiagnosticsMetrics/README.md | 41 -- .../RateAggregator.cs | 70 --- .../StringSequence.cs | 100 ---- .../StringSequence.netcore.cs | 58 -- .../src/Endpoint/ConfigurationSchema.json | 51 -- .../Endpoint/HostBuilderWrapperExtensions.cs | 7 - ...agementHostApplicationBuilderExtensions.cs | 19 - .../ManagementHostBuilderExtensions.cs | 19 - .../ManagementWebHostBuilderExtensions.cs | 19 - .../src/Endpoint/Properties/AssemblyInfo.cs | 2 - .../src/Endpoint/PublicAPI.Unshipped.txt | 51 +- .../src/Prometheus/PrometheusExtensions.cs | 2 - .../src/Wavefront/WavefrontExtensions.cs | 3 - .../Metrics/AspNetCoreHostingObserverTest.cs | 95 --- ...EndpointServiceCollectionExtensionsTest.cs | 41 -- .../Actuators/Metrics/MetricSampleTest.cs | 33 - .../Actuators/Metrics/MetricTagTest.cs | 45 -- .../Metrics/MetricsEndpointMiddlewareTest.cs | 275 --------- .../Metrics/MetricsEndpointOptionsTest.cs | 34 -- .../Actuators/Metrics/MetricsEndpointTest.cs | 563 ------------------ .../Metrics/MetricsListNamesResponseTest.cs | 47 -- .../Metrics/MetricsObserverOptionsTest.cs | 56 -- .../Actuators/Metrics/MetricsRequestTest.cs | 19 - .../Actuators/Metrics/MetricsResponseTest.cs | 67 --- .../Observers/EventCounterListenerTest.cs | 164 ----- src/Management/test/Endpoint.Test/BaseTest.cs | 29 - .../ContentNegotiationTest.cs | 1 - .../ContentNegotiation/EndpointName.cs | 1 - .../ContentNegotiation/MetricsStartup.cs | 2 - .../TestStartupExtensions.cs | 1 - .../ManagementHostBuilderExtensionsTest.cs | 27 - ...mentWebApplicationBuilderExtensionsTest.cs | 19 - .../ManagementWebHostBuilderExtensionsTest.cs | 27 - 67 files changed, 3 insertions(+), 5081 deletions(-) delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/ConfigureMetricsEndpointOptions.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/ConfigureMetricsObserverOptions.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/EndpointServiceCollectionExtensions.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/IMetricsEndpointHandler.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/MetricCollectionHostedService.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/MetricLabelExtensions.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/MetricSample.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/MetricStatistic.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/MetricTag.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/MetricsCollection.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/MetricsEndpointHandler.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/MetricsEndpointMiddleware.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/MetricsEndpointOptions.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/MetricsExporter.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/MetricsExporterOptions.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/MetricsRequest.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/MetricsResponse.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/Observers/AspNetCoreHostingObserver.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/Observers/ClrRuntimeObserver.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/Observers/ClrRuntimeSource.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/Observers/EventCounterListener.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/Observers/HttpClientObserver.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/Observers/MetricsObserver.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/ServiceCollectionExtensions.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/SteeltoeMetrics.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/AggregationManager.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/Aggregator.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/AggregatorStore.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/ExponentialHistogramAggregator.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/InstrumentState.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/LastValueAggregator.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/ObjectSequence.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/ObjectSequence.netcore.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/README.md delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/RateAggregator.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/StringSequence.cs delete mode 100644 src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/StringSequence.netcore.cs delete mode 100644 src/Management/test/Endpoint.Test/Actuators/Metrics/AspNetCoreHostingObserverTest.cs delete mode 100644 src/Management/test/Endpoint.Test/Actuators/Metrics/EndpointServiceCollectionExtensionsTest.cs delete mode 100644 src/Management/test/Endpoint.Test/Actuators/Metrics/MetricSampleTest.cs delete mode 100644 src/Management/test/Endpoint.Test/Actuators/Metrics/MetricTagTest.cs delete mode 100644 src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsEndpointMiddlewareTest.cs delete mode 100644 src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsEndpointOptionsTest.cs delete mode 100644 src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsEndpointTest.cs delete mode 100644 src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsListNamesResponseTest.cs delete mode 100644 src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsObserverOptionsTest.cs delete mode 100644 src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsRequestTest.cs delete mode 100644 src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsResponseTest.cs delete mode 100644 src/Management/test/Endpoint.Test/Actuators/Metrics/Observers/EventCounterListenerTest.cs diff --git a/src/Management/src/Endpoint/ActuatorServiceCollectionExtensions.cs b/src/Management/src/Endpoint/ActuatorServiceCollectionExtensions.cs index 140968c862..b5a1427b20 100644 --- a/src/Management/src/Endpoint/ActuatorServiceCollectionExtensions.cs +++ b/src/Management/src/Endpoint/ActuatorServiceCollectionExtensions.cs @@ -19,7 +19,6 @@ using Steeltoe.Management.Endpoint.Actuators.Hypermedia; using Steeltoe.Management.Endpoint.Actuators.Info; using Steeltoe.Management.Endpoint.Actuators.Loggers; -using Steeltoe.Management.Endpoint.Actuators.Metrics; using Steeltoe.Management.Endpoint.Actuators.Refresh; using Steeltoe.Management.Endpoint.Actuators.RouteMappings; using Steeltoe.Management.Endpoint.Actuators.Services; @@ -100,7 +99,6 @@ public static IServiceCollection AddAllActuators(this IServiceCollection service services.AddLoggersActuator(); services.AddHttpExchangesActuator(); services.AddMappingsActuator(); - services.AddMetricsActuator(); services.AddRefreshActuator(); services.AddServicesActuator(); diff --git a/src/Management/src/Endpoint/Actuators/Metrics/ConfigureMetricsEndpointOptions.cs b/src/Management/src/Endpoint/Actuators/Metrics/ConfigureMetricsEndpointOptions.cs deleted file mode 100644 index 43a6724eba..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/ConfigureMetricsEndpointOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Configuration; -using Steeltoe.Management.Endpoint.Configuration; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -internal sealed class ConfigureMetricsEndpointOptions(IConfiguration configuration) - : ConfigureEndpointOptions(configuration, ManagementInfoPrefix, "metrics") -{ - private const string ManagementInfoPrefix = "management:endpoints:metrics"; -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/ConfigureMetricsObserverOptions.cs b/src/Management/src/Endpoint/Actuators/Metrics/ConfigureMetricsObserverOptions.cs deleted file mode 100644 index 08c8bb28b6..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/ConfigureMetricsObserverOptions.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Configuration; -using Steeltoe.Management.Diagnostics; -using Steeltoe.Management.Endpoint.Configuration; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -internal sealed class ConfigureMetricsObserverOptions : IConfigureOptionsWithKey -{ - private const string ManagementMetricsPrefix = "management:metrics:observer"; - - internal const string DefaultIngressIgnorePattern = - @"/cloudfoundryapplication|/cloudfoundryapplication/.*|.*\.png|.*\.css|.*\.js|.*\.html|/favicon.ico|.*\.gif"; - - internal const string DefaultEgressIgnorePattern = "/api/v2/spans|/v2/apps/.*/permissions"; - private readonly IConfiguration _configuration; - - public string ConfigurationKey => ManagementMetricsPrefix; - - public ConfigureMetricsObserverOptions(IConfiguration configuration) - { - ArgumentNullException.ThrowIfNull(configuration); - - _configuration = configuration; - } - - public void Configure(MetricsObserverOptions options) - { - ArgumentNullException.ThrowIfNull(options); - - _configuration.GetSection(ManagementMetricsPrefix).Bind(options); - - if (string.IsNullOrEmpty(options.IngressIgnorePattern)) - { - options.IngressIgnorePattern = DefaultIngressIgnorePattern; - } - - if (string.IsNullOrEmpty(options.EgressIgnorePattern)) - { - options.EgressIgnorePattern = DefaultEgressIgnorePattern; - } - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/EndpointServiceCollectionExtensions.cs b/src/Management/src/Endpoint/Actuators/Metrics/EndpointServiceCollectionExtensions.cs deleted file mode 100644 index 4c55c9cf83..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/EndpointServiceCollectionExtensions.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics.Tracing; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Steeltoe.Management.Diagnostics; -using Steeltoe.Management.Endpoint.Actuators.Metrics.Observers; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -public static class EndpointServiceCollectionExtensions -{ - /// - /// Adds the metrics actuator to the service container. - /// - /// - /// The to add services to. - /// - /// - /// The incoming so that additional calls can be chained. - /// - public static IServiceCollection AddMetricsActuator(this IServiceCollection services) - { - ArgumentNullException.ThrowIfNull(services); - - services.TryAddSingleton(); - services.AddHostedService(); - - services.AddCommonActuatorServices(); - services.AddMetricsActuatorServices(); - - services.AddMetricsObservers(); - - return services; - } - - /// - /// Adds metrics observers to the service container. - /// - /// - /// The to add services to. - /// - /// - /// The incoming so that additional calls can be chained. - /// - public static IServiceCollection AddMetricsObservers(this IServiceCollection services) - { - ArgumentNullException.ThrowIfNull(services); - - services.ConfigureOptionsWithChangeTokenSource(); - services.TryAddEnumerable(ServiceDescriptor.Singleton()); - services.TryAddEnumerable(ServiceDescriptor.Singleton()); - services.TryAddEnumerable(ServiceDescriptor.Singleton()); - services.TryAddEnumerable(ServiceDescriptor.Singleton()); - - return services; - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/IMetricsEndpointHandler.cs b/src/Management/src/Endpoint/Actuators/Metrics/IMetricsEndpointHandler.cs deleted file mode 100644 index 78adc7baf8..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/IMetricsEndpointHandler.cs +++ /dev/null @@ -1,7 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -public interface IMetricsEndpointHandler : IEndpointHandler; diff --git a/src/Management/src/Endpoint/Actuators/Metrics/MetricCollectionHostedService.cs b/src/Management/src/Endpoint/Actuators/Metrics/MetricCollectionHostedService.cs deleted file mode 100644 index ec4cb1371e..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/MetricCollectionHostedService.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Hosting; -using Steeltoe.Management.Endpoint.Actuators.Metrics.SystemDiagnosticsMetrics; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -internal sealed class MetricCollectionHostedService : IHostedService -{ - private readonly AggregationManager _aggregationManager; - - public MetricCollectionHostedService(AggregationManager aggregationManager) - { - ArgumentNullException.ThrowIfNull(aggregationManager); - - _aggregationManager = aggregationManager; - } - - public Task StartAsync(CancellationToken cancellationToken) - { - return Task.Run(_aggregationManager.Start, cancellationToken); - } - - public Task StopAsync(CancellationToken cancellationToken) - { - return Task.Run(_aggregationManager.Dispose, cancellationToken); - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/MetricLabelExtensions.cs b/src/Management/src/Endpoint/Actuators/Metrics/MetricLabelExtensions.cs deleted file mode 100644 index 1fc7de1574..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/MetricLabelExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -internal static class MetricLabelExtensions -{ - public static ReadOnlySpan> AsReadonlySpan(this IDictionary keyValuePairs) - { - ArgumentNullException.ThrowIfNull(keyValuePairs); - - return keyValuePairs.ToArray(); - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/MetricSample.cs b/src/Management/src/Endpoint/Actuators/Metrics/MetricSample.cs deleted file mode 100644 index 3d89ee2ef7..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/MetricSample.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Text.Json.Serialization; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -public sealed class MetricSample(MetricStatistic statistic, double value, IList>? tags) -{ - [JsonPropertyName("statistic")] - public MetricStatistic Statistic { get; } = statistic; - - [JsonPropertyName("value")] - public double Value { get; } = value; - - [JsonIgnore] - public IList>? Tags { get; } = tags; - - public override string ToString() - { - return $"MeasurementSample{{Statistic={Statistic}, Value={Value}}}"; - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/MetricStatistic.cs b/src/Management/src/Endpoint/Actuators/Metrics/MetricStatistic.cs deleted file mode 100644 index e2638f5a38..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/MetricStatistic.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Text.Json.Serialization; -using Steeltoe.Common.CasingConventions; - -#if !DEBUG -#pragma warning disable SA1602 // Enumeration items should be documented -#endif - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -[JsonConverter(typeof(SnakeCaseAllCapsEnumMemberJsonConverter))] -public enum MetricStatistic -{ - Total, - TotalTime, - Count, - Max, - Value, - Rate -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/MetricTag.cs b/src/Management/src/Endpoint/Actuators/Metrics/MetricTag.cs deleted file mode 100644 index d342fe0c41..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/MetricTag.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Text.Json.Serialization; -using Steeltoe.Common; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -public sealed class MetricTag -{ - [JsonPropertyName("tag")] - public string Tag { get; } - - [JsonPropertyName("values")] - public ISet Values { get; } - - public MetricTag(string tag, ISet values) - { - ArgumentException.ThrowIfNullOrEmpty(tag); - ArgumentNullException.ThrowIfNull(values); - ArgumentGuard.ElementsNotNull(values); - - Tag = tag; - Values = values; - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/MetricsCollection.cs b/src/Management/src/Endpoint/Actuators/Metrics/MetricsCollection.cs deleted file mode 100644 index 3dec8dcae1..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/MetricsCollection.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Collections.Concurrent; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -internal sealed class MetricsCollection : ConcurrentDictionary -{ - public MetricsCollection() - { - } - - public MetricsCollection(IEnumerable> source) - : base(source) - { - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/MetricsEndpointHandler.cs b/src/Management/src/Endpoint/Actuators/Metrics/MetricsEndpointHandler.cs deleted file mode 100644 index cee8c7dcdb..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/MetricsEndpointHandler.cs +++ /dev/null @@ -1,146 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Steeltoe.Management.Configuration; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -internal sealed class MetricsEndpointHandler : IMetricsEndpointHandler -{ - private readonly IOptionsMonitor _optionsMonitor; - private readonly MetricsExporter _exporter; - private readonly ILogger _logger; - - public EndpointOptions Options => _optionsMonitor.CurrentValue; - - public MetricsEndpointHandler(IOptionsMonitor optionsMonitor, MetricsExporter exporter, ILoggerFactory loggerFactory) - { - ArgumentNullException.ThrowIfNull(optionsMonitor); - ArgumentNullException.ThrowIfNull(exporter); - ArgumentNullException.ThrowIfNull(loggerFactory); - - _optionsMonitor = optionsMonitor; - _exporter = exporter; - _logger = loggerFactory.CreateLogger(); - } - - public Task InvokeAsync(MetricsRequest? request, CancellationToken cancellationToken) - { - (MetricsCollection> measurements, MetricsCollection> availableTags) = GetMetrics(); - - var metricNames = new HashSet(measurements.Keys); - MetricsResponse? response; - - if (request == null) - { - response = new MetricsResponse(metricNames); - } - else - { - if (metricNames.Contains(request.MetricName)) - { - _logger.LogTrace("Fetching metrics for {Name}", request.MetricName); - IList sampleList = GetMetricSamplesByTags(measurements, request.MetricName, request.Tags); - - response = GetMetric(request, sampleList, availableTags.GetOrAdd(request.MetricName, new List())); - } - else - { - response = null; - } - } - - return Task.FromResult(response); - } - - internal IList GetMetricSamplesByTags(MetricsCollection> measurements, string metricName, - IList> tags) - { - IList filtered = measurements.GetOrAdd(metricName, new List()); - List sampleList = []; - - if (tags.Any()) - { - filtered = filtered.Where(sample => - tags.All(tag => sample.Tags != null && sample.Tags.Any(sampleTag => tag.Key == sampleTag.Key && tag.Value == sampleTag.Value))).ToArray(); - } - - try - { - MetricSample[] rateSamples = filtered.Where(sample => sample.Statistic == MetricStatistic.Rate).ToArray(); - - if (rateSamples.Length > 0) - { - MetricSample sample = rateSamples.Aggregate(SumAggregator); - sampleList.Add(new MetricSample(MetricStatistic.Rate, sample.Value / rateSamples.Length, sample.Tags)); - } - - MetricSample[] valueSamples = filtered.Where(sample => sample.Statistic == MetricStatistic.Value).ToArray(); - - if (valueSamples.Length > 0) - { - MetricSample sample = valueSamples.Aggregate(SumAggregator); - sampleList.Add(new MetricSample(MetricStatistic.Value, sample.Value / valueSamples.Length, sample.Tags)); - } - - MetricSample[] totalSamples = filtered.Where(sample => sample.Statistic == MetricStatistic.Total).ToArray(); - - if (totalSamples.Length > 0) - { - sampleList.Add(totalSamples.Aggregate(SumAggregator)); - } - - MetricSample[] totalTimeSamples = filtered.Where(sample => sample.Statistic == MetricStatistic.TotalTime).ToArray(); - - if (totalTimeSamples.Length > 0) - { - sampleList.Add(totalTimeSamples.Aggregate(SumAggregator)); - } - - MetricSample[] countSamples = filtered.Where(sample => sample.Statistic == MetricStatistic.Count).ToArray(); - - if (countSamples.Length > 0) - { - sampleList.Add(countSamples.Aggregate(SumAggregator)); - } - - MetricSample[] maxSamples = filtered.Where(sample => sample.Statistic == MetricStatistic.Max).ToArray(); - - if (maxSamples.Length > 0) - { - MetricSample sample = maxSamples.Aggregate(MaxAggregator); - sampleList.Add(new MetricSample(MetricStatistic.Max, sample.Value, sample.Tags)); - } - } - catch (Exception exception) - { - // Nothing we can do, log and move on - _logger.LogError(exception, "Error transforming metrics."); - } - - return sampleList; - - static MetricSample SumAggregator(MetricSample current, MetricSample next) - { - return new MetricSample(current.Statistic, current.Value + next.Value, current.Tags); - } - - static MetricSample MaxAggregator(MetricSample current, MetricSample next) - { - return new MetricSample(current.Statistic, current.Value > next.Value ? current.Value : next.Value, current.Tags); - } - } - - private static MetricsResponse GetMetric(MetricsRequest request, IList metricSamples, IList availableTags) - { - return new MetricsResponse(request.MetricName, metricSamples, availableTags); - } - - internal (MetricsCollection> Samples, MetricsCollection> Tags) GetMetrics() - { - return _exporter.Export(); - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/MetricsEndpointMiddleware.cs b/src/Management/src/Endpoint/Actuators/Metrics/MetricsEndpointMiddleware.cs deleted file mode 100644 index 0fe732cc2f..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/MetricsEndpointMiddleware.cs +++ /dev/null @@ -1,117 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Net; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.Extensions.Primitives; -using Steeltoe.Common; -using Steeltoe.Management.Endpoint.Configuration; -using Steeltoe.Management.Endpoint.Middleware; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -internal sealed class MetricsEndpointMiddleware( - IMetricsEndpointHandler endpointHandler, IOptionsMonitor managementOptionsMonitor, ILoggerFactory loggerFactory) - : EndpointMiddleware(endpointHandler, managementOptionsMonitor, loggerFactory) -{ - private readonly ILogger _logger = loggerFactory.CreateLogger(); - - protected override async Task InvokeEndpointHandlerAsync(HttpContext context, CancellationToken cancellationToken) - { - MetricsRequest? metricsRequest = GetMetricsRequest(context); - return await EndpointHandler.InvokeAsync(metricsRequest, cancellationToken); - } - - private MetricsRequest? GetMetricsRequest(HttpContext context) - { - HttpRequest request = context.Request; - _logger.LogDebug("Handling metrics for path: {Path}", request.Path.Value); - - string metricName = GetMetricName(request); - - if (!string.IsNullOrEmpty(metricName)) - { - // GET /metrics/{metricName}?tag=key:value&tag=key:value - IList> tags = ParseTags(request.Query); - return new MetricsRequest(metricName, tags); - } - - // GET /metrics - return null; - } - - internal string GetMetricName(HttpRequest request) - { - string? baseRequestPath = ManagementOptionsMonitor.CurrentValue.GetBaseRequestPath(request); - string path = $"{baseRequestPath}/{EndpointHandler.Options.Id}".Replace("//", "/", StringComparison.Ordinal); - - return GetMetricName(request, path); - } - - private string GetMetricName(HttpRequest request, string path) - { - if (request.Path.StartsWithSegments(path, out PathString remaining) && remaining.HasValue) - { - return remaining.Value!.TrimStart('/'); - } - - return string.Empty; - } - - internal IList> ParseTags(IQueryCollection query) - { - List> results = []; - - foreach (KeyValuePair parameter in query) - { - if (parameter.Key.Equals("tag", StringComparison.OrdinalIgnoreCase)) - { - foreach (string? value in parameter.Value) - { - KeyValuePair? pair = ParseTag(value); - - if (pair != null && !results.Contains(pair.Value, KeyValuePairEquality.GetComparer())) - { - results.Add(pair.Value); - } - } - } - } - - return results; - } - - internal KeyValuePair? ParseTag(string? tag) - { - if (tag != null) - { - string[] segments = tag.Split([':'], 2); - - if (segments.Length == 2) - { - return new KeyValuePair(segments[0], segments[1]); - } - } - - return null; - } - - protected override async Task WriteResponseAsync(MetricsResponse? result, HttpContext context, CancellationToken cancellationToken) - { - MetricsRequest? metricsRequest = GetMetricsRequest(context); - - if (metricsRequest != null && result == null) - { - context.Response.StatusCode = (int)HttpStatusCode.NotFound; - } - else - { - context.Response.StatusCode = (int)HttpStatusCode.OK; - } - - await base.WriteResponseAsync(result, context, cancellationToken); - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/MetricsEndpointOptions.cs b/src/Management/src/Endpoint/Actuators/Metrics/MetricsEndpointOptions.cs deleted file mode 100644 index 7d5da9d164..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/MetricsEndpointOptions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Management.Configuration; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -public sealed class MetricsEndpointOptions : EndpointOptions -{ - /// - /// Gets or sets the duration in milliseconds that metrics are cached for. Default value: 500. - /// - public int CacheDurationMilliseconds { get; set; } = 500; - - /// - /// Gets or sets the maximum number of time series to return. Default value: 100. - /// - public int MaxTimeSeries { get; set; } = 100; - - /// - /// Gets or sets the maximum number of histograms to return. Default value: 100. - /// - public int MaxHistograms { get; set; } = 100; - - /// - /// Gets the names of metrics to include. - /// - public IList IncludedMetrics { get; } = new List(); - - /// - public override bool RequiresExactMatch() - { - return false; - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/MetricsExporter.cs b/src/Management/src/Endpoint/Actuators/Metrics/MetricsExporter.cs deleted file mode 100644 index e5fde86d8a..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/MetricsExporter.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics.Metrics; -using Steeltoe.Management.Endpoint.Actuators.Metrics.SystemDiagnosticsMetrics; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -/// -/// Exports metrics to Steeltoe Format. -/// -internal sealed class MetricsExporter -{ - private readonly MetricsCollection> _metricSamples = new(); - private readonly MetricsCollection> _availableTags = new(); - - private readonly int _cacheDurationMilliseconds; - private readonly Lock _collectionLock = new(); - private MetricsCollection> _lastCollectionSamples = new(); - private MetricsCollection> _lastAvailableTags = new(); - private DateTime _lastCollection = DateTime.MinValue; - private Action? _collect; - - /// - /// Initializes a new instance of the class. - /// - /// - /// Options for the exporter. - /// - public MetricsExporter(MetricsExporterOptions options) - { - ArgumentNullException.ThrowIfNull(options); - - _cacheDurationMilliseconds = options.CacheDurationMilliseconds; - } - - public void SetCollect(Action collect) - { - ArgumentNullException.ThrowIfNull(collect); - - _collect = collect; - } - - public (MetricsCollection> MetricSamples, MetricsCollection> AvailableTags) Export() - { - if (_collect == null) - { - throw new InvalidOperationException("Call SetCollect() first."); - } - - lock (_collectionLock) - { - if (DateTime.UtcNow > _lastCollection.AddMilliseconds(_cacheDurationMilliseconds)) - { - _metricSamples.Clear(); - _availableTags.Clear(); - _collect(); // Calls AggregationManager.Collect - _lastCollectionSamples = new MetricsCollection>(_metricSamples); - _lastAvailableTags = new MetricsCollection>(_availableTags); - _lastCollection = DateTime.UtcNow; - } - } - - return (_lastCollectionSamples, _lastAvailableTags); - } - - internal void AddMetrics(Instrument instrument, LabeledAggregationStatistics stats) - { - ArgumentNullException.ThrowIfNull(instrument); - ArgumentNullException.ThrowIfNull(stats); - - UpdateAvailableTags(_availableTags, instrument.Name, stats.Labels); - - if (stats.AggregationStatistics is RateStatistics rateStats) - { - if (rateStats.Delta.HasValue) - { - var sample = new MetricSample(MetricStatistic.Rate, rateStats.Delta.Value, stats.Labels); - _metricSamples.GetOrAdd(instrument.Name, new List()).Add(sample); - } - } - else if (stats.AggregationStatistics is LastValueStatistics lastValueStats) - { - if (lastValueStats.LastValue.HasValue) - { - var sample = new MetricSample(MetricStatistic.Value, lastValueStats.LastValue.Value, stats.Labels); - _metricSamples.GetOrAdd(instrument.Name, new List()).Add(sample); - } - } - else if (stats.AggregationStatistics is HistogramStatistics histogramStats) - { - double sum = histogramStats.HistogramSum; - - if (instrument.Unit == "s") - { - var timeSample = new MetricSample(MetricStatistic.TotalTime, sum, stats.Labels); - _metricSamples.GetOrAdd(instrument.Name, new List()).Add(timeSample); - - var maxSample = new MetricSample(MetricStatistic.Max, histogramStats.HistogramMax, stats.Labels); - _metricSamples.GetOrAdd(instrument.Name, new List()).Add(maxSample); - } - else - { - var sample = new MetricSample(MetricStatistic.Total, sum, stats.Labels); - _metricSamples.GetOrAdd(instrument.Name, new List()).Add(sample); - } - } - } - - private static void UpdateAvailableTags(MetricsCollection> availableTags, string name, IEnumerable> labels) - { - foreach (KeyValuePair label in labels) - { - IList currentTags = availableTags.GetOrAdd(name, new List()); - MetricTag? existingTag = currentTags.FirstOrDefault(tag => tag.Tag.Equals(label.Key, StringComparison.OrdinalIgnoreCase)); - - if (existingTag != null) - { - _ = existingTag.Values.Add(label.Value); - } - else - { - currentTags.Add(new MetricTag(label.Key, new HashSet(new List - { - label.Value - }))); - } - } - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/MetricsExporterOptions.cs b/src/Management/src/Endpoint/Actuators/Metrics/MetricsExporterOptions.cs deleted file mode 100644 index 9840fa1eba..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/MetricsExporterOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -internal sealed class MetricsExporterOptions -{ - public int CacheDurationMilliseconds { get; set; } - public int MaxTimeSeries { get; set; } - public int MaxHistograms { get; set; } - public IList IncludedMetrics { get; } = new List(); -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/MetricsRequest.cs b/src/Management/src/Endpoint/Actuators/Metrics/MetricsRequest.cs deleted file mode 100644 index 526e801f5c..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/MetricsRequest.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -public sealed class MetricsRequest -{ - public string MetricName { get; } - - public IList> Tags { get; } - - public MetricsRequest(string metricName, IList> tags) - { - ArgumentException.ThrowIfNullOrEmpty(metricName); - ArgumentNullException.ThrowIfNull(tags); - - MetricName = metricName; - Tags = tags; - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/MetricsResponse.cs b/src/Management/src/Endpoint/Actuators/Metrics/MetricsResponse.cs deleted file mode 100644 index 4c4b03d69e..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/MetricsResponse.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Text.Json.Serialization; -using Steeltoe.Common; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -public sealed class MetricsResponse -{ - [JsonPropertyName("name")] - [JsonPropertyOrder(1)] - public string? Name { get; } - - [JsonPropertyName("measurements")] - [JsonPropertyOrder(2)] - public IList? Measurements { get; } - - [JsonPropertyOrder(3)] - [JsonPropertyName("availableTags")] - public IList? AvailableTags { get; } - - [JsonPropertyName("names")] - [JsonPropertyOrder(0)] - public ISet? Names { get; } - - public MetricsResponse(ISet names) - { - ArgumentNullException.ThrowIfNull(names); - ArgumentGuard.ElementsNotNullOrEmpty(names); - - Names = names; - } - - public MetricsResponse(string name, IList measurements, IList availableTags) - { - ArgumentException.ThrowIfNullOrEmpty(name); - ArgumentNullException.ThrowIfNull(measurements); - ArgumentGuard.ElementsNotNull(measurements); - ArgumentNullException.ThrowIfNull(availableTags); - ArgumentGuard.ElementsNotNull(availableTags); - - Name = name; - Measurements = measurements; - AvailableTags = availableTags; - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/Observers/AspNetCoreHostingObserver.cs b/src/Management/src/Endpoint/Actuators/Metrics/Observers/AspNetCoreHostingObserver.cs deleted file mode 100644 index 59460b4f06..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/Observers/AspNetCoreHostingObserver.cs +++ /dev/null @@ -1,113 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics; -using System.Diagnostics.Metrics; -using System.Globalization; -using System.Text.RegularExpressions; -using Microsoft.AspNetCore.Diagnostics; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Steeltoe.Management.Diagnostics; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics.Observers; - -internal sealed class AspNetCoreHostingObserver : MetricsObserver -{ - private const string DefaultObserverName = "AspNetCoreHostingObserver"; - private const string DiagnosticName = "Microsoft.AspNetCore"; - private const string StatusTagKey = "status"; - private const string ExceptionTagKey = "exception"; - private const string MethodTagKey = "method"; - private const string UriTagKey = "uri"; - private const string StopEventName = "Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop"; - - private readonly Histogram _responseTime; - private readonly Histogram _serverCount; - private readonly ILogger _logger; - - public AspNetCoreHostingObserver(IOptionsMonitor optionsMonitor, ILoggerFactory loggerFactory) - : base(DefaultObserverName, DiagnosticName, loggerFactory) - { - ArgumentNullException.ThrowIfNull(optionsMonitor); - - string? ingressIgnorePattern = optionsMonitor.CurrentValue.IngressIgnorePattern; - - if (ingressIgnorePattern != null) - { - SetPathMatcher(new Regex(ingressIgnorePattern, RegexOptions.None, TimeSpan.FromSeconds(1))); - } - - Meter meter = SteeltoeMetrics.Meter; - - _responseTime = meter.CreateHistogram("http.server.requests.seconds", "s", "measures the duration of the inbound request in seconds"); - _serverCount = meter.CreateHistogram("http.server.requests.count", "total", "number of requests"); - _logger = loggerFactory.CreateLogger(); - } - - public override void ProcessEvent(string eventName, object? value) - { - if (value == null || eventName != StopEventName) - { - return; - } - - Activity? current = Activity.Current; - - if (current == null) - { - return; - } - - _logger.LogTrace("HandleStopEvent start {Thread}", System.Environment.CurrentManagedThreadId); - - if (value is HttpContext httpContext) - { - HandleStopEvent(current, httpContext); - } - - _logger.LogTrace("HandleStopEvent finish {Thread}", System.Environment.CurrentManagedThreadId); - } - - private void HandleStopEvent(Activity current, HttpContext context) - { - if (ShouldIgnoreRequest(context.Request.Path)) - { - _logger.LogDebug("HandleStopEvent: Ignoring path: {Path}", context.Request.Path); - return; - } - - if (current.Duration.TotalMilliseconds > 0) - { - ReadOnlySpan> labels = GetLabelSets(context).AsReadonlySpan(); - _serverCount.Record(1, labels); - _responseTime.Record(current.Duration.TotalSeconds, labels); - } - } - - internal IDictionary GetLabelSets(HttpContext context) - { - ArgumentNullException.ThrowIfNull(context); - - string uri = context.Request.Path.ToString(); - string statusCode = context.Response.StatusCode.ToString(CultureInfo.InvariantCulture); - string exceptionTypeName = GetExceptionTypeName(context); - - return new Dictionary - { - { UriTagKey, uri }, - { StatusTagKey, statusCode }, - { ExceptionTagKey, exceptionTypeName }, - { MethodTagKey, context.Request.Method } - }; - } - - internal static string GetExceptionTypeName(HttpContext context) - { - var exception = context.Features.Get(); - - return exception != null ? exception.Error.GetType().Name : "None"; - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/Observers/ClrRuntimeObserver.cs b/src/Management/src/Endpoint/Actuators/Metrics/Observers/ClrRuntimeObserver.cs deleted file mode 100644 index a1c3dbcebf..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/Observers/ClrRuntimeObserver.cs +++ /dev/null @@ -1,114 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics; -using System.Diagnostics.Metrics; -using Microsoft.Extensions.Options; -using Steeltoe.Management.Diagnostics; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics.Observers; - -internal sealed class ClrRuntimeObserver : IRuntimeDiagnosticSource -{ - private const string GenerationTagValueName = "gen"; - private const string GenerationKey = "generation"; - - private readonly Dictionary _heapTags = new() - { - { "area", "heap" } - }; - - private readonly Dictionary _workerTags = new() - { - { "kind", "worker" } - }; - - private readonly Dictionary _completionPortTags = new() - { - { "kind", "completionPort" } - }; - - private readonly IOptionsMonitor _options; - - private ClrRuntimeSource.HeapMetrics? _previous; - - public ClrRuntimeObserver(IOptionsMonitor options) - { - ArgumentNullException.ThrowIfNull(options); - - _options = options; - } - - private IEnumerable> GetCollectionCount() - { - ClrRuntimeSource.HeapMetrics metrics = ClrRuntimeSource.GetHeapMetrics(); - - for (int index = 0; index < metrics.CollectionCounts.Count; index++) - { - long count = metrics.CollectionCounts[index]; - - if (_previous != null && index < _previous.Value.CollectionCounts.Count && _previous.Value.CollectionCounts[index] <= count) - { - count -= _previous.Value.CollectionCounts[index]; - } - - var tags = new Dictionary - { - { GenerationKey, GenerationTagValueName + index } - }; - - yield return new Measurement(count, tags.AsReadonlySpan()); - _previous = metrics; - } - } - - private Measurement GetMemoryUsed() - { - ClrRuntimeSource.HeapMetrics metrics = ClrRuntimeSource.GetHeapMetrics(); - return new Measurement(metrics.TotalMemory, _heapTags.AsReadonlySpan()); - } - - private double GetUpTime() - { - using var process = Process.GetCurrentProcess(); - TimeSpan diff = DateTime.UtcNow - process.StartTime.ToUniversalTime(); - return diff.TotalSeconds; - } - - private IEnumerable> GetActiveThreadPoolWorkers() - { - ClrRuntimeSource.ThreadMetrics metrics = ClrRuntimeSource.GetThreadMetrics(); - long active = metrics.MaxThreadPoolWorkers - metrics.AvailableThreadPoolWorkers; - long activeCompPort = metrics.MaxThreadCompletionPort - metrics.AvailableThreadCompletionPort; - - yield return new Measurement(active, _workerTags.AsReadonlySpan()); - yield return new Measurement(activeCompPort, _completionPortTags.AsReadonlySpan()); - } - - private IEnumerable> GetAvailableThreadPoolWorkers() - { - ClrRuntimeSource.ThreadMetrics metrics = ClrRuntimeSource.GetThreadMetrics(); - yield return new Measurement(metrics.AvailableThreadPoolWorkers, _workerTags.AsReadonlySpan()); - yield return new Measurement(metrics.AvailableThreadCompletionPort, _completionPortTags.AsReadonlySpan()); - } - - public void AddInstrumentation() - { - Meter meter = SteeltoeMetrics.Meter; - - if (_options.CurrentValue.GCEvents) - { - meter.CreateObservableGauge("clr.memory.used", GetMemoryUsed, "Current CLR memory usage", "bytes"); - meter.CreateObservableGauge("clr.gc.collections", GetCollectionCount, "Garbage collection count", "count"); - meter.CreateObservableGauge("clr.process.uptime", GetUpTime, "Process uptime in seconds", "count"); - meter.CreateObservableGauge("clr.cpu.count", () => System.Environment.ProcessorCount, "Total processor count", "count"); - } - - if (_options.CurrentValue.ThreadPoolEvents) - { - meter.CreateObservableGauge("clr.threadpool.active", GetActiveThreadPoolWorkers, "Active thread count", "count"); - meter.CreateObservableGauge("clr.threadpool.avail", GetAvailableThreadPoolWorkers, "Available thread count", "count"); - } - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/Observers/ClrRuntimeSource.cs b/src/Management/src/Endpoint/Actuators/Metrics/Observers/ClrRuntimeSource.cs deleted file mode 100644 index aea568af27..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/Observers/ClrRuntimeSource.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics.Observers; - -internal static class ClrRuntimeSource -{ - public static HeapMetrics GetHeapMetrics() - { - long totalMemory = GC.GetTotalMemory(false); - - var counts = new List(GC.MaxGeneration); - - for (int index = 0; index < GC.MaxGeneration; index++) - { - counts.Add(GC.CollectionCount(index)); - } - - return new HeapMetrics(totalMemory, counts); - } - - public static ThreadMetrics GetThreadMetrics() - { - ThreadPool.GetAvailableThreads(out int availWorkerThreads, out int availCompPortThreads); - ThreadPool.GetMaxThreads(out int maxWorkerThreads, out int maxCompPortThreads); - return new ThreadMetrics(availWorkerThreads, availCompPortThreads, maxWorkerThreads, maxCompPortThreads); - } - - public readonly record struct HeapMetrics(long TotalMemory, IList CollectionCounts) - { - public readonly long TotalMemory = TotalMemory; - public readonly IList CollectionCounts = CollectionCounts; - } - - public readonly record struct ThreadMetrics( - long AvailableThreadPoolWorkers, long AvailableThreadCompletionPort, long MaxThreadPoolWorkers, long MaxThreadCompletionPort) - { - public readonly long AvailableThreadPoolWorkers = AvailableThreadPoolWorkers; - public readonly long AvailableThreadCompletionPort = AvailableThreadCompletionPort; - public readonly long MaxThreadPoolWorkers = MaxThreadPoolWorkers; - public readonly long MaxThreadCompletionPort = MaxThreadCompletionPort; - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/Observers/EventCounterListener.cs b/src/Management/src/Endpoint/Actuators/Metrics/Observers/EventCounterListener.cs deleted file mode 100644 index 9743dc88f1..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/Observers/EventCounterListener.cs +++ /dev/null @@ -1,226 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Collections.Concurrent; -using System.Diagnostics.Metrics; -using System.Diagnostics.Tracing; -using System.Globalization; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Steeltoe.Management.Diagnostics; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics.Observers; - -internal sealed class EventCounterListener : EventListener -{ - private const string EventSourceName = "System.Runtime"; - private const string EventName = "EventCounters"; - private readonly IOptionsMonitor _optionsMonitor; - private readonly ILogger _logger; - private readonly bool _isInitialized; - - private readonly Dictionary _refreshInterval = []; - - private readonly ConcurrentDictionary> _doubleMeasureMetrics = new(); - private readonly ConcurrentDictionary> _longMeasureMetrics = new(); - - private readonly ConcurrentDictionary _lastDoubleValue = new(); - private readonly ConcurrentDictionary _lastLongValue = new(); - - private readonly ConcurrentBag _eventSources = []; - - public EventCounterListener(IOptionsMonitor optionsMonitor, ILogger logger) - { - ArgumentNullException.ThrowIfNull(optionsMonitor); - ArgumentNullException.ThrowIfNull(logger); - - _optionsMonitor = optionsMonitor; - _logger = logger; - MetricsObserverOptions observerOptions = optionsMonitor.CurrentValue; - - if (observerOptions.EventCounterEvents) - { - _isInitialized = true; - - _refreshInterval = new Dictionary - { - { "EventCounterIntervalSec", observerOptions.EventCounterIntervalSec?.ToString(CultureInfo.InvariantCulture) ?? "1" } - }; - - ProcessPreInitEventSources(); - } - } - - /// - /// Processes a new EventSource event. - /// - /// - /// Event to process. - /// - protected override void OnEventWritten(EventWrittenEventArgs eventData) - { - ArgumentNullException.ThrowIfNull(eventData); - - if (!_isInitialized) - { - return; - } - - try - { - if (string.Equals(eventData.EventName, EventName, StringComparison.OrdinalIgnoreCase) && eventData.Payload != null) - { - foreach (IDictionary? payload in eventData.Payload.Cast?>()) - { - if (payload != null) - { - ExtractAndRecordMetric(eventData.EventSource.Name, payload); - } - } - } - } - catch (Exception exception) - { - _logger.LogError(exception, "Failed to write event {Id}", eventData.EventId); - } - } - - protected override void OnEventSourceCreated(EventSource eventSource) - { - ArgumentNullException.ThrowIfNull(eventSource); - - if (EventSourceName.Equals(eventSource.Name, StringComparison.OrdinalIgnoreCase)) - { - if (!_isInitialized) - { - _eventSources.Add(eventSource); - } - else - { - SafeEnableEvents(eventSource); - } - } - } - - private void ProcessPreInitEventSources() - { - foreach (EventSource eventSource in _eventSources) - { - SafeEnableEvents(eventSource); - } - } - - private void SafeEnableEvents(EventSource eventSource) - { - try - { - if (!_isInitialized) - { - throw new InvalidOperationException("Should not call enable events before initialization"); - } - - if (!_optionsMonitor.CurrentValue.EventCounterEvents) - { - return; - } - - EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.All, _refreshInterval); - } - catch (Exception exception) - { - _logger.LogError(exception, "Failed to enable events for {Source}", eventSource.Guid); - } - } - - private void ExtractAndRecordMetric(string eventSourceName, IDictionary eventPayload) - { - string metricName = string.Empty; - double? doubleValue = null; - long? longValue = null; - string counterName = string.Empty; - List> labelSet = []; - string? counterDisplayUnit = null; - string? counterDisplayName = null; - - foreach ((string key, object? value) in eventPayload) - { - switch (key) - { - case var _ when key.Equals("Name", StringComparison.OrdinalIgnoreCase): - counterName = value?.ToString() ?? string.Empty; - IList includedMetrics = _optionsMonitor.CurrentValue.IncludedMetrics; - IList excludedMetrics = _optionsMonitor.CurrentValue.ExcludedMetrics; - - if ((includedMetrics.Any() && !includedMetrics.Contains(counterName)) || excludedMetrics.Contains(counterName)) - { - return; - } - - break; - case var _ when key.Equals("DisplayName", StringComparison.OrdinalIgnoreCase): - counterDisplayName = value?.ToString(); - labelSet.Add(KeyValuePair.Create("DisplayName", (object?)counterDisplayName)); - break; - case var _ when key.Equals("DisplayUnits", StringComparison.OrdinalIgnoreCase): - counterDisplayUnit = value?.ToString(); - labelSet.Add(KeyValuePair.Create("DisplayUnits", (object?)counterDisplayUnit)); - break; - case var _ when key.Equals("Mean", StringComparison.OrdinalIgnoreCase): - doubleValue = Convert.ToDouble(value, CultureInfo.InvariantCulture); - break; - case var _ when key.Equals("Increment", StringComparison.OrdinalIgnoreCase): - longValue = Convert.ToInt64(value, CultureInfo.InvariantCulture); - break; - case var _ when key.Equals("IntervalSec", StringComparison.OrdinalIgnoreCase): - doubleValue = Convert.ToDouble(value, CultureInfo.InvariantCulture); - break; - case var _ when key.Equals("Count", StringComparison.OrdinalIgnoreCase): - longValue = Convert.ToInt64(value, CultureInfo.InvariantCulture); - break; - case var _ when key.Equals("Metadata", StringComparison.OrdinalIgnoreCase): - string? metadata = value?.ToString(); - - if (!string.IsNullOrEmpty(metadata)) - { - string[] keyValuePairStrings = metadata.Split(','); - - foreach (string keyValuePairString in keyValuePairStrings) - { - string[] keyValuePair = keyValuePairString.Split(':'); - labelSet.Add(KeyValuePair.Create(keyValuePair[0], (object?)keyValuePair[1])); - } - } - - break; - } - - metricName = $"{eventSourceName}.{counterName}"; - } - - if (doubleValue.HasValue) - { - _lastDoubleValue[metricName] = doubleValue.Value; - - _doubleMeasureMetrics.GetOrAdd(metricName, - name => SteeltoeMetrics.Meter.CreateObservableGauge($"{name}", () => ObserveDouble(name, labelSet), counterDisplayUnit, counterDisplayName)); - } - else if (longValue.HasValue) - { - _lastLongValue[metricName] = longValue.Value; - - _longMeasureMetrics.GetOrAdd(metricName, - name => SteeltoeMetrics.Meter.CreateObservableGauge($"{name}", () => ObserveLong(name, labelSet), counterDisplayUnit, counterDisplayName)); - } - } - - private Measurement ObserveDouble(string name, List> labelSet) - { - return new Measurement(_lastDoubleValue[name], labelSet); - } - - private Measurement ObserveLong(string name, List> labelSet) - { - return new Measurement(_lastLongValue[name], labelSet); - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/Observers/HttpClientObserver.cs b/src/Management/src/Endpoint/Actuators/Metrics/Observers/HttpClientObserver.cs deleted file mode 100644 index ae4976711f..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/Observers/HttpClientObserver.cs +++ /dev/null @@ -1,140 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics; -using System.Diagnostics.Metrics; -using System.Globalization; -using System.Text.RegularExpressions; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Steeltoe.Common; -using Steeltoe.Management.Diagnostics; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics.Observers; - -internal sealed class HttpClientObserver : MetricsObserver -{ - private const string StatusTagKey = "status"; - private const string UriTagKey = "uri"; - private const string MethodTagKey = "method"; - private const string ClientTagKey = "clientName"; - private const string DiagnosticName = "HttpHandlerDiagnosticListener"; - private const string DefaultObserverName = "HttpClientObserver"; - - private const string StopEventName = "System.Net.Http.HttpRequestOut.Stop"; - private const string ExceptionEvent = "System.Net.Http.Exception"; - private readonly Histogram _clientTimeMeasure; - private readonly Histogram _clientCountMeasure; - private readonly ILogger _logger; - - public HttpClientObserver(IOptionsMonitor optionsMonitor, ILoggerFactory loggerFactory) - : base(DefaultObserverName, DiagnosticName, loggerFactory) - { - ArgumentNullException.ThrowIfNull(optionsMonitor); - - string? egressIgnorePattern = optionsMonitor.CurrentValue.EgressIgnorePattern; - - if (egressIgnorePattern != null) - { - SetPathMatcher(new Regex(egressIgnorePattern, RegexOptions.None, TimeSpan.FromSeconds(1))); - } - - _clientTimeMeasure = SteeltoeMetrics.Meter.CreateHistogram("http.client.request.time"); - _clientCountMeasure = SteeltoeMetrics.Meter.CreateHistogram("http.client.request.count"); - _logger = loggerFactory.CreateLogger(); - } - - public override void ProcessEvent(string eventName, object? value) - { - if (value == null || (eventName != StopEventName && eventName != ExceptionEvent)) - { - return; - } - - Activity? current = Activity.Current; - - if (current == null) - { - return; - } - - var request = GetPropertyOrDefault(value, "Request"); - - if (request == null) - { - return; - } - - if (eventName == StopEventName) - { - _logger.LogTrace("HandleStopEvent start {Thread}", System.Environment.CurrentManagedThreadId); - - var response = GetPropertyOrDefault(value, "Response"); - var requestStatus = GetPropertyOrDefault(value, "RequestTaskStatus"); - HandleStopEvent(current, request, response, requestStatus); - - _logger.LogTrace("HandleStopEvent finished {Thread}", System.Environment.CurrentManagedThreadId); - } - else if (eventName == ExceptionEvent) - { - _logger.LogTrace("HandleExceptionEvent start {Thread}", System.Environment.CurrentManagedThreadId); - - HandleExceptionEvent(current, request); - - _logger.LogTrace("HandleExceptionEvent finished {Thread}", System.Environment.CurrentManagedThreadId); - } - } - - private void HandleExceptionEvent(Activity current, HttpRequestMessage request) - { - HandleStopEvent(current, request, null, TaskStatus.Faulted); - } - - private void HandleStopEvent(Activity current, HttpRequestMessage request, HttpResponseMessage? response, TaskStatus taskStatus) - { - if (ShouldIgnoreRequest(request.RequestUri?.AbsolutePath)) - { - _logger.LogDebug("HandleStopEvent: Ignoring path: {Path}", SecurityUtilities.SanitizeInput(request.RequestUri?.AbsolutePath)); - return; - } - - if (current.Duration.TotalMilliseconds > 0) - { - ReadOnlySpan> labels = GetLabels(request, response, taskStatus).AsReadonlySpan(); - _clientTimeMeasure.Record(current.Duration.TotalMilliseconds, labels); - _clientCountMeasure.Record(1, labels); - } - } - - private static Dictionary GetLabels(HttpRequestMessage request, HttpResponseMessage? response, TaskStatus taskStatus) - { - string uri = request.RequestUri!.GetComponents(UriComponents.PathAndQuery, UriFormat.UriEscaped); - string statusCode = GetStatusCode(response, taskStatus); - string clientName = request.RequestUri.GetComponents(UriComponents.HostAndPort, UriFormat.UriEscaped); - - return new Dictionary - { - { UriTagKey, uri }, - { StatusTagKey, statusCode }, - { ClientTagKey, clientName }, - { MethodTagKey, request.Method.ToString() } - }; - } - - private static string GetStatusCode(HttpResponseMessage? response, TaskStatus taskStatus) - { - if (response == null) - { - return taskStatus switch - { - TaskStatus.Faulted => "CLIENT_FAULT", - TaskStatus.Canceled => "CLIENT_CANCELED", - _ => "CLIENT_ERROR" - }; - } - - int value = (int)response.StatusCode; - return value.ToString(CultureInfo.InvariantCulture); - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/Observers/MetricsObserver.cs b/src/Management/src/Endpoint/Actuators/Metrics/Observers/MetricsObserver.cs deleted file mode 100644 index dffa971297..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/Observers/MetricsObserver.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Text.RegularExpressions; -using Microsoft.Extensions.Logging; -using Steeltoe.Management.Diagnostics; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics.Observers; - -internal abstract class MetricsObserver(string observerName, string diagnosticName, ILoggerFactory loggerFactory) - : DiagnosticObserver(observerName, diagnosticName, loggerFactory) -{ - private Regex? _pathMatcher; - - protected void SetPathMatcher(Regex value) - { - _pathMatcher = value; - } - - public bool ShouldIgnoreRequest(string? path) - { - if (string.IsNullOrEmpty(path)) - { - return false; - } - - return _pathMatcher != null && _pathMatcher.IsMatch(path); - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/ServiceCollectionExtensions.cs b/src/Management/src/Endpoint/Actuators/Metrics/ServiceCollectionExtensions.cs deleted file mode 100644 index 6ebc5be3c7..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,114 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Steeltoe.Management.Endpoint.Actuators.Metrics.SystemDiagnosticsMetrics; -using Steeltoe.Management.Endpoint.Middleware; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -/// -/// Add services used by the Metrics actuator. -/// -internal static class ServiceCollectionExtensions -{ - /// - /// Adds the services used by the Metrics actuator. - /// - /// - /// The to add services to. - /// - /// - /// The incoming so that additional calls can be chained. - /// - public static IServiceCollection AddMetricsActuatorServices(this IServiceCollection services) - { - ArgumentNullException.ThrowIfNull(services); - - services.ConfigureEndpointOptions(); - services.TryAddSingleton(); - services.TryAddEnumerable(ServiceDescriptor.Singleton()); - services.TryAddSingleton(); - - services.TryAddSingleton(provider => - { - MetricsEndpointOptions options = provider.GetRequiredService>().CurrentValue; - return CreateMetricsExporterOptionsFrom(options); - }); - - services.TryAddSingleton(provider => - { - var exporterOptions = provider.GetRequiredService(); - return new MetricsExporter(exporterOptions); - }); - - services.AddSteeltoeCollector(); - - return services; - } - - private static MetricsExporterOptions CreateMetricsExporterOptionsFrom(MetricsEndpointOptions endpointOptions) - { - var exporterOptions = new MetricsExporterOptions - { - CacheDurationMilliseconds = endpointOptions.CacheDurationMilliseconds, - MaxTimeSeries = endpointOptions.MaxTimeSeries, - MaxHistograms = endpointOptions.MaxHistograms - }; - - foreach (string metric in endpointOptions.IncludedMetrics) - { - exporterOptions.IncludedMetrics.Add(metric); - } - - return exporterOptions; - } - - public static IServiceCollection AddSteeltoeCollector(this IServiceCollection services) - { - ArgumentNullException.ThrowIfNull(services); - - return services.AddSingleton(provider => - { - var exporter = provider.GetRequiredService(); - - var exporterOptions = provider.GetRequiredService(); - var logger = provider.GetRequiredService>(); - - var aggregationManager = new AggregationManager(exporterOptions.MaxTimeSeries, exporterOptions.MaxHistograms, exporter.AddMetrics, - (intervalStartTime, nextIntervalStartTime) => logger.LogTrace("Begin collection from {IntervalStartTime} to {NextIntervalStartTime}", - intervalStartTime, nextIntervalStartTime), - (intervalStartTime, nextIntervalStartTime) => logger.LogTrace("End collection from {IntervalStartTime} to {NextIntervalStartTime}", - intervalStartTime, nextIntervalStartTime), - instrument => logger.LogTrace("Begin measurements from {InstrumentName} for {MeterName}", instrument.Name, instrument.Meter.Name), - instrument => logger.LogTrace("End measurements from {InstrumentName} for {MeterName}", instrument.Name, instrument.Meter.Name), - instrument => logger.LogTrace("Instrument {InstrumentName} published for {MeterName}", instrument.Name, instrument.Meter.Name), - () => logger.LogTrace("Steeltoe metrics collector started."), exception => logger.LogError(exception, "An error occurred while collecting"), - () => logger.LogWarning("Cannot collect any more time series because the configured limit of {MaxTimeSeries} was reached", - exporterOptions.MaxTimeSeries), - () => logger.LogWarning("Cannot collect any more Histograms because the configured limit of {MaxHistograms} was reached", - exporterOptions.MaxHistograms), exception => logger.LogError(exception, "An error occurred while collecting observable instruments")); - - exporter.SetCollect(aggregationManager.Collect); - aggregationManager.Include(SteeltoeMetrics.InstrumentationName); // Default to Steeltoe Metrics - - foreach (string filter in exporterOptions.IncludedMetrics) - { - string[] filterParts = filter.Split(':'); - - if (filterParts.Length == 2) - { - string meter = filterParts[0]; - string instrument = filterParts[1]; - aggregationManager.Include(meter, instrument); - } - } - - return aggregationManager; - }).AddHostedService(); - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/SteeltoeMetrics.cs b/src/Management/src/Endpoint/Actuators/Metrics/SteeltoeMetrics.cs deleted file mode 100644 index 9ece1cb2ab..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/SteeltoeMetrics.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics.Metrics; -using System.Reflection; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics; - -internal static class SteeltoeMetrics -{ - private static readonly AssemblyName AssemblyName = typeof(SteeltoeMetrics).Assembly.GetName(); - private static readonly string? InstrumentationVersion = AssemblyName.Version?.ToString(); - - public static Meter Meter => new(InstrumentationName, InstrumentationVersion); - public static string InstrumentationName { get; set; } = AssemblyName.Name ?? throw new InvalidOperationException(nameof(InstrumentationName)); -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/AggregationManager.cs b/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/AggregationManager.cs deleted file mode 100644 index 8d2147b5f2..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/AggregationManager.cs +++ /dev/null @@ -1,393 +0,0 @@ -#pragma warning disable -// Steeltoe: Copy of version in System.Diagnostics.Metrics (see README.md for details). - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Diagnostics.Metrics; -using System.Runtime.Versioning; -using System.Security; -using System.Threading; -using System.Threading.Tasks; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics.SystemDiagnosticsMetrics -{ - [UnsupportedOSPlatform("browser")] - [SecuritySafeCritical] - internal sealed class AggregationManager - // Steeltoe-Start: Add IDisposable, so we can dispose from tests. - : IDisposable - // Steeltoe-End: Add IDisposable, so we can dispose from tests. - { - public const double MinCollectionTimeSecs = 0.1; - private static readonly QuantileAggregation s_defaultHistogramConfig = new QuantileAggregation(new double[] { 0.50, 0.95, 0.99 }); - - // these fields are modified after construction and accessed on multiple threads, use lock(this) to ensure the data - // is synchronized - private readonly List> _instrumentConfigFuncs = new(); - private TimeSpan _collectionPeriod; - - private readonly ConcurrentDictionary _instrumentStates = new(); - private readonly CancellationTokenSource _cts = new(); - private Thread? _collectThread; - private readonly MeterListener _listener; - private int _currentTimeSeries; - private int _currentHistograms; - - private readonly int _maxTimeSeries; - private readonly int _maxHistograms; - private readonly Action _collectMeasurement; - private readonly Action _beginCollection; - private readonly Action _endCollection; - private readonly Action _beginInstrumentMeasurements; - private readonly Action _endInstrumentMeasurements; - private readonly Action _instrumentPublished; - private readonly Action _initialInstrumentEnumerationComplete; - private readonly Action _collectionError; - private readonly Action _timeSeriesLimitReached; - private readonly Action _histogramLimitReached; - private readonly Action _observableInstrumentCallbackError; - - public AggregationManager( - int maxTimeSeries, - int maxHistograms, - Action collectMeasurement, - Action beginCollection, - Action endCollection, - Action beginInstrumentMeasurements, - Action endInstrumentMeasurements, - Action instrumentPublished, - Action initialInstrumentEnumerationComplete, - Action collectionError, - Action timeSeriesLimitReached, - Action histogramLimitReached, - Action observableInstrumentCallbackError) - { - _maxTimeSeries = maxTimeSeries; - _maxHistograms = maxHistograms; - _collectMeasurement = collectMeasurement; - _beginCollection = beginCollection; - _endCollection = endCollection; - _beginInstrumentMeasurements = beginInstrumentMeasurements; - _endInstrumentMeasurements = endInstrumentMeasurements; - _instrumentPublished = instrumentPublished; - _initialInstrumentEnumerationComplete = initialInstrumentEnumerationComplete; - _collectionError = collectionError; - _timeSeriesLimitReached = timeSeriesLimitReached; - _histogramLimitReached = histogramLimitReached; - _observableInstrumentCallbackError = observableInstrumentCallbackError; - - _listener = new MeterListener() - { - InstrumentPublished = (instrument, listener) => - { - _instrumentPublished(instrument); - InstrumentState? state = GetInstrumentState(instrument); - if (state != null) - { - _beginInstrumentMeasurements(instrument); - listener.EnableMeasurementEvents(instrument, state); - } - }, - MeasurementsCompleted = (instrument, cookie) => - { - _endInstrumentMeasurements(instrument); - RemoveInstrumentState(instrument, (InstrumentState)cookie!); - } - }; - _listener.SetMeasurementEventCallback((i, m, l, c) => ((InstrumentState)c!).Update((double)m, l)); - _listener.SetMeasurementEventCallback((i, m, l, c) => ((InstrumentState)c!).Update((double)m, l)); - _listener.SetMeasurementEventCallback((i, m, l, c) => ((InstrumentState)c!).Update((double)m, l)); - _listener.SetMeasurementEventCallback((i, m, l, c) => ((InstrumentState)c!).Update((double)m, l)); - _listener.SetMeasurementEventCallback((i, m, l, c) => ((InstrumentState)c!).Update((double)m, l)); - _listener.SetMeasurementEventCallback((i, m, l, c) => ((InstrumentState)c!).Update((double)m, l)); - _listener.SetMeasurementEventCallback((i, m, l, c) => ((InstrumentState)c!).Update((double)m, l)); - } - - public void Include(string meterName) - { - Include(i => i.Meter.Name == meterName); - } - - public void Include(string meterName, string instrumentName) - { - Include(i => i.Meter.Name == meterName && i.Name == instrumentName); - } - - private void Include(Predicate instrumentFilter) - { - lock (this) - { - _instrumentConfigFuncs.Add(instrumentFilter); - } - } - - public AggregationManager SetCollectionPeriod(TimeSpan collectionPeriod) - { - // The caller, MetricsEventSource, is responsible for enforcing this - Debug.Assert(collectionPeriod.TotalSeconds >= MinCollectionTimeSecs); - lock (this) - { - _collectionPeriod = collectionPeriod; - } - return this; - } - - public void Start() - { - // Steeltoe-Start: Commented out the code block below. - // Because Steeltoe calls Start() only once at startup and always calls the Collect method for each ASP.NET request or - // from the hosted service. Therefore we don't need a background thread to collect. - /* - // if already started or already stopped we can't be started again - Debug.Assert(_collectThread == null && !_cts.IsCancellationRequested); - Debug.Assert(_collectionPeriod.TotalSeconds >= MinCollectionTimeSecs); - - // This explicitly uses a Thread and not a Task so that metrics still work - // even when an app is experiencing thread-pool starvation. Although we - // can't make in-proc metrics robust to everything, this is a common enough - // problem in .NET apps that it feels worthwhile to take the precaution. - _collectThread = new Thread(() => CollectWorker(_cts.Token)); - _collectThread.IsBackground = true; - _collectThread.Name = "MetricsEventSource CollectWorker"; - _collectThread.Start(); - */ - // Steeltoe-End: Commented out the code block below. - - _listener.Start(); - _initialInstrumentEnumerationComplete(); - } - - private void CollectWorker(CancellationToken cancelToken) - { - try - { - double collectionIntervalSecs = -1; - lock (this) - { - collectionIntervalSecs = _collectionPeriod.TotalSeconds; - } - Debug.Assert(collectionIntervalSecs >= MinCollectionTimeSecs); - - DateTime startTime = DateTime.UtcNow; - DateTime intervalStartTime = startTime; - while (!cancelToken.IsCancellationRequested) - { - // intervals end at startTime + X*collectionIntervalSecs. Under normal - // circumstance X increases by 1 each interval, but if the time it - // takes to do collection is very large then we might need to skip - // ahead multiple intervals to catch back up. - // - DateTime now = DateTime.UtcNow; - double secsSinceStart = (now - startTime).TotalSeconds; - double alignUpSecsSinceStart = Math.Ceiling(secsSinceStart / collectionIntervalSecs) * - collectionIntervalSecs; - DateTime nextIntervalStartTime = startTime.AddSeconds(alignUpSecsSinceStart); - - // The delay timer precision isn't exact. We might have a situation - // where in the previous loop iterations intervalStartTime=20.00, - // nextIntervalStartTime=21.00, the timer was supposed to delay for 1s but - // it exited early so we looped around and DateTime.Now=20.99. - // Aligning up from DateTime.Now would give us 21.00 again so we also need to skip - // forward one time interval - DateTime minNextInterval = intervalStartTime.AddSeconds(collectionIntervalSecs); - if (nextIntervalStartTime <= minNextInterval) - { - nextIntervalStartTime = minNextInterval; - } - - // pause until the interval is complete - TimeSpan delayTime = nextIntervalStartTime - now; - if (cancelToken.WaitHandle.WaitOne(delayTime)) - { - // don't do collection if timer may not have run to completion - break; - } - - // collect statistics for the completed interval - _beginCollection(intervalStartTime, nextIntervalStartTime); - Collect(); - _endCollection(intervalStartTime, nextIntervalStartTime); - intervalStartTime = nextIntervalStartTime; - } - } - catch (Exception exception) - { - _collectionError(exception); - } - } - - public void Dispose() - { - _cts.Cancel(); - if (_collectThread != null) - { - _collectThread.Join(); - _collectThread = null; - } - _listener.Dispose(); - } - - private void RemoveInstrumentState(Instrument instrument, InstrumentState state) - { - _instrumentStates.TryRemove(instrument, out _); - } - - private InstrumentState? GetInstrumentState(Instrument instrument) - { - if (!_instrumentStates.TryGetValue(instrument, out InstrumentState? instrumentState)) - { - lock (this) // protect _instrumentConfigFuncs list - { - foreach (Predicate filter in _instrumentConfigFuncs) - { - if (filter(instrument)) - { - instrumentState = BuildInstrumentState(instrument); - if (instrumentState != null) - { - _instrumentStates.TryAdd(instrument, instrumentState); - // I don't think it is possible for the instrument to be removed immediately - // and instrumentState = _instrumentStates[instrument] should work, but writing - // this defensively. - _instrumentStates.TryGetValue(instrument, out instrumentState); - } - break; - } - } - } - } - return instrumentState; - } - - [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", - Justification = "MakeGenericType is creating instances over reference types that works fine in AOT.")] - internal InstrumentState? BuildInstrumentState(Instrument instrument) - { - Func? createAggregatorFunc = GetAggregatorFactory(instrument); - if (createAggregatorFunc == null) - { - return null; - } - Type aggregatorType = createAggregatorFunc.GetType().GenericTypeArguments[0]; - Type instrumentStateType = typeof(InstrumentState<>).MakeGenericType(aggregatorType); - return (InstrumentState)Activator.CreateInstance(instrumentStateType, createAggregatorFunc)!; - } - - private Func? GetAggregatorFactory(Instrument instrument) - { - Type type = instrument.GetType(); - Type? genericDefType = null; - genericDefType = type.IsGenericType ? type.GetGenericTypeDefinition() : null; - if (genericDefType == typeof(Counter<>)) - { - return () => - { - lock (this) - { - return CheckTimeSeriesAllowed() ? new RateSumAggregator() : null; - } - }; - } - else if (genericDefType == typeof(ObservableCounter<>)) - { - return () => - { - lock (this) - { - return CheckTimeSeriesAllowed() ? new RateAggregator() : null; - } - }; - } - else if (genericDefType == typeof(ObservableGauge<>)) - { - return () => - { - lock (this) - { - return CheckTimeSeriesAllowed() ? new LastValue() : null; - } - }; - } - else if (genericDefType == typeof(Histogram<>)) - { - return () => - { - lock (this) - { - // checking currentHistograms first because avoiding unexpected increment of TimeSeries count. - return (!CheckHistogramAllowed() || !CheckTimeSeriesAllowed()) ? - null : - new ExponentialHistogramAggregator(s_defaultHistogramConfig); - } - }; - } - else - { - return null; - } - } - - private bool CheckTimeSeriesAllowed() - { - if (_currentTimeSeries < _maxTimeSeries) - { - _currentTimeSeries++; - return true; - } - else if (_currentTimeSeries == _maxTimeSeries) - { - _currentTimeSeries++; - _timeSeriesLimitReached(); - return false; - } - else - { - return false; - } - } - - private bool CheckHistogramAllowed() - { - if (_currentHistograms < _maxHistograms) - { - _currentHistograms++; - return true; - } - else if (_currentHistograms == _maxHistograms) - { - _currentHistograms++; - _histogramLimitReached(); - return false; - } - else - { - return false; - } - } - - internal void Collect() - { - try - { - _listener.RecordObservableInstruments(); - } - catch (Exception exception) - { - _observableInstrumentCallbackError(exception); - } - - foreach (KeyValuePair kv in _instrumentStates) - { - kv.Value.Collect(kv.Key, (LabeledAggregationStatistics labeledAggStats) => - { - _collectMeasurement(kv.Key, labeledAggStats); - }); - } - } - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/Aggregator.cs b/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/Aggregator.cs deleted file mode 100644 index 4dc38fc114..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/Aggregator.cs +++ /dev/null @@ -1,68 +0,0 @@ -#pragma warning disable -// Steeltoe: Copy of version in System.Diagnostics.Metrics (see README.md for details). - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Diagnostics.Tracing; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics.SystemDiagnosticsMetrics -{ - internal abstract class Aggregator - { - // This can be called concurrently with Collect() - public abstract void Update(double measurement); - - // This can be called concurrently with Update() - public abstract IAggregationStatistics Collect(); - } - - internal interface IAggregationStatistics { } - - internal readonly struct QuantileValue - { - public QuantileValue(double quantile, double value) - { - Quantile = quantile; - Value = value; - } - public double Quantile { get; } - public double Value { get; } - } - - internal sealed class HistogramStatistics : IAggregationStatistics - { - // Steeltoe-Start: Track sum and max. - //internal HistogramStatistics(QuantileValue[] quantiles) - internal HistogramStatistics(QuantileValue[] quantiles, double sum, double max) - // Steeltoe-End: Track sum and max. - { - Quantiles = quantiles; - - // Steeltoe-Start: Track sum and max. - HistogramSum = sum; - HistogramMax = max; - // Steeltoe-End: Track sum and max. - } - - public QuantileValue[] Quantiles { get; } - - // Steeltoe-Start: Track sum and max. - public double HistogramSum { get; } - public double HistogramMax { get; } - // Steeltoe-End: Track sum and max. - } - - internal sealed class LabeledAggregationStatistics - { - public LabeledAggregationStatistics(IAggregationStatistics stats, params KeyValuePair[] labels) - { - AggregationStatistics = stats; - Labels = labels; - } - - public KeyValuePair[] Labels { get; } - public IAggregationStatistics AggregationStatistics { get; } - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/AggregatorStore.cs b/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/AggregatorStore.cs deleted file mode 100644 index 0b9166dacf..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/AggregatorStore.cs +++ /dev/null @@ -1,520 +0,0 @@ -#pragma warning disable -// Steeltoe: Copy of version in System.Diagnostics.Metrics (see README.md for details). - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Threading; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics.SystemDiagnosticsMetrics -{ - /// - /// AggregatorStore is a high performance map from an unordered list of labels (KeyValuePairs) to an instance of TAggregator - /// - /// The type of Aggregator returned by the store - // - // This is implemented as a two level Dictionary lookup with a number of optimizations applied. The conceptual lookup is: - // 1. Sort ReadOnlySpan> by the key names - // 2. Split ReadOnlySpan> into ReadOnlySpan and ReadOnlySpan - // 3. LabelNameDictionary.Lookup(ReadOnlySpan) -> ConcurrentDictionary - // 4. ConcurrentDictionary.Lookup(ReadOnlySpan) -> TAggregator - // - // There are several things we are optimizing for: - // - CPU instructions per lookup: In the common case the key portion of the KeyValuePairs is unchanged between requests - // and they are given in the same order. This means we can cache the 2nd level concurrent dictionary and the permutation that - // will sort the labels as long as we determine the keys are unchanged from the previous request. The first time a new set of - // keys is observed we call into LabelInstructionCompiler.Create which will determine the canonical sort order, do the 1st level - // lookup, and then return a new _cachedLookupFunc. Invoking _cachedLookupFunc confirms the keys match what was previously - // observed, re-orders the values with the cached permutation and performs the 2nd level lookup against the cached 2nd level - // Dictionary. If we wanted to get really fancy we could have that compiler generate IL that would be JIT compiled, but right now - // LabelInstructionCompiler simply creates a managed data structure (LabelInstructionInterpreter) that encodes the permutation - // in an array of LabelInstructions and the 2nd level dictionary in another field. LabelInstructionInterpreter.GetAggregator - // re-orders the values with a for loop and then does the lookup. Depending on ratio between fast-path and slow-path invocations - // it may also not be a win to further pessimize the slow-path (JIT compilation is expensive) to squeeze yet more cycles out of - // the fast path. - // - Allocations per lookup: Any lookup of 3 or fewer labels on the above fast path is allocation free. We have separate - // dictionaries dependending on the number of labels in the list and the dictionary keys are structures representing fixed size - // lists of strings or objects. For example with two labels the lookup is done in a - // FixedSizeLabelNameDictionary> - // Above 3 labels we have StringSequenceMany and ObjectSequenceMany which wraps an underlying string[] or object?[] respectively. - // Doing a lookup with those types will need to do allocations for those arrays. - // - Total memory footprint per-store: We have a store for every instrument we are tracking and an entry in the 2nd level - // dictionary for every label set. This can add up to a lot of entries. Splitting the label sets into keys and values means we - // only need to store each unique key list once (as the key of the 1st level dictionary). It is common for all labelsets on an - // instrument to have the same keys so this can be a sizable savings. We also use a union to store the 1st level dictionaries - // for different label set sizes because most instruments always specify labelsets with the same number of labels (most likely - // zero). - [System.Security.SecuritySafeCritical] // using SecurityCritical type ReadOnlySpan - internal struct AggregatorStore where TAggregator : Aggregator - { - // this union can be: - // null - // TAggregator - // FixedSizeLabelNameDictionary> - // FixedSizeLabelNameDictionary> - // FixedSizeLabelNameDictionary> - // FixedSizeLabelNameDictionary> - // MultiSizeLabelNameDictionary - this is used when we need to store more than one of the above union items - private volatile object? _stateUnion; - private volatile AggregatorLookupFunc? _cachedLookupFunc; - private readonly Func _createAggregatorFunc; - - public AggregatorStore(Func createAggregator) - { - _stateUnion = null; - _cachedLookupFunc = null; - _createAggregatorFunc = createAggregator; - } - - public TAggregator? GetAggregator(ReadOnlySpan> labels) - { - AggregatorLookupFunc? lookupFunc = _cachedLookupFunc; - if (lookupFunc != null) - { - if (lookupFunc(labels, out TAggregator? aggregator)) return aggregator; - } - - // slow path, label names have changed from what the lookupFunc cached so we need to - // rebuild it - return GetAggregatorSlow(labels); - } - - private TAggregator? GetAggregatorSlow(ReadOnlySpan> labels) - { - AggregatorLookupFunc lookupFunc = LabelInstructionCompiler.Create(ref this, _createAggregatorFunc, labels); - _cachedLookupFunc = lookupFunc; - bool match = lookupFunc(labels, out TAggregator? aggregator); - Debug.Assert(match); - return aggregator; - } - - public void Collect(Action visitFunc) - { - object? stateUnion = _stateUnion; - switch (_stateUnion) - { - case TAggregator agg: - IAggregationStatistics stats = agg.Collect(); - visitFunc(new LabeledAggregationStatistics(stats)); - break; - - case FixedSizeLabelNameDictionary aggs1: - aggs1.Collect(visitFunc); - break; - - case FixedSizeLabelNameDictionary aggs2: - aggs2.Collect(visitFunc); - break; - - case FixedSizeLabelNameDictionary aggs3: - aggs3.Collect(visitFunc); - break; - - case FixedSizeLabelNameDictionary aggsMany: - aggsMany.Collect(visitFunc); - break; - - case MultiSizeLabelNameDictionary aggsMultiSize: - aggsMultiSize.Collect(visitFunc); - break; - } - } - - - public TAggregator? GetAggregator() - { - while (true) - { - object? state = _stateUnion; - if (state == null) - { - // running this delegate will increment the counter for the number of time series - // even though in the rare race condition we don't store it. If we wanted to be perfectly - // accurate we need to decrement the counter again, but I don't think mitigating that - // error is worth the complexity - TAggregator? newState = _createAggregatorFunc(); - if (newState == null) - { - return newState; - } - if (Interlocked.CompareExchange(ref _stateUnion, newState, null) is null) - { - return newState; - } - continue; - } - else if (state is TAggregator aggState) - { - return aggState; - } - else if (state is MultiSizeLabelNameDictionary multiSizeState) - { - return multiSizeState.GetNoLabelAggregator(_createAggregatorFunc); - } - else - { - MultiSizeLabelNameDictionary newState = new(state); - if (Interlocked.CompareExchange(ref _stateUnion, newState, state) == state) - { - return newState.GetNoLabelAggregator(_createAggregatorFunc); - } - continue; - } - } - } - - public ConcurrentDictionary GetLabelValuesDictionary(in TStringSequence names) - where TStringSequence : IStringSequence, IEquatable - where TObjectSequence : IObjectSequence, IEquatable - { - while (true) - { - object? state = _stateUnion; - if (state == null) - { - FixedSizeLabelNameDictionary newState = new(); - if (Interlocked.CompareExchange(ref _stateUnion, newState, null) is null) - { - return newState.GetValuesDictionary(names); - } - continue; - } - else if (state is FixedSizeLabelNameDictionary fixedState) - { - return fixedState.GetValuesDictionary(names); - } - else if (state is MultiSizeLabelNameDictionary multiSizeState) - { - return multiSizeState.GetFixedSizeLabelNameDictionary().GetValuesDictionary(names); - } - else - { - MultiSizeLabelNameDictionary newState = new(state); - if (Interlocked.CompareExchange(ref _stateUnion, newState, state) == state) - { - return newState.GetFixedSizeLabelNameDictionary().GetValuesDictionary(names); - } - continue; - } - } - } - } - - internal sealed class MultiSizeLabelNameDictionary where TAggregator : Aggregator - { - private TAggregator? NoLabelAggregator; - private FixedSizeLabelNameDictionary? Label1; - private FixedSizeLabelNameDictionary? Label2; - private FixedSizeLabelNameDictionary? Label3; - private FixedSizeLabelNameDictionary? LabelMany; - - public MultiSizeLabelNameDictionary(object initialLabelNameDict) - { - NoLabelAggregator = null; - Label1 = null; - Label2 = null; - Label3 = null; - LabelMany = null; - switch (initialLabelNameDict) - { - case TAggregator val0: - NoLabelAggregator = val0; - break; - - case FixedSizeLabelNameDictionary val1: - Label1 = val1; - break; - - case FixedSizeLabelNameDictionary val2: - Label2 = val2; - break; - - case FixedSizeLabelNameDictionary val3: - Label3 = val3; - break; - - case FixedSizeLabelNameDictionary valMany: - LabelMany = valMany; - break; - } - } - - public TAggregator? GetNoLabelAggregator(Func createFunc) - { - if (NoLabelAggregator == null) - { - TAggregator? aggregator = createFunc(); - if (aggregator != null) - { - Interlocked.CompareExchange(ref NoLabelAggregator, aggregator, null); - } - } - return NoLabelAggregator; - } - - public FixedSizeLabelNameDictionary GetFixedSizeLabelNameDictionary() - where TStringSequence : IStringSequence, IEquatable - where TObjectSequence : IObjectSequence, IEquatable - { - TStringSequence? seq = default; - switch (seq) - { - case StringSequence1: - if (Label1 == null) - { - Interlocked.CompareExchange(ref Label1, new FixedSizeLabelNameDictionary(), null); - } - return (FixedSizeLabelNameDictionary)(object)Label1; - - case StringSequence2: - if (Label2 == null) - { - Interlocked.CompareExchange(ref Label2, new FixedSizeLabelNameDictionary(), null); - } - return (FixedSizeLabelNameDictionary)(object)Label2; - - case StringSequence3: - if (Label3 == null) - { - Interlocked.CompareExchange(ref Label3, new FixedSizeLabelNameDictionary(), null); - } - return (FixedSizeLabelNameDictionary)(object)Label3; - - case StringSequenceMany: - if (LabelMany == null) - { - Interlocked.CompareExchange(ref LabelMany, new FixedSizeLabelNameDictionary(), null); - } - return (FixedSizeLabelNameDictionary)(object)LabelMany; - - default: - // we should never get here unless this library has a bug - Debug.Fail("Unexpected sequence type"); - return null; - } - } - - public void Collect(Action visitFunc) - { - if (NoLabelAggregator != null) - { - IAggregationStatistics stats = NoLabelAggregator.Collect(); - visitFunc(new LabeledAggregationStatistics(stats)); - } - Label1?.Collect(visitFunc); - Label2?.Collect(visitFunc); - Label3?.Collect(visitFunc); - LabelMany?.Collect(visitFunc); - } - } - - internal struct LabelInstruction - { - public LabelInstruction(int sourceIndex, string labelName) - { - SourceIndex = sourceIndex; - LabelName = labelName; - } - public readonly int SourceIndex { get; } - public readonly string LabelName { get; } - } - - internal delegate bool AggregatorLookupFunc(ReadOnlySpan> labels, out TAggregator? aggregator); - - [System.Security.SecurityCritical] // using SecurityCritical type ReadOnlySpan - internal static class LabelInstructionCompiler - { - public static AggregatorLookupFunc Create( - ref AggregatorStore aggregatorStore, - Func createAggregatorFunc, - ReadOnlySpan> labels) - where TAggregator : Aggregator - { - LabelInstruction[] instructions = Compile(labels); - Array.Sort(instructions, (LabelInstruction a, LabelInstruction b) => string.CompareOrdinal(a.LabelName, b.LabelName)); - int expectedLabels = labels.Length; - switch (instructions.Length) - { - case 0: - TAggregator? defaultAggregator = aggregatorStore.GetAggregator(); - return (ReadOnlySpan> l, out TAggregator? aggregator) => - { - if (l.Length != expectedLabels) - { - aggregator = null; - return false; - } - aggregator = defaultAggregator; - return true; - }; - - case 1: - StringSequence1 names1 = new StringSequence1(instructions[0].LabelName); - ConcurrentDictionary valuesDict1 = - aggregatorStore.GetLabelValuesDictionary(names1); - LabelInstructionInterpreter interpreter1 = - new LabelInstructionInterpreter( - expectedLabels, instructions, valuesDict1, createAggregatorFunc); - return interpreter1.GetAggregator; - - case 2: - StringSequence2 names2 = new StringSequence2(instructions[0].LabelName, instructions[1].LabelName); - ConcurrentDictionary valuesDict2 = - aggregatorStore.GetLabelValuesDictionary(names2); - LabelInstructionInterpreter interpreter2 = - new LabelInstructionInterpreter( - expectedLabels, instructions, valuesDict2, createAggregatorFunc); - return interpreter2.GetAggregator; - - case 3: - StringSequence3 names3 = new StringSequence3(instructions[0].LabelName, instructions[1].LabelName, - instructions[2].LabelName); - ConcurrentDictionary valuesDict3 = - aggregatorStore.GetLabelValuesDictionary(names3); - LabelInstructionInterpreter interpreter3 = - new LabelInstructionInterpreter( - expectedLabels, instructions, valuesDict3, createAggregatorFunc); - return interpreter3.GetAggregator; - - default: - string[] labelNames = new string[instructions.Length]; - for (int i = 0; i < instructions.Length; i++) - { - labelNames[i] = instructions[i].LabelName; - } - StringSequenceMany namesMany = new StringSequenceMany(labelNames); - ConcurrentDictionary valuesDictMany = - aggregatorStore.GetLabelValuesDictionary(namesMany); - LabelInstructionInterpreter interpreter4 = - new LabelInstructionInterpreter( - expectedLabels, instructions, valuesDictMany, createAggregatorFunc); - return interpreter4.GetAggregator; - } - } - - private static LabelInstruction[] Compile(ReadOnlySpan> labels) - { - LabelInstruction[] valueFetches = new LabelInstruction[labels.Length]; - for (int i = 0; i < labels.Length; i++) - { - valueFetches[i] = new LabelInstruction(i, labels[i].Key); - } - - return valueFetches; - } - } - - [System.Security.SecurityCritical] // using SecurityCritical type ReadOnlySpan - internal sealed class LabelInstructionInterpreter - where TObjectSequence : struct, IObjectSequence, IEquatable - where TAggregator : Aggregator - { - private int _expectedLabelCount; - private LabelInstruction[] _instructions; - private ConcurrentDictionary _valuesDict; - private Func _createAggregator; - - public LabelInstructionInterpreter( - int expectedLabelCount, - LabelInstruction[] instructions, - ConcurrentDictionary valuesDict, - Func createAggregator) - { - _expectedLabelCount = expectedLabelCount; - _instructions = instructions; - _valuesDict = valuesDict; - _createAggregator = _ => createAggregator(); - } - - // Returns true if label keys matched what was expected - // aggregator may be null even when true is returned if - // we have hit the storage limits - public bool GetAggregator( - ReadOnlySpan> labels, - out TAggregator? aggregator) - { - aggregator = null; - if (labels.Length != _expectedLabelCount) - { - return false; - } - - TObjectSequence values = default; - if (values is ObjectSequenceMany) - { - values = (TObjectSequence)(object)new ObjectSequenceMany(new object[_expectedLabelCount]); - } -#if MEMORYMARSHAL_SUPPORT - Span indexedValues = values.AsSpan(); -#else - ref TObjectSequence indexedValues = ref values; -#endif - for (int i = 0; i < _instructions.Length; i++) - { - LabelInstruction instr = _instructions[i]; - if (instr.LabelName != labels[instr.SourceIndex].Key) - { - return false; - } - indexedValues[i] = labels[instr.SourceIndex].Value; - } - - if (!_valuesDict.TryGetValue(values, out aggregator)) - { - // running this delegate will increment the counter for the number of time series - // even though in the rare race condition we don't store it. If we wanted to be perfectly - // accurate we need to decrement the counter again, but I don't think mitigating that - // error is worth the complexity - aggregator = _createAggregator(values); - if (aggregator is null) - { - return true; - } - aggregator = _valuesDict.GetOrAdd(values, aggregator); - } - return true; - } - } - - internal sealed class FixedSizeLabelNameDictionary : - ConcurrentDictionary> - where TAggregator : Aggregator - where TStringSequence : IStringSequence, IEquatable - where TObjectSequence : IObjectSequence, IEquatable - { - public void Collect(Action visitFunc) - { - foreach (KeyValuePair> kvName in this) - { -#if MEMORYMARSHAL_SUPPORT - Span indexedNames = kvName.Key.AsSpan(); -#else - TStringSequence indexedNames = kvName.Key; -#endif - foreach (KeyValuePair kvValue in kvName.Value) - { -#if MEMORYMARSHAL_SUPPORT - Span indexedValues = kvValue.Key.AsSpan(); -#else - TObjectSequence indexedValues = kvValue.Key; -#endif - KeyValuePair[] labels = new KeyValuePair[indexedNames.Length]; - for (int i = 0; i < labels.Length; i++) - { - labels[i] = new KeyValuePair(indexedNames[i], indexedValues[i]?.ToString() ?? ""); - } - IAggregationStatistics stats = kvValue.Value.Collect(); - visitFunc(new LabeledAggregationStatistics(stats, labels)); - } - } - } - - public ConcurrentDictionary GetValuesDictionary(in TStringSequence names) => - GetOrAdd(names, _ => new ConcurrentDictionary()); - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/ExponentialHistogramAggregator.cs b/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/ExponentialHistogramAggregator.cs deleted file mode 100644 index fe6e7573a8..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/ExponentialHistogramAggregator.cs +++ /dev/null @@ -1,266 +0,0 @@ -#pragma warning disable -// Steeltoe: Copy of version in System.Diagnostics.Metrics (see README.md for details). - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Diagnostics; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics.SystemDiagnosticsMetrics -{ - internal sealed class QuantileAggregation - { - public QuantileAggregation(params double[] quantiles) - { - Quantiles = quantiles; - Array.Sort(Quantiles); - } - - public double[] Quantiles { get; set; } - public double MaxRelativeError { get; set; } = 0.001; - } - - - - // This histogram ensures that the quantiles reported from the histogram are within some bounded % error of the correct - // value. More mathematically, if we have a set of measurements where quantile X = Y, the histogram should always report a - // value Y` where Y*(1-E) <= Y` <= Y*(1+E). E is our allowable error, so if E = 0.01 then the reported value Y` is - // between 0.99*Y and 1.01*Y. We achieve this by ensuring that if a bucket holds measurements from M_min to M_max - // then M_max - M_min <= M_min*E. We can determine which bucket must hold quantile X and we know that all values assigned to - // the bucket are within the error bound if we approximate the result as M_min. - // Note: we should be able to refine this to return the bucket midpoint rather than bucket lower bound, halving the number of - // buckets to achieve the same error bound. - // - // Implementation: The histogram buckets are implemented as an array of arrays (a tree). The top level has a fixed 4096 entries - // corresponding to every possible sign+exponent in the encoding of a double (IEEE 754 spec). The 2nd level has variable size - // depending on how many buckets are needed to achieve the error bounds. For ease of insertion we round the 2nd level size up to - // the nearest power of 2. This lets us mask off the first k bits in the mantissa to map a measurement to one of 2^k 2nd level - // buckets. The top level array is pre-allocated but the 2nd level arrays are created on demand. - // - // PERF Note: This histogram has a fast Update() but the _counters array has a sizable memory footprint (32KB+ on 64 bit) - // It is probably well suited for tracking 10s or maybe 100s of histograms but if we wanted to go higher - // we probably want to trade a little more CPU cost in Update() + code complexity to avoid eagerly allocating 4096 - // top level entries. - internal sealed class ExponentialHistogramAggregator : Aggregator - { - private const int ExponentArraySize = 4096; - private const int ExponentShift = 52; - private const double MinRelativeError = 0.0001; - - private readonly QuantileAggregation _config; - private int[]?[] _counters; - private int _count; - private readonly int _mantissaMax; - private readonly int _mantissaMask; - private readonly int _mantissaShift; - - // Steeltoe-Start: Track sum and max. - private double _sum; - private double _max; - // Steeltoe-End: Track sum and max. - - private struct Bucket - { - public Bucket(double value, int count) - { - Value = value; - Count = count; - } - public double Value; - public int Count; - } - - public ExponentialHistogramAggregator(QuantileAggregation config) - { - _config = config; - _counters = new int[ExponentArraySize][]; - if (_config.MaxRelativeError < MinRelativeError) - { - // Ensure that we don't create enormous histograms trying to get overly high precision - throw new ArgumentException(); - } - int mantissaBits = (int)Math.Ceiling(Math.Log(1 / _config.MaxRelativeError, 2)) - 1; - _mantissaShift = 52 - mantissaBits; - _mantissaMax = 1 << mantissaBits; - _mantissaMask = _mantissaMax - 1; - } - - public override IAggregationStatistics Collect() - { - int[]?[] counters; - int count; - - // Steeltoe-Start: Track sum and max. - double sum; - double max; - // Steeltoe-End: Track sum and max. - - lock (this) - { - counters = _counters; - count = _count; - - // Steeltoe-Start: Track sum and max. - sum = _sum; - max = _max; - // Steeltoe-End: Track sum and max. - - _counters = new int[ExponentArraySize][]; - _count = 0; - - // Steeltoe-Start: Track sum and max. - _sum = 0; - _max = 0; - // Steeltoe-End: Track sum and max. - } - - QuantileValue[] quantiles = new QuantileValue[_config.Quantiles.Length]; - int nextQuantileIndex = 0; - if (nextQuantileIndex == _config.Quantiles.Length) - { - // Steeltoe-Start: Track sum and max. - //return new HistogramStatistics(quantiles); - return new HistogramStatistics(quantiles, sum, max); - // Steeltoe-End: Track sum and max. - } - - // Reduce the count if there are any NaN or +/-Infinity values that were logged - count -= GetInvalidCount(counters); - - // Consider each bucket to have N entries in it, and each entry has value GetBucketCanonicalValue(). - // If all these entries were inserted in a sorted array, we are trying to find the value of the entry with - // index=target. - int target = QuantileToRank(_config.Quantiles[nextQuantileIndex], count); - - // the total number of entries in all buckets iterated so far - int cur = 0; - foreach (Bucket b in IterateBuckets(counters)) - { - cur += b.Count; - while (cur > target) - { - quantiles[nextQuantileIndex] = new QuantileValue( - _config.Quantiles[nextQuantileIndex], b.Value); - nextQuantileIndex++; - if (nextQuantileIndex == _config.Quantiles.Length) - { - // Steeltoe-Start: Track sum and max. - //return new HistogramStatistics(quantiles); - return new HistogramStatistics(quantiles, sum, max); - // Steeltoe-End: Track sum and max. - } - target = QuantileToRank(_config.Quantiles[nextQuantileIndex], count); - } - } - - Debug.Assert(count == 0); - - // Steeltoe-Start: Track sum and max. - //return new HistogramStatistics(Array.Empty()); - return new HistogramStatistics(Array.Empty(), sum, max); - // Steeltoe-End: Track sum and max. - } - - private static int GetInvalidCount(int[]?[] counters) - { - int[]? positiveInfAndNan = counters[ExponentArraySize / 2 - 1]; - int[]? negativeInfAndNan = counters[ExponentArraySize - 1]; - int count = 0; - if (positiveInfAndNan != null) - { - foreach (int bucketCount in positiveInfAndNan) - { - count += bucketCount; - } - } - if (negativeInfAndNan != null) - { - foreach (int bucketCount in negativeInfAndNan) - { - count += bucketCount; - } - } - return count; - } - - private IEnumerable IterateBuckets(int[]?[] counters) - { - // iterate over the negative exponent buckets - const int LowestNegativeOffset = ExponentArraySize / 2; - // exponent = ExponentArraySize-1 encodes infinity and NaN, which we want to ignore - for (int exponent = ExponentArraySize-2; exponent >= LowestNegativeOffset; exponent--) - { - int[]? mantissaCounts = counters[exponent]; - if (mantissaCounts == null) - { - continue; - } - for (int mantissa = _mantissaMax-1; mantissa >= 0; mantissa--) - { - int count = mantissaCounts[mantissa]; - if (count > 0) - { - yield return new Bucket(GetBucketCanonicalValue(exponent, mantissa), count); - } - } - } - - // iterate over the positive exponent buckets - // exponent = lowestNegativeOffset-1 encodes infinity and NaN, which we want to ignore - for (int exponent = 0; exponent < LowestNegativeOffset-1; exponent++) - { - int[]? mantissaCounts = counters[exponent]; - if (mantissaCounts == null) - { - continue; - } - for (int mantissa = 0; mantissa < _mantissaMax; mantissa++) - { - int count = mantissaCounts[mantissa]; - if (count > 0) - { - yield return new Bucket(GetBucketCanonicalValue(exponent, mantissa), count); - } - } - } - } - - public override void Update(double measurement) - { - lock (this) - { - // Steeltoe-Start: Track sum and max. - _sum += measurement; - _max = Math.Max(_max, measurement); - // Steeltoe-End: Track sum and max. - - // This is relying on the bit representation of IEEE 754 to decompose - // the double. The sign bit + exponent bits land in exponent, the - // remainder lands in mantissa. - // the bucketing precision comes entirely from how many significant - // bits of the mantissa are preserved. - ulong bits = (ulong)BitConverter.DoubleToInt64Bits(measurement); - int exponent = (int)(bits >> ExponentShift); - int mantissa = (int)(bits >> _mantissaShift) & _mantissaMask; - ref int[]? mantissaCounts = ref _counters[exponent]; - mantissaCounts ??= new int[_mantissaMax]; - mantissaCounts[mantissa]++; - _count++; - } - } - - private static int QuantileToRank(double quantile, int count) - { - return Math.Min(Math.Max(0, (int)(quantile * count)), count - 1); - } - - // This is the upper bound for negative valued buckets and the - // lower bound for positive valued buckets - private double GetBucketCanonicalValue(int exponent, int mantissa) - { - long bits = ((long)exponent << ExponentShift) | ((long)mantissa << _mantissaShift); - return BitConverter.Int64BitsToDouble(bits); - } - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/InstrumentState.cs b/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/InstrumentState.cs deleted file mode 100644 index 1b8bc5a68e..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/InstrumentState.cs +++ /dev/null @@ -1,46 +0,0 @@ -#pragma warning disable -// Steeltoe: Copy of version in System.Diagnostics.Metrics (see README.md for details). - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Diagnostics.Metrics; -using System.Security; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics.SystemDiagnosticsMetrics -{ - internal abstract class InstrumentState - { - // This can be called concurrently with Collect() - [SecuritySafeCritical] - public abstract void Update(double measurement, ReadOnlySpan> labels); - - // This can be called concurrently with Update() - public abstract void Collect(Instrument instrument, Action aggregationVisitFunc); - } - - - internal sealed class InstrumentState : InstrumentState - where TAggregator : Aggregator - { - private AggregatorStore _aggregatorStore; - - public InstrumentState(Func createAggregatorFunc) - { - _aggregatorStore = new AggregatorStore(createAggregatorFunc); - } - - public override void Collect(Instrument instrument, Action aggregationVisitFunc) - { - _aggregatorStore.Collect(aggregationVisitFunc); - } - - [SecuritySafeCritical] - public override void Update(double measurement, ReadOnlySpan> labels) - { - TAggregator? aggregator = _aggregatorStore.GetAggregator(labels); - aggregator?.Update(measurement); - } - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/LastValueAggregator.cs b/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/LastValueAggregator.cs deleted file mode 100644 index 0ae6d0faaf..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/LastValueAggregator.cs +++ /dev/null @@ -1,38 +0,0 @@ -#pragma warning disable -// Steeltoe: Copy of version in System.Diagnostics.Metrics (see README.md for details). - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics.SystemDiagnosticsMetrics -{ - internal sealed class LastValue : Aggregator - { - private double? _lastValue; - - public override void Update(double value) - { - _lastValue = value; - } - - public override IAggregationStatistics Collect() - { - lock (this) - { - LastValueStatistics stats = new LastValueStatistics(_lastValue); - _lastValue = null; - return stats; - } - } - } - - internal sealed class LastValueStatistics : IAggregationStatistics - { - internal LastValueStatistics(double? lastValue) - { - LastValue = lastValue; - } - - public double? LastValue { get; } - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/ObjectSequence.cs b/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/ObjectSequence.cs deleted file mode 100644 index 2fe572f9f7..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/ObjectSequence.cs +++ /dev/null @@ -1,125 +0,0 @@ -#pragma warning disable -// Steeltoe: Copy of version in System.Diagnostics.Metrics (see README.md for details). - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Numerics; -using System.Runtime.InteropServices; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics.SystemDiagnosticsMetrics -{ - internal partial struct ObjectSequence1 : IEquatable, IObjectSequence - { - public object? Value1; - - public ObjectSequence1(object? value1) - { - Value1 = value1; - } - - public override int GetHashCode() => Value1?.GetHashCode() ?? 0; - - public bool Equals(ObjectSequence1 other) - { - return Value1 is null ? other.Value1 is null : Value1.Equals(other.Value1); - } - - //GetHashCode() is in the platform specific files - public override bool Equals(object? obj) - { - return obj is ObjectSequence1 os1 && Equals(os1); - } - } - - internal partial struct ObjectSequence2 : IEquatable, IObjectSequence - { - public object? Value1; - public object? Value2; - - public ObjectSequence2(object? value1, object? value2) - { - Value1 = value1; - Value2 = value2; - } - - public bool Equals(ObjectSequence2 other) - { - return (Value1 is null ? other.Value1 is null : Value1.Equals(other.Value1)) && - (Value2 is null ? other.Value2 is null : Value2.Equals(other.Value2)); - } - - //GetHashCode() is in the platform specific files - public override bool Equals(object? obj) - { - return obj is ObjectSequence2 os2 && Equals(os2); - } - } - - internal partial struct ObjectSequence3 : IEquatable, IObjectSequence - { - public object? Value1; - public object? Value2; - public object? Value3; - - public ObjectSequence3(object? value1, object? value2, object? value3) - { - Value1 = value1; - Value2 = value2; - Value3 = value3; - } - - public bool Equals(ObjectSequence3 other) - { - return (Value1 is null ? other.Value1 is null : Value1.Equals(other.Value1)) && - (Value2 is null ? other.Value2 is null : Value2.Equals(other.Value2)) && - (Value3 is null ? other.Value3 is null : Value3.Equals(other.Value3)); - } - - //GetHashCode() is in the platform specific files - public override bool Equals(object? obj) - { - return obj is ObjectSequence3 os3 && Equals(os3); - } - } - - internal partial struct ObjectSequenceMany : IEquatable, IObjectSequence - { - private readonly object?[] _values; - - public ObjectSequenceMany(object[] values) - { - _values = values; - } - - public bool Equals(ObjectSequenceMany other) - { - if (_values.Length != other._values.Length) - { - return false; - } - for (int i = 0; i < _values.Length; i++) - { - object? value = _values[i], otherValue = other._values[i]; - if (value is null) - { - if (otherValue is not null) - { - return false; - } - } - else if (!value.Equals(otherValue)) - { - return false; - } - } - return true; - } - - //GetHashCode() is in the platform specific files - public override bool Equals(object? obj) - { - return obj is ObjectSequenceMany osm && Equals(osm); - } - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/ObjectSequence.netcore.cs b/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/ObjectSequence.netcore.cs deleted file mode 100644 index 352f9e8bb1..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/ObjectSequence.netcore.cs +++ /dev/null @@ -1,63 +0,0 @@ -#pragma warning disable -// Steeltoe: Copy of version in System.Diagnostics.Metrics (see README.md for details). - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Numerics; -using System.Runtime.InteropServices; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics.SystemDiagnosticsMetrics -{ - internal interface IObjectSequence - { - Span AsSpan(); - } - - internal partial struct ObjectSequence1 : IEquatable, IObjectSequence - { - public Span AsSpan() - { - return MemoryMarshal.CreateSpan(ref Value1, 1); - } - } - - internal partial struct ObjectSequence2 : IEquatable, IObjectSequence - { - public Span AsSpan() - { - return MemoryMarshal.CreateSpan(ref Value1, 2); - } - - public override int GetHashCode() => HashCode.Combine(Value1, Value2); - } - - internal partial struct ObjectSequence3 : IEquatable, IObjectSequence - { - public Span AsSpan() - { - return MemoryMarshal.CreateSpan(ref Value1, 3); - } - - public override int GetHashCode() => HashCode.Combine(Value1, Value2, Value3); - } - - internal partial struct ObjectSequenceMany : IEquatable, IObjectSequence - { - - public Span AsSpan() - { - return _values.AsSpan(); - } - - public override int GetHashCode() - { - HashCode h = default; - for (int i = 0; i < _values.Length; i++) - { - h.Add(_values[i]); - } - return h.ToHashCode(); - } - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/README.md b/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/README.md deleted file mode 100644 index 3cf9de7bb2..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# SystemDiagnosticsMetrics - -The code files in this directory were copied in their entirety from the release/7.0 branch of the .NET runtime repository on github: -https://github.com/dotnet/runtime/tree/release/7.0/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics (at commit 4bc0fe951dc90c5af58631f23f194ab7985892d5). - -This was done because Steeltoe needs access (and a few small changes) to internal types. - -## Changes - -There is a baseline commit that contains an exact copy of the code files. All changes in this repo will be separate commits on top of that. - -All warnings are turned off using `#pragma warning disable` at the top of each file and the namespaces are adjusted to Steeltoe. -This directory is excluded from Resharper code analysis and formatting. - -Aside from the above, any code changes from the baseline are wrapped in `Steeltoe-Start`/`Steeltoe-End` comment blocks. - -## License from dotnet/runtime - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/RateAggregator.cs b/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/RateAggregator.cs deleted file mode 100644 index cfd44b6a14..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/RateAggregator.cs +++ /dev/null @@ -1,70 +0,0 @@ -#pragma warning disable -// Steeltoe: Copy of version in System.Diagnostics.Metrics (see README.md for details). - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics.SystemDiagnosticsMetrics -{ - internal sealed class RateSumAggregator : Aggregator - { - private double _sum; - - public override void Update(double value) - { - lock (this) - { - _sum += value; - } - } - - public override IAggregationStatistics Collect() - { - lock (this) - { - RateStatistics? stats = new RateStatistics(_sum); - _sum = 0; - return stats; - } - } - } - - internal sealed class RateAggregator : Aggregator - { - private double? _prevValue; - private double _value; - - public override void Update(double value) - { - lock (this) - { - _value = value; - } - } - - public override IAggregationStatistics Collect() - { - lock (this) - { - double? delta = null; - if (_prevValue.HasValue) - { - delta = _value - _prevValue.Value; - } - RateStatistics stats = new RateStatistics(delta); - _prevValue = _value; - return stats; - } - } - } - - internal sealed class RateStatistics : IAggregationStatistics - { - public RateStatistics(double? delta) - { - Delta = delta; - } - - public double? Delta { get; } - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/StringSequence.cs b/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/StringSequence.cs deleted file mode 100644 index e9b321cef3..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/StringSequence.cs +++ /dev/null @@ -1,100 +0,0 @@ -#pragma warning disable -// Steeltoe: Copy of version in System.Diagnostics.Metrics (see README.md for details). - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Numerics; -using System.Runtime.InteropServices; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics.SystemDiagnosticsMetrics -{ - internal partial struct StringSequence1 : IEquatable, IStringSequence - { - public string Value1; - - public StringSequence1(string value1) - { - Value1 = value1; - } - - public override int GetHashCode() => Value1.GetHashCode(); - - public bool Equals(StringSequence1 other) - { - return Value1 == other.Value1; - } - - //GetHashCode() is in the platform specific files - public override bool Equals(object? obj) - { - return obj is StringSequence1 ss1 && Equals(ss1); - } - } - - internal partial struct StringSequence2 : IEquatable, IStringSequence - { - public string Value1; - public string Value2; - - public StringSequence2(string value1, string value2) - { - Value1 = value1; - Value2 = value2; - } - - public bool Equals(StringSequence2 other) - { - return Value1 == other.Value1 && Value2 == other.Value2; - } - - //GetHashCode() is in the platform specific files - public override bool Equals(object? obj) - { - return obj is StringSequence2 ss2 && Equals(ss2); - } - } - - internal partial struct StringSequence3 : IEquatable, IStringSequence - { - public string Value1; - public string Value2; - public string Value3; - - public StringSequence3(string value1, string value2, string value3) - { - Value1 = value1; - Value2 = value2; - Value3 = value3; - } - - public bool Equals(StringSequence3 other) - { - return Value1 == other.Value1 && Value2 == other.Value2 && Value3 == other.Value3; - } - - //GetHashCode() is in the platform specific files - public override bool Equals(object? obj) - { - return obj is StringSequence3 ss3 && Equals(ss3); - } - } - - internal partial struct StringSequenceMany : IEquatable, IStringSequence - { - private readonly string[] _values; - - public StringSequenceMany(string[] values) => - _values = values; - - public Span AsSpan() => - _values.AsSpan(); - - public bool Equals(StringSequenceMany other) => - _values.AsSpan().SequenceEqual(other._values.AsSpan()); - - //GetHashCode() is in the platform specific files - public override bool Equals(object? obj) => - obj is StringSequenceMany ssm && Equals(ssm); - } -} diff --git a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/StringSequence.netcore.cs b/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/StringSequence.netcore.cs deleted file mode 100644 index 67e12357c0..0000000000 --- a/src/Management/src/Endpoint/Actuators/Metrics/SystemDiagnosticsMetrics/StringSequence.netcore.cs +++ /dev/null @@ -1,58 +0,0 @@ -#pragma warning disable -// Steeltoe: Copy of version in System.Diagnostics.Metrics (see README.md for details). - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Numerics; -using System.Runtime.InteropServices; - -namespace Steeltoe.Management.Endpoint.Actuators.Metrics.SystemDiagnosticsMetrics -{ - internal interface IStringSequence - { - Span AsSpan(); - } - - internal partial struct StringSequence1 : IEquatable, IStringSequence - { - - public Span AsSpan() - { - return MemoryMarshal.CreateSpan(ref Value1, 1); - } - } - - internal partial struct StringSequence2 : IEquatable, IStringSequence - { - public Span AsSpan() - { - return MemoryMarshal.CreateSpan(ref Value1, 2); - } - - public override int GetHashCode() => HashCode.Combine(Value1, Value2); - } - - internal partial struct StringSequence3 : IEquatable, IStringSequence - { - public Span AsSpan() - { - return MemoryMarshal.CreateSpan(ref Value1, 3); - } - - public override int GetHashCode() => HashCode.Combine(Value1, Value2, Value3); - } - - internal partial struct StringSequenceMany : IEquatable, IStringSequence - { - public override int GetHashCode() - { - HashCode h = default; - for (int i = 0; i < _values.Length; i++) - { - h.Add(_values[i]); - } - return h.ToHashCode(); - } - } -} diff --git a/src/Management/src/Endpoint/ConfigurationSchema.json b/src/Management/src/Endpoint/ConfigurationSchema.json index 1cfc0f3553..7d6c0f5732 100644 --- a/src/Management/src/Endpoint/ConfigurationSchema.json +++ b/src/Management/src/Endpoint/ConfigurationSchema.json @@ -545,57 +545,6 @@ } } }, - "Metrics": { - "type": "object", - "properties": { - "AllowedVerbs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Gets the list of HTTP verbs that are allowed for this endpoint." - }, - "CacheDurationMilliseconds": { - "type": "integer", - "description": "Gets or sets the duration in milliseconds that metrics are cached for. Default value: 500." - }, - "Enabled": { - "type": "boolean", - "description": "Gets or sets a value indicating whether this endpoint is enabled." - }, - "Id": { - "type": "string", - "description": "Gets or sets the unique ID of this endpoint." - }, - "IncludedMetrics": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Gets the names of metrics to include." - }, - "MaxHistograms": { - "type": "integer", - "description": "Gets or sets the maximum number of histograms to return. Default value: 100." - }, - "MaxTimeSeries": { - "type": "integer", - "description": "Gets or sets the maximum number of time series to return. Default value: 100." - }, - "Path": { - "type": "string", - "description": "Gets or sets the relative path at which this endpoint is exposed." - }, - "RequiredPermissions": { - "enum": [ - "None", - "Restricted", - "Full" - ], - "description": "Gets or sets the permissions required to access this endpoint, when running on Cloud Foundry. Default value: Restricted." - } - } - }, "Path": { "type": "string", "description": "Gets or sets the HTTP request path at which management endpoints are exposed. Default value: /actuator." diff --git a/src/Management/src/Endpoint/HostBuilderWrapperExtensions.cs b/src/Management/src/Endpoint/HostBuilderWrapperExtensions.cs index 550465f663..db97c81aad 100644 --- a/src/Management/src/Endpoint/HostBuilderWrapperExtensions.cs +++ b/src/Management/src/Endpoint/HostBuilderWrapperExtensions.cs @@ -15,7 +15,6 @@ using Steeltoe.Management.Endpoint.Actuators.Hypermedia; using Steeltoe.Management.Endpoint.Actuators.Info; using Steeltoe.Management.Endpoint.Actuators.Loggers; -using Steeltoe.Management.Endpoint.Actuators.Metrics; using Steeltoe.Management.Endpoint.Actuators.Refresh; using Steeltoe.Management.Endpoint.Actuators.RouteMappings; using Steeltoe.Management.Endpoint.Actuators.Services; @@ -82,12 +81,6 @@ public static void AddMappingsActuator(this HostBuilderWrapper wrapper) RegisterActuatorEndpoints(wrapper, null); } - public static void AddMetricsActuator(this HostBuilderWrapper wrapper) - { - wrapper.ConfigureServices(services => services.AddMetricsActuator()); - RegisterActuatorEndpoints(wrapper, null); - } - public static void AddRefreshActuator(this HostBuilderWrapper wrapper) { wrapper.ConfigureServices(services => services.AddRefreshActuator()); diff --git a/src/Management/src/Endpoint/ManagementHostApplicationBuilderExtensions.cs b/src/Management/src/Endpoint/ManagementHostApplicationBuilderExtensions.cs index b3833554cb..fa47483130 100644 --- a/src/Management/src/Endpoint/ManagementHostApplicationBuilderExtensions.cs +++ b/src/Management/src/Endpoint/ManagementHostApplicationBuilderExtensions.cs @@ -163,25 +163,6 @@ public static IHostApplicationBuilder AddMappingsActuator(this IHostApplicationB return builder; } - /// - /// Adds the Metrics actuator to the application. - /// - /// - /// The to configure. - /// - /// - /// The incoming so that additional calls can be chained. - /// - public static IHostApplicationBuilder AddMetricsActuator(this IHostApplicationBuilder builder) - { - ArgumentNullException.ThrowIfNull(builder); - - HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); - wrapper.AddMetricsActuator(); - - return builder; - } - /// /// Adds the Refresh actuator to the application. /// diff --git a/src/Management/src/Endpoint/ManagementHostBuilderExtensions.cs b/src/Management/src/Endpoint/ManagementHostBuilderExtensions.cs index aa4a44bee2..432843ae11 100644 --- a/src/Management/src/Endpoint/ManagementHostBuilderExtensions.cs +++ b/src/Management/src/Endpoint/ManagementHostBuilderExtensions.cs @@ -163,25 +163,6 @@ public static IHostBuilder AddMappingsActuator(this IHostBuilder builder) return builder; } - /// - /// Adds the Metrics actuator to the application. - /// - /// - /// The to configure. - /// - /// - /// The incoming so that additional calls can be chained. - /// - public static IHostBuilder AddMetricsActuator(this IHostBuilder builder) - { - ArgumentNullException.ThrowIfNull(builder); - - HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); - wrapper.AddMetricsActuator(); - - return builder; - } - /// /// Adds the Refresh actuator to the application. /// diff --git a/src/Management/src/Endpoint/ManagementWebHostBuilderExtensions.cs b/src/Management/src/Endpoint/ManagementWebHostBuilderExtensions.cs index b4d831c073..49d309fb4d 100755 --- a/src/Management/src/Endpoint/ManagementWebHostBuilderExtensions.cs +++ b/src/Management/src/Endpoint/ManagementWebHostBuilderExtensions.cs @@ -163,25 +163,6 @@ public static IWebHostBuilder AddMappingsActuator(this IWebHostBuilder builder) return builder; } - /// - /// Adds the Metrics actuator to the application. - /// - /// - /// The to configure. - /// - /// - /// The incoming so that additional calls can be chained. - /// - public static IWebHostBuilder AddMetricsActuator(this IWebHostBuilder builder) - { - ArgumentNullException.ThrowIfNull(builder); - - HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); - wrapper.AddMetricsActuator(); - - return builder; - } - /// /// Adds the Refresh actuator to the application. /// diff --git a/src/Management/src/Endpoint/Properties/AssemblyInfo.cs b/src/Management/src/Endpoint/Properties/AssemblyInfo.cs index ef5e382a31..6173877ae2 100644 --- a/src/Management/src/Endpoint/Properties/AssemblyInfo.cs +++ b/src/Management/src/Endpoint/Properties/AssemblyInfo.cs @@ -16,7 +16,6 @@ using Steeltoe.Management.Endpoint.Actuators.Hypermedia; using Steeltoe.Management.Endpoint.Actuators.Info; using Steeltoe.Management.Endpoint.Actuators.Loggers; -using Steeltoe.Management.Endpoint.Actuators.Metrics; using Steeltoe.Management.Endpoint.Actuators.Refresh; using Steeltoe.Management.Endpoint.Actuators.RouteMappings; using Steeltoe.Management.Endpoint.Actuators.Services; @@ -36,7 +35,6 @@ [assembly: ConfigurationSchema("Management:Endpoints:HeapDump", typeof(HeapDumpEndpointOptions))] [assembly: ConfigurationSchema("Management:Endpoints:Info", typeof(InfoEndpointOptions))] [assembly: ConfigurationSchema("Management:Endpoints:Loggers", typeof(LoggersEndpointOptions))] -[assembly: ConfigurationSchema("Management:Endpoints:Metrics", typeof(MetricsEndpointOptions))] [assembly: ConfigurationSchema("Management:Endpoints:Refresh", typeof(RefreshEndpointOptions))] [assembly: ConfigurationSchema("Management:Endpoints:Mappings", typeof(RouteMappingsEndpointOptions))] [assembly: ConfigurationSchema("Management:Endpoints:Services", typeof(ServicesEndpointOptions))] diff --git a/src/Management/src/Endpoint/PublicAPI.Unshipped.txt b/src/Management/src/Endpoint/PublicAPI.Unshipped.txt index 53a5835571..8a39ff1e00 100755 --- a/src/Management/src/Endpoint/PublicAPI.Unshipped.txt +++ b/src/Management/src/Endpoint/PublicAPI.Unshipped.txt @@ -12,8 +12,6 @@ override Steeltoe.Management.Endpoint.Actuators.Info.EpochSecondsDateTimeConvert override Steeltoe.Management.Endpoint.Actuators.Info.EpochSecondsDateTimeConverter.Write(System.Text.Json.Utf8JsonWriter! writer, System.DateTime value, System.Text.Json.JsonSerializerOptions! options) -> void override Steeltoe.Management.Endpoint.Actuators.Loggers.LoggersEndpointOptions.RequiresExactMatch() -> bool override Steeltoe.Management.Endpoint.Actuators.Loggers.LoggersRequest.ToString() -> string! -override Steeltoe.Management.Endpoint.Actuators.Metrics.MetricSample.ToString() -> string! -override Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsEndpointOptions.RequiresExactMatch() -> bool override Steeltoe.Management.Endpoint.Actuators.Services.ServiceRegistration.ToString() -> string! static readonly Steeltoe.Management.Endpoint.Actuators.Health.Availability.LivenessState.Broken -> Steeltoe.Management.Endpoint.Actuators.Health.Availability.LivenessState! static readonly Steeltoe.Management.Endpoint.Actuators.Health.Availability.LivenessState.Correct -> Steeltoe.Management.Endpoint.Actuators.Health.Availability.LivenessState! @@ -38,8 +36,6 @@ static Steeltoe.Management.Endpoint.Actuators.Info.EndpointServiceCollectionExte static Steeltoe.Management.Endpoint.Actuators.Info.EndpointServiceCollectionExtensions.AddInfoContributor(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Type! infoContributorType) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! static Steeltoe.Management.Endpoint.Actuators.Info.EndpointServiceCollectionExtensions.AddInfoContributor(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! static Steeltoe.Management.Endpoint.Actuators.Loggers.EndpointServiceCollectionExtensions.AddLoggersActuator(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -static Steeltoe.Management.Endpoint.Actuators.Metrics.EndpointServiceCollectionExtensions.AddMetricsActuator(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -static Steeltoe.Management.Endpoint.Actuators.Metrics.EndpointServiceCollectionExtensions.AddMetricsObservers(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! static Steeltoe.Management.Endpoint.Actuators.Refresh.EndpointServiceCollectionExtensions.AddRefreshActuator(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! static Steeltoe.Management.Endpoint.Actuators.RouteMappings.EndpointServiceCollectionExtensions.AddMappingsActuator(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! static Steeltoe.Management.Endpoint.Actuators.RouteMappings.RouteBuilderExtensions.AddRoutesToMappingsActuator(this Microsoft.AspNetCore.Routing.IRouteBuilder! builder) -> Microsoft.AspNetCore.Routing.IRouteBuilder! @@ -65,7 +61,6 @@ static Steeltoe.Management.Endpoint.ManagementHostApplicationBuilderExtensions.A static Steeltoe.Management.Endpoint.ManagementHostApplicationBuilderExtensions.AddInfoActuator(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder) -> Microsoft.Extensions.Hosting.IHostApplicationBuilder! static Steeltoe.Management.Endpoint.ManagementHostApplicationBuilderExtensions.AddLoggersActuator(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder) -> Microsoft.Extensions.Hosting.IHostApplicationBuilder! static Steeltoe.Management.Endpoint.ManagementHostApplicationBuilderExtensions.AddMappingsActuator(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder) -> Microsoft.Extensions.Hosting.IHostApplicationBuilder! -static Steeltoe.Management.Endpoint.ManagementHostApplicationBuilderExtensions.AddMetricsActuator(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder) -> Microsoft.Extensions.Hosting.IHostApplicationBuilder! static Steeltoe.Management.Endpoint.ManagementHostApplicationBuilderExtensions.AddRefreshActuator(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder) -> Microsoft.Extensions.Hosting.IHostApplicationBuilder! static Steeltoe.Management.Endpoint.ManagementHostApplicationBuilderExtensions.AddServicesActuator(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder) -> Microsoft.Extensions.Hosting.IHostApplicationBuilder! static Steeltoe.Management.Endpoint.ManagementHostApplicationBuilderExtensions.AddThreadDumpActuator(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder) -> Microsoft.Extensions.Hosting.IHostApplicationBuilder! @@ -83,7 +78,6 @@ static Steeltoe.Management.Endpoint.ManagementHostBuilderExtensions.AddHypermedi static Steeltoe.Management.Endpoint.ManagementHostBuilderExtensions.AddInfoActuator(this Microsoft.Extensions.Hosting.IHostBuilder! builder) -> Microsoft.Extensions.Hosting.IHostBuilder! static Steeltoe.Management.Endpoint.ManagementHostBuilderExtensions.AddLoggersActuator(this Microsoft.Extensions.Hosting.IHostBuilder! builder) -> Microsoft.Extensions.Hosting.IHostBuilder! static Steeltoe.Management.Endpoint.ManagementHostBuilderExtensions.AddMappingsActuator(this Microsoft.Extensions.Hosting.IHostBuilder! builder) -> Microsoft.Extensions.Hosting.IHostBuilder! -static Steeltoe.Management.Endpoint.ManagementHostBuilderExtensions.AddMetricsActuator(this Microsoft.Extensions.Hosting.IHostBuilder! builder) -> Microsoft.Extensions.Hosting.IHostBuilder! static Steeltoe.Management.Endpoint.ManagementHostBuilderExtensions.AddRefreshActuator(this Microsoft.Extensions.Hosting.IHostBuilder! builder) -> Microsoft.Extensions.Hosting.IHostBuilder! static Steeltoe.Management.Endpoint.ManagementHostBuilderExtensions.AddServicesActuator(this Microsoft.Extensions.Hosting.IHostBuilder! builder) -> Microsoft.Extensions.Hosting.IHostBuilder! static Steeltoe.Management.Endpoint.ManagementHostBuilderExtensions.AddThreadDumpActuator(this Microsoft.Extensions.Hosting.IHostBuilder! builder) -> Microsoft.Extensions.Hosting.IHostBuilder! @@ -101,7 +95,6 @@ static Steeltoe.Management.Endpoint.ManagementWebHostBuilderExtensions.AddHyperm static Steeltoe.Management.Endpoint.ManagementWebHostBuilderExtensions.AddInfoActuator(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder! static Steeltoe.Management.Endpoint.ManagementWebHostBuilderExtensions.AddLoggersActuator(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder! static Steeltoe.Management.Endpoint.ManagementWebHostBuilderExtensions.AddMappingsActuator(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder! -static Steeltoe.Management.Endpoint.ManagementWebHostBuilderExtensions.AddMetricsActuator(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder! static Steeltoe.Management.Endpoint.ManagementWebHostBuilderExtensions.AddRefreshActuator(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder! static Steeltoe.Management.Endpoint.ManagementWebHostBuilderExtensions.AddServicesActuator(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder! static Steeltoe.Management.Endpoint.ManagementWebHostBuilderExtensions.AddThreadDumpActuator(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder! @@ -241,6 +234,9 @@ Steeltoe.Management.Endpoint.Actuators.HttpExchanges.HttpExchange.SerializedTime Steeltoe.Management.Endpoint.Actuators.HttpExchanges.HttpExchange.Session.get -> Steeltoe.Management.Endpoint.Actuators.HttpExchanges.HttpExchangeSession? Steeltoe.Management.Endpoint.Actuators.HttpExchanges.HttpExchange.Timestamp.get -> System.DateTime Steeltoe.Management.Endpoint.Actuators.HttpExchanges.HttpExchange.TimeTaken.get -> System.TimeSpan +Steeltoe.Management.Endpoint.Actuators.HttpExchanges.HttpExchangePrincipal +Steeltoe.Management.Endpoint.Actuators.HttpExchanges.HttpExchangePrincipal.HttpExchangePrincipal(string! name) -> void +Steeltoe.Management.Endpoint.Actuators.HttpExchanges.HttpExchangePrincipal.Name.get -> string! Steeltoe.Management.Endpoint.Actuators.HttpExchanges.HttpExchangeRequest Steeltoe.Management.Endpoint.Actuators.HttpExchanges.HttpExchangeRequest.Headers.get -> System.Collections.Generic.IDictionary! Steeltoe.Management.Endpoint.Actuators.HttpExchanges.HttpExchangeRequest.HttpExchangeRequest(string! method, System.Uri! uri, System.Collections.Generic.IDictionary! headers, string? remoteAddress) -> void @@ -284,9 +280,6 @@ Steeltoe.Management.Endpoint.Actuators.HttpExchanges.HttpExchangesResult.HttpExc Steeltoe.Management.Endpoint.Actuators.HttpExchanges.IHttpExchangesEndpointHandler Steeltoe.Management.Endpoint.Actuators.HttpExchanges.IHttpExchangesRepository Steeltoe.Management.Endpoint.Actuators.HttpExchanges.IHttpExchangesRepository.GetHttpExchanges() -> Steeltoe.Management.Endpoint.Actuators.HttpExchanges.HttpExchangesResult! -Steeltoe.Management.Endpoint.Actuators.HttpExchanges.HttpExchangePrincipal -Steeltoe.Management.Endpoint.Actuators.HttpExchanges.HttpExchangePrincipal.Name.get -> string! -Steeltoe.Management.Endpoint.Actuators.HttpExchanges.HttpExchangePrincipal.HttpExchangePrincipal(string! name) -> void Steeltoe.Management.Endpoint.Actuators.Hypermedia.EndpointServiceCollectionExtensions Steeltoe.Management.Endpoint.Actuators.Hypermedia.HypermediaEndpointOptions Steeltoe.Management.Endpoint.Actuators.Hypermedia.HypermediaEndpointOptions.HypermediaEndpointOptions() -> void @@ -335,44 +328,6 @@ Steeltoe.Management.Endpoint.Actuators.Loggers.LoggersResponse Steeltoe.Management.Endpoint.Actuators.Loggers.LoggersResponse.Data.get -> System.Collections.Generic.IDictionary! Steeltoe.Management.Endpoint.Actuators.Loggers.LoggersResponse.HasError.get -> bool Steeltoe.Management.Endpoint.Actuators.Loggers.LoggersResponse.LoggersResponse(System.Collections.Generic.IDictionary! data, bool hasError) -> void -Steeltoe.Management.Endpoint.Actuators.Metrics.EndpointServiceCollectionExtensions -Steeltoe.Management.Endpoint.Actuators.Metrics.IMetricsEndpointHandler -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricSample -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricSample.MetricSample(Steeltoe.Management.Endpoint.Actuators.Metrics.MetricStatistic statistic, double value, System.Collections.Generic.IList>? tags) -> void -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricSample.Statistic.get -> Steeltoe.Management.Endpoint.Actuators.Metrics.MetricStatistic -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricSample.Tags.get -> System.Collections.Generic.IList>? -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricSample.Value.get -> double -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsEndpointOptions -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsEndpointOptions.CacheDurationMilliseconds.get -> int -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsEndpointOptions.CacheDurationMilliseconds.set -> void -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsEndpointOptions.IncludedMetrics.get -> System.Collections.Generic.IList! -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsEndpointOptions.MaxHistograms.get -> int -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsEndpointOptions.MaxHistograms.set -> void -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsEndpointOptions.MaxTimeSeries.get -> int -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsEndpointOptions.MaxTimeSeries.set -> void -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsEndpointOptions.MetricsEndpointOptions() -> void -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsRequest -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsRequest.MetricName.get -> string! -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsRequest.MetricsRequest(string! metricName, System.Collections.Generic.IList>! tags) -> void -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsRequest.Tags.get -> System.Collections.Generic.IList>! -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsResponse -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsResponse.AvailableTags.get -> System.Collections.Generic.IList? -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsResponse.Measurements.get -> System.Collections.Generic.IList? -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsResponse.MetricsResponse(string! name, System.Collections.Generic.IList! measurements, System.Collections.Generic.IList! availableTags) -> void -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsResponse.MetricsResponse(System.Collections.Generic.ISet! names) -> void -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsResponse.Name.get -> string? -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricsResponse.Names.get -> System.Collections.Generic.ISet? -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricStatistic -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricStatistic.Count = 2 -> Steeltoe.Management.Endpoint.Actuators.Metrics.MetricStatistic -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricStatistic.Max = 3 -> Steeltoe.Management.Endpoint.Actuators.Metrics.MetricStatistic -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricStatistic.Rate = 5 -> Steeltoe.Management.Endpoint.Actuators.Metrics.MetricStatistic -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricStatistic.Total = 0 -> Steeltoe.Management.Endpoint.Actuators.Metrics.MetricStatistic -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricStatistic.TotalTime = 1 -> Steeltoe.Management.Endpoint.Actuators.Metrics.MetricStatistic -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricStatistic.Value = 4 -> Steeltoe.Management.Endpoint.Actuators.Metrics.MetricStatistic -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricTag -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricTag.MetricTag(string! tag, System.Collections.Generic.ISet! values) -> void -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricTag.Tag.get -> string! -Steeltoe.Management.Endpoint.Actuators.Metrics.MetricTag.Values.get -> System.Collections.Generic.ISet! Steeltoe.Management.Endpoint.Actuators.Refresh.EndpointServiceCollectionExtensions Steeltoe.Management.Endpoint.Actuators.Refresh.IRefreshEndpointHandler Steeltoe.Management.Endpoint.Actuators.Refresh.RefreshEndpointOptions diff --git a/src/Management/src/Prometheus/PrometheusExtensions.cs b/src/Management/src/Prometheus/PrometheusExtensions.cs index d68e13c2d5..c3f80db820 100644 --- a/src/Management/src/Prometheus/PrometheusExtensions.cs +++ b/src/Management/src/Prometheus/PrometheusExtensions.cs @@ -8,7 +8,6 @@ using OpenTelemetry.Metrics; using Steeltoe.Management.Configuration; using Steeltoe.Management.Endpoint; -using Steeltoe.Management.Endpoint.Actuators.Metrics; using Steeltoe.Management.Endpoint.Configuration; namespace Steeltoe.Management.Prometheus; @@ -32,7 +31,6 @@ public static IServiceCollection AddPrometheusActuator(this IServiceCollection s services.AddOpenTelemetry().WithMetrics(builder => { - builder.AddMeter(SteeltoeMetrics.InstrumentationName); builder.AddPrometheusExporter(); }); diff --git a/src/Management/src/Wavefront/WavefrontExtensions.cs b/src/Management/src/Wavefront/WavefrontExtensions.cs index f1c9d190d7..114adf1546 100644 --- a/src/Management/src/Wavefront/WavefrontExtensions.cs +++ b/src/Management/src/Wavefront/WavefrontExtensions.cs @@ -9,7 +9,6 @@ using OpenTelemetry.Metrics; using Steeltoe.Management.Diagnostics; using Steeltoe.Management.Endpoint; -using Steeltoe.Management.Endpoint.Actuators.Metrics; using Steeltoe.Management.Wavefront.Exporters; namespace Steeltoe.Management.Wavefront; @@ -33,11 +32,9 @@ public static IServiceCollection AddWavefrontMetrics(this IServiceCollection ser services.AddHostedService(); services.ConfigureOptionsWithChangeTokenSource(); - services.AddMetricsObservers(); services.AddOpenTelemetry().WithMetrics(builder => { - builder.AddMeter(SteeltoeMetrics.InstrumentationName); builder.AddWavefrontExporter(); }); diff --git a/src/Management/test/Endpoint.Test/Actuators/Metrics/AspNetCoreHostingObserverTest.cs b/src/Management/test/Endpoint.Test/Actuators/Metrics/AspNetCoreHostingObserverTest.cs deleted file mode 100644 index beeccc3a7e..0000000000 --- a/src/Management/test/Endpoint.Test/Actuators/Metrics/AspNetCoreHostingObserverTest.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Diagnostics; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using Steeltoe.Management.Diagnostics; -using Steeltoe.Management.Endpoint.Actuators.Metrics; -using Steeltoe.Management.Endpoint.Actuators.Metrics.Observers; - -namespace Steeltoe.Management.Endpoint.Test.Actuators.Metrics; - -public sealed class AspNetCoreHostingObserverTest : BaseTest -{ - [Fact] - public void ShouldIgnore_ReturnsExpected() - { - IOptionsMonitor options = GetOptionsMonitorFromSettings(); - var observer = new AspNetCoreHostingObserver(options, NullLoggerFactory.Instance); - - Assert.True(observer.ShouldIgnoreRequest("/cloudfoundryapplication/info")); - Assert.True(observer.ShouldIgnoreRequest("/cloudfoundryapplication/health")); - Assert.True(observer.ShouldIgnoreRequest("/foo/bar/image.png")); - Assert.True(observer.ShouldIgnoreRequest("/foo/bar/image.gif")); - Assert.True(observer.ShouldIgnoreRequest("/favicon.ico")); - Assert.True(observer.ShouldIgnoreRequest("/foo.js")); - Assert.True(observer.ShouldIgnoreRequest("/foo.css")); - Assert.True(observer.ShouldIgnoreRequest("/javascript/foo.js")); - Assert.True(observer.ShouldIgnoreRequest("/css/foo.css")); - Assert.True(observer.ShouldIgnoreRequest("/foo.html")); - Assert.True(observer.ShouldIgnoreRequest("/html/foo.html")); - Assert.False(observer.ShouldIgnoreRequest("/api/test")); - Assert.False(observer.ShouldIgnoreRequest("/v2/apps")); - } - - [Fact] - public void GetException_ReturnsExpected() - { - HttpContext context = GetHttpRequestMessage(); - string exceptionTypeName = AspNetCoreHostingObserver.GetExceptionTypeName(context); - Assert.Equal("None", exceptionTypeName); - - var exceptionHandlerFeature = new ExceptionHandlerFeature(new InvalidOperationException()); - - context.Features.Set(exceptionHandlerFeature); - exceptionTypeName = AspNetCoreHostingObserver.GetExceptionTypeName(context); - Assert.Equal("InvalidOperationException", exceptionTypeName); - } - - [Fact] - public void GetLabelSets_ReturnsExpected() - { - IOptionsMonitor options = GetOptionsMonitorFromSettings(); - var observer = new AspNetCoreHostingObserver(options, NullLoggerFactory.Instance); - - HttpContext context = GetHttpRequestMessage(); - - var exceptionHandlerFeature = new ExceptionHandlerFeature(new InvalidOperationException()); - - context.Features.Set(exceptionHandlerFeature); - context.Response.StatusCode = 404; - - List> tagContext = [.. observer.GetLabelSets(context)]; - - Assert.Contains(KeyValuePair.Create("exception", (object?)"InvalidOperationException"), tagContext); - Assert.Contains(KeyValuePair.Create("uri", (object?)"/foobar"), tagContext); - Assert.Contains(KeyValuePair.Create("status", (object?)"404"), tagContext); - Assert.Contains(KeyValuePair.Create("method", (object?)"GET"), tagContext); - } - - private static HttpContext GetHttpRequestMessage(string method = "GET", string path = "/foobar") - { - HttpContext context = new DefaultHttpContext - { - TraceIdentifier = Guid.NewGuid().ToString() - }; - - context.Request.Body = new MemoryStream(); - context.Response.Body = new MemoryStream(); - - context.Request.Method = method; - context.Request.Path = path; - context.Request.Scheme = "http"; - - context.Request.Host = new HostString("localhost", 5555); - return context; - } - - private sealed class ExceptionHandlerFeature(Exception error) : IExceptionHandlerFeature - { - public Exception Error { get; } = error; - } -} diff --git a/src/Management/test/Endpoint.Test/Actuators/Metrics/EndpointServiceCollectionExtensionsTest.cs b/src/Management/test/Endpoint.Test/Actuators/Metrics/EndpointServiceCollectionExtensionsTest.cs deleted file mode 100644 index 913a2b433f..0000000000 --- a/src/Management/test/Endpoint.Test/Actuators/Metrics/EndpointServiceCollectionExtensionsTest.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Options; -using Steeltoe.Management.Diagnostics; -using Steeltoe.Management.Endpoint.Actuators.Metrics; - -namespace Steeltoe.Management.Endpoint.Test.Actuators.Metrics; - -public sealed class EndpointServiceCollectionExtensionsTest : BaseTest -{ - [Fact] - public async Task AddMetricsActuator_AddsCorrectServices() - { - var builder = new ConfigurationBuilder(); - IConfiguration configuration = builder.Build(); - - var services = new ServiceCollection(); - services.AddOptions(); - services.AddLogging(); - services.AddSingleton(configuration); - services.AddMetricsActuator(); - - await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); - - serviceProvider.GetService().Should().NotBeNull(); - serviceProvider.GetServices().OfType().Should().HaveCount(1); - - var optionsMonitor = serviceProvider.GetRequiredService>(); - optionsMonitor.CurrentValue.EgressIgnorePattern.Should().NotBeNullOrEmpty(); - - IDiagnosticObserver[] observers = serviceProvider.GetServices().ToArray(); - observers.Should().NotBeEmpty(); - - serviceProvider.GetService().Should().NotBeNull(); - } -} diff --git a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricSampleTest.cs b/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricSampleTest.cs deleted file mode 100644 index 8680ac2104..0000000000 --- a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricSampleTest.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Common.TestResources; -using Steeltoe.Management.Endpoint.Actuators.Metrics; - -namespace Steeltoe.Management.Endpoint.Test.Actuators.Metrics; - -public sealed class MetricSampleTest : BaseTest -{ - [Fact] - public void Constructor_SetsValues() - { - var sample = new MetricSample(MetricStatistic.Total, 100.00, null); - Assert.Equal(MetricStatistic.Total, sample.Statistic); - Assert.Equal(100.00, sample.Value); - } - - [Fact] - public void JsonSerialization_ReturnsExpected() - { - var sample = new MetricSample(MetricStatistic.Total, 100.00, null); - string result = Serialize(sample); - - result.Should().BeJson(""" - { - "statistic": "TOTAL", - "value": 100 - } - """); - } -} diff --git a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricTagTest.cs b/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricTagTest.cs deleted file mode 100644 index ea783d6239..0000000000 --- a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricTagTest.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Common.TestResources; -using Steeltoe.Management.Endpoint.Actuators.Metrics; - -namespace Steeltoe.Management.Endpoint.Test.Actuators.Metrics; - -public sealed class MetricTagTest : BaseTest -{ - [Fact] - public void Constructor_SetsValues() - { - var tags = new HashSet - { - "tagValue" - }; - - var metricTag = new MetricTag("tagName", tags); - Assert.Equal("tagName", metricTag.Tag); - Assert.Same(tags, metricTag.Values); - } - - [Fact] - public void JsonSerialization_ReturnsExpected() - { - var tags = new HashSet - { - "tagValue" - }; - - var metricTag = new MetricTag("tagName", tags); - string result = Serialize(metricTag); - - result.Should().BeJson(""" - { - "tag": "tagName", - "values": [ - "tagValue" - ] - } - """); - } -} diff --git a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsEndpointMiddlewareTest.cs b/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsEndpointMiddlewareTest.cs deleted file mode 100644 index 3d699e86ca..0000000000 --- a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsEndpointMiddlewareTest.cs +++ /dev/null @@ -1,275 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics.Metrics; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using Steeltoe.Common.TestResources; -using Steeltoe.Management.Endpoint.Actuators.Metrics; -using Steeltoe.Management.Endpoint.Actuators.Metrics.SystemDiagnosticsMetrics; -using Steeltoe.Management.Endpoint.Configuration; - -namespace Steeltoe.Management.Endpoint.Test.Actuators.Metrics; - -public sealed class MetricsEndpointMiddlewareTest : BaseTest -{ - private readonly MetricsExporterOptions _scraperOptions = new() - { - CacheDurationMilliseconds = 500 - }; - - [Fact] - public void ParseTag_ReturnsExpected() - { - IOptionsMonitor endpointOptionsMonitor = GetOptionsMonitorFromSettings(); - IOptionsMonitor managementOptionsMonitor = GetOptionsMonitorFromSettings(); - - var handler = new MetricsEndpointHandler(endpointOptionsMonitor, new MetricsExporter(_scraperOptions), NullLoggerFactory.Instance); - - var middleware = new MetricsEndpointMiddleware(handler, managementOptionsMonitor, NullLoggerFactory.Instance); - - Assert.Null(middleware.ParseTag("foobar")); - Assert.Equal(new KeyValuePair("foo", "bar"), middleware.ParseTag("foo:bar")); - Assert.Equal(new KeyValuePair("foo", "bar:bar"), middleware.ParseTag("foo:bar:bar")); - Assert.Null(middleware.ParseTag("foo,bar")); - } - - [Fact] - public void ParseTags_ReturnsExpected() - { - IOptionsMonitor endpointOptionsMonitor = GetOptionsMonitorFromSettings(); - IOptionsMonitor managementOptionsMonitor = GetOptionsMonitorFromSettings(); - - var handler = new MetricsEndpointHandler(endpointOptionsMonitor, new MetricsExporter(_scraperOptions), NullLoggerFactory.Instance); - - var middleware = new MetricsEndpointMiddleware(handler, managementOptionsMonitor, NullLoggerFactory.Instance); - - HttpContext context1 = CreateRequest("GET", "/cloudfoundryapplication/metrics/Foo.Bar.Class", "?foo=key:value"); - IList> result = middleware.ParseTags(context1.Request.Query); - Assert.NotNull(result); - Assert.Empty(result); - - HttpContext context2 = CreateRequest("GET", "/cloudfoundryapplication/metrics/Foo.Bar.Class", "?tag=key:value"); - result = middleware.ParseTags(context2.Request.Query); - Assert.NotNull(result); - Assert.Contains(new KeyValuePair("key", "value"), result); - - HttpContext context3 = CreateRequest("GET", "/cloudfoundryapplication/metrics/Foo.Bar.Class", "?tag=key:value&foo=key:value&tag=key1:value1"); - result = middleware.ParseTags(context3.Request.Query); - Assert.NotNull(result); - Assert.Contains(new KeyValuePair("key", "value"), result); - Assert.Contains(new KeyValuePair("key1", "value1"), result); - Assert.Equal(2, result.Count); - - HttpContext context4 = CreateRequest("GET", "/cloudfoundryapplication/metrics/Foo.Bar.Class", "?tag=key:value&foo=key:value&tag=key:value"); - result = middleware.ParseTags(context4.Request.Query); - Assert.NotNull(result); - Assert.Contains(new KeyValuePair("key", "value"), result); - Assert.Single(result); - } - - [Fact] - public void GetMetricName_ReturnsExpected() - { - IOptionsMonitor endpointOptionsMonitor = GetOptionsMonitorFromSettings(); - IOptionsMonitor managementOptionsMonitor = GetOptionsMonitorFromSettings(); - - var handler = new MetricsEndpointHandler(endpointOptionsMonitor, new MetricsExporter(_scraperOptions), NullLoggerFactory.Instance); - - var middleware = new MetricsEndpointMiddleware(handler, managementOptionsMonitor, NullLoggerFactory.Instance); - - HttpContext context1 = CreateRequest("GET", "/cloudfoundryapplication/metrics", null); - Assert.Empty(middleware.GetMetricName(context1.Request)); - - HttpContext context2 = CreateRequest("GET", "/cloudfoundryapplication/metrics/Foo.Bar.Class", null); - Assert.Equal("Foo.Bar.Class", middleware.GetMetricName(context2.Request)); - - HttpContext context3 = CreateRequest("GET", "/cloudfoundryapplication/metrics", "?tag=key:value&tag=key1:value1"); - Assert.Empty(middleware.GetMetricName(context3.Request)); - } - - [Fact] - public void GetMetricName_ReturnsExpected_When_ManagementPath_Is_Slash() - { - IOptionsMonitor endpointOptionsMonitor = GetOptionsMonitorFromSettings(); - IOptionsMonitor managementOptionsMonitor = GetOptionsMonitorFromSettings(); - - var handler = new MetricsEndpointHandler(endpointOptionsMonitor, new MetricsExporter(_scraperOptions), NullLoggerFactory.Instance); - - var middleware = new MetricsEndpointMiddleware(handler, managementOptionsMonitor, NullLoggerFactory.Instance); - - HttpContext context1 = CreateRequest("GET", "/actuator/metrics", null); - Assert.Empty(middleware.GetMetricName(context1.Request)); - - HttpContext context2 = CreateRequest("GET", "/actuator/metrics/Foo.Bar.Class", null); - Assert.Equal("Foo.Bar.Class", middleware.GetMetricName(context2.Request)); - - HttpContext context3 = CreateRequest("GET", "/actuator/metrics", "?tag=key:value&tag=key1:value1"); - Assert.Empty(middleware.GetMetricName(context3.Request)); - } - - [Fact] - public async Task HandleMetricsRequestAsync_GetMetricsNames_ReturnsExpected() - { - var appSettings = new Dictionary - { - ["management:endpoints:actuator:exposure:include:0"] = "*" - }; - - IOptionsMonitor endpointOptionsMonitor = GetOptionsMonitorFromSettings(); - IOptionsMonitor managementOptions = GetOptionsMonitorFromSettings(appSettings); - - SteeltoeMetrics.InstrumentationName = Guid.NewGuid().ToString(); - var exporter = new MetricsExporter(_scraperOptions); - - using AggregationManager aggregationManager = GetTestMetrics(exporter); - - var handler = new MetricsEndpointHandler(endpointOptionsMonitor, exporter, NullLoggerFactory.Instance); - - var middleware = new MetricsEndpointMiddleware(handler, managementOptions, NullLoggerFactory.Instance); - - HttpContext context = CreateRequest("GET", "/cloudfoundryapplication/metrics", null); - - await middleware.InvokeAsync(context, null); - context.Response.Body.Seek(0, SeekOrigin.Begin); - var reader = new StreamReader(context.Response.Body); - string json = await reader.ReadToEndAsync(); - - json.Should().BeJson(""" - { - "names": [] - } - """); - } - - [Fact] - public async Task HandleMetricsRequestAsync_GetSpecificNonExistingMetric_ReturnsExpected() - { - IOptionsMonitor endpointOptionsMonitor = GetOptionsMonitorFromSettings(); - IOptionsMonitor managementOptionsMonitor = GetOptionsMonitorFromSettings(); - - var exporter = new MetricsExporter(_scraperOptions); - - var handler = new MetricsEndpointHandler(endpointOptionsMonitor, exporter, NullLoggerFactory.Instance); - - using AggregationManager aggregationManager = GetTestMetrics(exporter); - var middleware = new MetricsEndpointMiddleware(handler, managementOptionsMonitor, NullLoggerFactory.Instance); - - HttpContext context = CreateRequest("GET", "/cloudfoundryapplication/metrics/foo.bar", null); - - await middleware.InvokeAsync(context, null); - Assert.Equal(404, context.Response.StatusCode); - } - - [Fact] - public async Task HandleMetricsRequestAsync_GetSpecificExistingMetric_ReturnsExpected() - { - IOptionsMonitor endpointOptionsMonitor = GetOptionsMonitorFromSettings(); - IOptionsMonitor managementOptionsMonitor = GetOptionsMonitorFromSettings(); - - var exporter = new MetricsExporter(_scraperOptions); - using AggregationManager aggregationManager = GetTestMetrics(exporter); - aggregationManager.Start(); - var handler = new MetricsEndpointHandler(endpointOptionsMonitor, exporter, NullLoggerFactory.Instance); - - var middleware = new MetricsEndpointMiddleware(handler, managementOptionsMonitor, NullLoggerFactory.Instance); - - SetupTestView(); - - HttpContext context = CreateRequest("GET", "/cloudfoundryapplication/metrics/test", "?tag=a:v1"); - - await middleware.InvokeAsync(context, null); - Assert.Equal(200, context.Response.StatusCode); - - context.Response.Body.Seek(0, SeekOrigin.Begin); - var reader = new StreamReader(context.Response.Body); - string json = await reader.ReadToEndAsync(); - - json.Should().BeJson(""" - { - "name": "test", - "measurements": [ - { - "statistic": "Rate", - "value": 45 - } - ], - "availableTags": [ - { - "tag": "a", - "values": [ - "v1" - ] - }, - { - "tag": "b", - "values": [ - "v1" - ] - }, - { - "tag": "c", - "values": [ - "v1" - ] - } - ] - } - """); - } - - [Fact] - public void RoutesByPathAndVerb() - { - var endpointOptions = GetOptionsFromSettings(); - ManagementOptions managementOptions = GetOptionsMonitorFromSettings().CurrentValue; - - Assert.False(endpointOptions.RequiresExactMatch()); - Assert.Equal("/actuator/metrics/{**_}", endpointOptions.GetPathMatchPattern(managementOptions, managementOptions.Path)); - - Assert.Equal("/cloudfoundryapplication/metrics/{**_}", - endpointOptions.GetPathMatchPattern(managementOptions, ConfigureManagementOptions.DefaultCloudFoundryPath)); - - Assert.Contains("Get", endpointOptions.AllowedVerbs); - } - - private HttpContext CreateRequest(string method, string path, string? query) - { - HttpContext context = new DefaultHttpContext - { - TraceIdentifier = Guid.NewGuid().ToString() - }; - - context.Response.Body = new MemoryStream(); - context.Request.Method = method; - context.Request.Path = path; - context.Request.Scheme = "http"; - context.Request.Host = new HostString("localhost"); - - if (!string.IsNullOrEmpty(query)) - { - context.Request.QueryString = new QueryString(query); - } - - return context; - } - - private void SetupTestView() - { - Counter counter = SteeltoeMetrics.Meter.CreateCounter("test"); - - var labels = new Dictionary - { - { "a", "v1" }, - { "b", "v1" }, - { "c", "v1" } - }; - - for (int index = 0; index < 10; index++) - { - counter.Add(index, labels.ToArray()); - } - } -} diff --git a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsEndpointOptionsTest.cs b/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsEndpointOptionsTest.cs deleted file mode 100644 index baf8a9493f..0000000000 --- a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsEndpointOptionsTest.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Management.Endpoint.Actuators.Metrics; - -namespace Steeltoe.Management.Endpoint.Test.Actuators.Metrics; - -public sealed class MetricsEndpointOptionsTest : BaseTest -{ - [Fact] - public void Constructor_InitializesWithDefaults() - { - var options = GetOptionsFromSettings(); - Assert.Null(options.Enabled); - Assert.Equal("metrics", options.Id); - } - - [Fact] - public void Constructor_BindsConfigurationCorrectly() - { - var appSettings = new Dictionary - { - ["management:endpoints:enabled"] = "false", - ["management:endpoints:path"] = "/management", - ["management:endpoints:metrics:enabled"] = "false", - ["management:endpoints:metrics:id"] = "metrics-management" - }; - - MetricsEndpointOptions options = GetOptionsFromSettings(appSettings); - Assert.False(options.Enabled); - Assert.Equal("metrics-management", options.Id); - } -} diff --git a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsEndpointTest.cs b/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsEndpointTest.cs deleted file mode 100644 index eac57175fe..0000000000 --- a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsEndpointTest.cs +++ /dev/null @@ -1,563 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics.Metrics; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Steeltoe.Management.Endpoint.Actuators.Metrics; -using Xunit.Abstractions; - -namespace Steeltoe.Management.Endpoint.Test.Actuators.Metrics; - -public sealed class MetricsEndpointTest : BaseTest -{ - private readonly ITestOutputHelper _testOutputHelper; - - public MetricsEndpointTest(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - SteeltoeMetrics.InstrumentationName = Guid.NewGuid().ToString(); - } - - [Fact] - public async Task Invoke_WithNullMetricsRequest_ReturnsExpected() - { - using (var testContext = new TestContext(_testOutputHelper)) - { - testContext.AdditionalServices = (services, _) => - { - services.AddMetricsActuator(); - }; - - MetricCollectionHostedService service = testContext.GetServices().OfType().Single(); - - await service.StartAsync(CancellationToken.None); - - try - { - var handler = testContext.GetRequiredService(); - Counter requests = SteeltoeMetrics.Meter.CreateCounter("http.server.requests"); - requests.Add(1); - Counter memory = SteeltoeMetrics.Meter.CreateCounter("gc.memory.used"); - memory.Add(25); - - MetricsResponse? result = await handler.InvokeAsync(null, CancellationToken.None); - Assert.NotNull(result); - Assert.NotNull(result.Names); - Assert.NotEmpty(result.Names); - Assert.Contains("http.server.requests", result.Names); - Assert.Contains("gc.memory.used", result.Names); - - Assert.Equal(2, result.Names.Count); - } - finally - { - await service.StopAsync(CancellationToken.None); - } - } - - using (var testContext = new TestContext(_testOutputHelper)) - { - testContext.AdditionalServices = (services, _) => - { - services.AddMetricsActuator(); - }; - - MetricCollectionHostedService service = testContext.GetServices().OfType().Single(); - - await service.StartAsync(CancellationToken.None); - - try - { - var handler = testContext.GetRequiredService(); - MetricsResponse? result = await handler.InvokeAsync(null, CancellationToken.None); - Assert.NotNull(result); - - Assert.IsType(result); - Assert.NotNull(result.Names); - Assert.Empty(result.Names); - } - finally - { - await service.StopAsync(CancellationToken.None); - } - } - } - - [Fact] - public async Task Invoke_WithMetricsRequest_ReturnsExpected() - { - using var testContext = new TestContext(_testOutputHelper); - - testContext.AdditionalServices = (services, _) => - { - services.AddMetricsActuator(); - }; - - MetricCollectionHostedService service = testContext.GetServices().OfType().Single(); - - await service.StartAsync(CancellationToken.None); - - try - { - var handler = testContext.GetRequiredService(); - - Counter testMeasure = SteeltoeMetrics.Meter.CreateCounter("test.test5"); - long allKeysSum = 0; - - Dictionary labels = new() - { - { "a", "v1" }, - { "b", "v1" }, - { "c", "v1" } - }; - - for (int index = 0; index < 10; index++) - { - allKeysSum += index; - testMeasure.Add(index, labels.AsReadonlySpan()); - } - - List> tags = labels.Select(pair => new KeyValuePair(pair.Key, pair.Value!.ToString()!)).ToList(); - var request = new MetricsRequest("test.test5", tags); - MetricsResponse? response = await handler.InvokeAsync(request, CancellationToken.None); - Assert.NotNull(response); - - Assert.Equal("test.test5", response.Name); - - Assert.NotNull(response.Measurements); - Assert.Single(response.Measurements); - - MetricSample? sample = response.Measurements.SingleOrDefault(metricSample => metricSample.Statistic == MetricStatistic.Rate); - Assert.NotNull(sample); - Assert.Equal(allKeysSum, sample.Value); - - Assert.NotNull(response.AvailableTags); - Assert.Equal(3, response.AvailableTags.Count); - - request = new MetricsRequest("foo.bar", tags); - response = await handler.InvokeAsync(request, CancellationToken.None); - Assert.Null(response); - } - finally - { - await service.StopAsync(CancellationToken.None); - } - } - - [Fact] - public async Task Invoke_WithMetricsRequest_ReturnsExpected_IncludesAdditionalInstruments() - { - using var testContext = new TestContext(_testOutputHelper); - - testContext.AdditionalConfiguration = configuration => - { - configuration.AddInMemoryCollection(new Dictionary - { - ["management:endpoints:metrics:includedMetrics:0"] = "AdditionalTestMeter:AdditionalInstrument" - }); - }; - - testContext.AdditionalServices = (services, _) => - { - services.AddMetricsActuator(); - }; - - MetricCollectionHostedService service = testContext.GetServices().OfType().Single(); - - await service.StartAsync(CancellationToken.None); - - try - { - var handler = testContext.GetRequiredService(); - - Counter testMeasure = SteeltoeMetrics.Meter.CreateCounter("test.test5"); - var additionalMeter = new Meter("AdditionalTestMeter"); - Counter additionalInstrument = additionalMeter.CreateCounter("AdditionalInstrument"); - - long allKeysSum = 0; - - Dictionary labels = new() - { - { "a", "v1" }, - { "b", "v1" }, - { "c", "v1" } - }; - - for (int index = 0; index < 10; index++) - { - allKeysSum += index; - testMeasure.Add(index, labels.AsReadonlySpan()); - additionalInstrument.Add(index, labels.AsReadonlySpan()); - } - - List> tags = labels.Select(pair => new KeyValuePair(pair.Key, pair.Value!.ToString()!)).ToList(); - var request = new MetricsRequest("test.test5", tags); - MetricsResponse? response = await handler.InvokeAsync(request, CancellationToken.None); - Assert.NotNull(response); - - Assert.Equal("test.test5", response.Name); - - Assert.NotNull(response.Measurements); - Assert.Single(response.Measurements); - - MetricSample? sample = response.Measurements.SingleOrDefault(metricSample => metricSample.Statistic == MetricStatistic.Rate); - Assert.NotNull(sample); - Assert.Equal(allKeysSum, sample.Value); - - Assert.NotNull(response.AvailableTags); - Assert.Equal(3, response.AvailableTags.Count); - - request = new MetricsRequest("AdditionalInstrument", tags); - response = await handler.InvokeAsync(request, CancellationToken.None); - Assert.NotNull(response); - - Assert.Equal("AdditionalInstrument", response.Name); - } - finally - { - await service.StopAsync(CancellationToken.None); - } - } - - [Fact] - public async Task GetMetricSamples_ReturnsExpectedCounter() - { - using var testContext = new TestContext(_testOutputHelper); - - testContext.AdditionalServices = (services, _) => - { - services.AddMetricsActuator(); - }; - - MetricCollectionHostedService service = testContext.GetServices().OfType().Single(); - - await service.StartAsync(CancellationToken.None); - - try - { - var handler = (MetricsEndpointHandler)testContext.GetRequiredService(); - - Counter counter = SteeltoeMetrics.Meter.CreateCounter("test.test7"); - counter.Add(100); - - (MetricsCollection> measurements, _) = handler.GetMetrics(); - Assert.NotNull(measurements); - Assert.Single(measurements.Values); - MetricSample sample = measurements.Values.First()[0]; - Assert.Equal(100, sample.Value); - Assert.Equal(MetricStatistic.Rate, sample.Statistic); - } - finally - { - await service.StopAsync(CancellationToken.None); - } - } - - [Fact] - public async Task GetAvailableTags_ReturnsExpected() - { - using var testContext = new TestContext(_testOutputHelper); - - testContext.AdditionalServices = (services, _) => - { - services.AddMetricsActuator(); - }; - - MetricCollectionHostedService service = testContext.GetServices().OfType().Single(); - - await service.StartAsync(CancellationToken.None); - - try - { - var handler = (MetricsEndpointHandler)testContext.GetRequiredService(); - Counter counter = SteeltoeMetrics.Meter.CreateCounter("test.test2"); - - Dictionary v1Tags = new() - { - { "a", "v1" }, - { "b", "v1" }, - { "c", "v1" } - }; - - Dictionary v2Tags = new() - { - { "a", "v2" }, - { "b", "v2" }, - { "c", "v2" } - }; - - counter.Add(1, v1Tags.AsReadonlySpan()); - counter.Add(1, v2Tags.AsReadonlySpan()); - - (_, MetricsCollection> tagDictionary) = handler.GetMetrics(); - - Assert.NotNull(tagDictionary); - Assert.Single(tagDictionary.Values); - - IList tags = tagDictionary.GetOrAdd("test.test2", []); - - Assert.Equal(3, tags.Count); - - MetricTag tag = tags[0]; - Assert.NotNull(tag); - Assert.Contains("v1", tag.Values); - Assert.Contains("v2", tag.Values); - - tag = tags[1]; - Assert.Equal("b", tag.Tag); - Assert.Contains("v1", tag.Values); - Assert.Contains("v2", tag.Values); - - tag = tags[2]; - Assert.Equal("c", tag.Tag); - Assert.Contains("v1", tag.Values); - Assert.Contains("v2", tag.Values); - - Counter counter2 = SteeltoeMetrics.Meter.CreateCounter("test.test3"); - - counter2.Add(1); - - (_, tagDictionary) = handler.GetMetrics(); - - Assert.NotNull(tagDictionary); - Assert.Single(tagDictionary.Values); - - tags = tagDictionary.GetOrAdd("test.test3", []); - Assert.Empty(tags); - } - finally - { - await service.StopAsync(CancellationToken.None); - } - } - - [Fact] - public async Task GetMetricMeasurements_ReturnsExpected() - { - using var testContext = new TestContext(_testOutputHelper); - - testContext.AdditionalServices = (services, _) => - { - services.AddMetricsActuator(); - }; - - MetricCollectionHostedService service = testContext.GetServices().OfType().Single(); - - await service.StartAsync(CancellationToken.None); - - try - { - var handler = (MetricsEndpointHandler)testContext.GetRequiredService(); - - Histogram testMeasure = SteeltoeMetrics.Meter.CreateHistogram("test.test1"); - - Dictionary context1 = new() - { - { "a", "v1" }, - { "b", "v1" }, - { "c", "v1" } - }; - - Dictionary context2 = new() - { - { "a", "v1" } - }; - - Dictionary context3 = new() - { - { "b", "v1" } - }; - - Dictionary context4 = new() - { - { "c", "v1" } - }; - - long allKeysSum = 0; - - for (int index = 0; index < 10; index++) - { - allKeysSum += index; - testMeasure.Record(index, context1.AsReadonlySpan()); - } - - long aSum = 0; - - for (int index = 0; index < 10; index++) - { - aSum += index; - testMeasure.Record(index, context2.AsReadonlySpan()); - } - - long bSum = 0; - - for (int index = 0; index < 10; index++) - { - bSum += index; - testMeasure.Record(index, context3.AsReadonlySpan()); - } - - long cSum = 0; - - for (int index = 0; index < 10; index++) - { - cSum += index; - testMeasure.Record(index, context4.AsReadonlySpan()); - } - - (MetricsCollection> measurements, _) = handler.GetMetrics(); - Assert.NotNull(measurements); - Assert.Single(measurements); - - IList measurement = measurements.GetOrAdd("test.test1", []); - Assert.Equal(4, measurement.Count); - - MetricSample sample = measurement[0]; - Assert.Equal(allKeysSum, sample.Value); - Assert.Equal(MetricStatistic.Total, sample.Statistic); - - List> aTags = [new("a", "v1")]; - - IList result = handler.GetMetricSamplesByTags(measurements, "test.test1", aTags); - Assert.NotNull(result); - Assert.Single(result); - - sample = result[0]; - Assert.Equal(allKeysSum + aSum, sample.Value); - Assert.Equal(MetricStatistic.Total, sample.Statistic); - - List> bTags = [new("b", "v1")]; - - result = handler.GetMetricSamplesByTags(measurements, "test.test1", bTags); - - Assert.NotNull(result); - Assert.Single(result); - - sample = result[0]; - - Assert.Equal(allKeysSum + bSum, sample.Value); - Assert.Equal(MetricStatistic.Total, sample.Statistic); - - List> cTags = [new("c", "v1")]; - - result = handler.GetMetricSamplesByTags(measurements, "test.test1", cTags); - Assert.NotNull(result); - Assert.Single(result); - - sample = result[0]; - Assert.Equal(allKeysSum + cSum, sample.Value); - Assert.Equal(MetricStatistic.Total, sample.Statistic); - - List> abTags = - [ - new("a", "v1"), - new("b", "v1") - ]; - - result = handler.GetMetricSamplesByTags(measurements, "test.test1", abTags); - - Assert.NotNull(result); - Assert.Single(result); - - sample = result[0]; - Assert.Equal(allKeysSum, sample.Value); - Assert.Equal(MetricStatistic.Total, sample.Statistic); - - List> acTags = - [ - new("a", "v1"), - new("c", "v1") - ]; - - result = handler.GetMetricSamplesByTags(measurements, "test.test1", acTags); - - Assert.NotNull(result); - Assert.Single(result); - - sample = result[0]; - - Assert.Equal(allKeysSum, sample.Value); - Assert.Equal(MetricStatistic.Total, sample.Statistic); - - List> bcTags = - [ - new("b", "v1"), - new("c", "v1") - ]; - - result = handler.GetMetricSamplesByTags(measurements, "test.test1", bcTags); - - Assert.NotNull(result); - Assert.Single(result); - - sample = result[0]; - - Assert.Equal(allKeysSum, sample.Value); - Assert.Equal(MetricStatistic.Total, sample.Statistic); - } - finally - { - await service.StopAsync(CancellationToken.None); - } - } - - [Fact] - public async Task GetMetric_ReturnsExpected() - { - using var testContext = new TestContext(_testOutputHelper); - - testContext.AdditionalServices = (services, _) => - { - services.AddMetricsActuator(); - }; - - MetricCollectionHostedService service = testContext.GetServices().OfType().Single(); - - await service.StartAsync(CancellationToken.None); - - try - { - var handler = testContext.GetRequiredService(); - - Counter testMeasure = SteeltoeMetrics.Meter.CreateCounter("test.total"); - - Dictionary labels = new() - { - { "a", "v1" }, - { "b", "v1" }, - { "c", "v1" } - }; - - double allKeysSum = 0; - - for (double index = 0; index < 10; index++) - { - allKeysSum += index; - testMeasure.Add(index, labels.AsReadonlySpan()); - } - - var request = new MetricsRequest("test.total", labels.Select(pair => new KeyValuePair(pair.Key, pair.Value!.ToString()!)).ToList()); - - MetricsResponse? response = await handler.InvokeAsync(request, CancellationToken.None); - - Assert.NotNull(response); - - Assert.Equal("test.total", response.Name); - - Assert.NotNull(response.Measurements); - Assert.Single(response.Measurements); - MetricSample sample = response.Measurements[0]; - Assert.Equal(MetricStatistic.Rate, sample.Statistic); - Assert.Equal(allKeysSum, sample.Value); - - Assert.NotNull(response.AvailableTags); - Assert.Equal(3, response.AvailableTags.Count); - } - finally - { - await service.StopAsync(CancellationToken.None); - } - } -} diff --git a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsListNamesResponseTest.cs b/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsListNamesResponseTest.cs deleted file mode 100644 index 5859340f9e..0000000000 --- a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsListNamesResponseTest.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Common.TestResources; -using Steeltoe.Management.Endpoint.Actuators.Metrics; - -namespace Steeltoe.Management.Endpoint.Test.Actuators.Metrics; - -public sealed class MetricsListNamesResponseTest : BaseTest -{ - [Fact] - public void Constructor_SetsValues() - { - var names = new HashSet - { - "foo.bar", - "bar.foo" - }; - - var response = new MetricsResponse(names); - Assert.NotNull(response.Names); - Assert.Same(names, response.Names); - } - - [Fact] - public void JsonSerialization_ReturnsExpected() - { - var names = new HashSet - { - "foo.bar", - "bar.foo" - }; - - var response = new MetricsResponse(names); - string result = Serialize(response); - - result.Should().BeJson(""" - { - "names": [ - "foo.bar", - "bar.foo" - ] - } - """); - } -} diff --git a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsObserverOptionsTest.cs b/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsObserverOptionsTest.cs deleted file mode 100644 index acdf62bd44..0000000000 --- a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsObserverOptionsTest.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Management.Diagnostics; -using Steeltoe.Management.Endpoint.Actuators.Metrics; - -namespace Steeltoe.Management.Endpoint.Test.Actuators.Metrics; - -public sealed class MetricsObserverOptionsTest : BaseTest -{ - [Fact] - public void Constructor_InitializesWithDefaults() - { - MetricsObserverOptions options = GetOptionsFromSettings(); - - Assert.Equal(ConfigureMetricsObserverOptions.DefaultIngressIgnorePattern, options.IngressIgnorePattern); - Assert.Equal(ConfigureMetricsObserverOptions.DefaultEgressIgnorePattern, options.EgressIgnorePattern); - Assert.True(options.AspNetCoreHosting); - Assert.True(options.GCEvents); - Assert.False(options.EventCounterEvents); - Assert.Equal(1, options.EventCounterIntervalSec); - Assert.True(options.ThreadPoolEvents); - Assert.False(options.HttpClientCore); - Assert.False(options.HttpClientDesktop); - } - - [Fact] - public void Constructor_BindsConfigurationCorrectly() - { - var appSettings = new Dictionary - { - ["management:metrics:observer:ingressIgnorePattern"] = "pattern", - ["management:metrics:observer:egressIgnorePattern"] = "pattern", - ["management:metrics:observer:aspnetcoreHosting"] = "false", - ["management:metrics:observer:gcEvents"] = "false", - ["management:metrics:observer:eventCounterEvents"] = "true", - ["management:metrics:observer:eventCounterIntervalSec"] = "5", - ["management:metrics:observer:threadPoolEvents"] = "false", - ["management:metrics:observer:httpClientCore"] = "true", - ["management:metrics:observer:httpClientDesktop"] = "true" - }; - - MetricsObserverOptions options = GetOptionsFromSettings(appSettings); - - Assert.Equal("pattern", options.IngressIgnorePattern); - Assert.Equal("pattern", options.EgressIgnorePattern); - Assert.False(options.AspNetCoreHosting); - Assert.False(options.GCEvents); - Assert.True(options.EventCounterEvents); - Assert.Equal(5, options.EventCounterIntervalSec); - Assert.False(options.ThreadPoolEvents); - Assert.True(options.HttpClientCore); - Assert.True(options.HttpClientDesktop); - } -} diff --git a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsRequestTest.cs b/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsRequestTest.cs deleted file mode 100644 index 1c8d77d88e..0000000000 --- a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsRequestTest.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Management.Endpoint.Actuators.Metrics; - -namespace Steeltoe.Management.Endpoint.Test.Actuators.Metrics; - -public sealed class MetricsRequestTest : BaseTest -{ - [Fact] - public void Constructor_SetsValues() - { - List> tags = []; - var request = new MetricsRequest("foo.bar", tags); - Assert.Equal("foo.bar", request.MetricName); - Assert.Same(tags, request.Tags); - } -} diff --git a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsResponseTest.cs b/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsResponseTest.cs deleted file mode 100644 index 9c8b8c20ae..0000000000 --- a/src/Management/test/Endpoint.Test/Actuators/Metrics/MetricsResponseTest.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Common.TestResources; -using Steeltoe.Management.Endpoint.Actuators.Metrics; - -namespace Steeltoe.Management.Endpoint.Test.Actuators.Metrics; - -public sealed class MetricsResponseTest : BaseTest -{ - [Fact] - public void Constructor_SetsValues() - { - List samples = [new(MetricStatistic.TotalTime, 100.00, null)]; - - List tags = - [ - new("tag", new HashSet - { - "tagValue" - }) - ]; - - var response = new MetricsResponse("foo.bar", samples, tags); - Assert.Equal("foo.bar", response.Name); - Assert.Same(samples, response.Measurements); - Assert.Same(tags, response.AvailableTags); - } - - [Fact] - public void JsonSerialization_ReturnsExpected() - { - List samples = [new(MetricStatistic.TotalTime, 100.1, null)]; - - List tags = - [ - new("tag", new HashSet - { - "tagValue" - }) - ]; - - var response = new MetricsResponse("foo.bar", samples, tags); - string result = Serialize(response); - - result.Should().BeJson(""" - { - "name": "foo.bar", - "measurements": [ - { - "statistic": "TOTAL_TIME", - "value": 100.1 - } - ], - "availableTags": [ - { - "tag": "tag", - "values": [ - "tagValue" - ] - } - ] - } - """); - } -} diff --git a/src/Management/test/Endpoint.Test/Actuators/Metrics/Observers/EventCounterListenerTest.cs b/src/Management/test/Endpoint.Test/Actuators/Metrics/Observers/EventCounterListenerTest.cs deleted file mode 100644 index 66751e3e2b..0000000000 --- a/src/Management/test/Endpoint.Test/Actuators/Metrics/Observers/EventCounterListenerTest.cs +++ /dev/null @@ -1,164 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Logging.Abstractions; -using Steeltoe.Common.TestResources; -using Steeltoe.Management.Diagnostics; -using Steeltoe.Management.Endpoint.Actuators.Metrics; -using Steeltoe.Management.Endpoint.Actuators.Metrics.Observers; -using Steeltoe.Management.Endpoint.Actuators.Metrics.SystemDiagnosticsMetrics; - -namespace Steeltoe.Management.Endpoint.Test.Actuators.Metrics.Observers; - -public sealed class EventCounterListenerTest : BaseTest -{ - private readonly MetricsExporterOptions _exporterOptions = new() - { - CacheDurationMilliseconds = 500 - }; - - private readonly string[] _metrics = - [ - "System.Runtime.alloc-rate", - "System.Runtime.gen-2-gc-count", - "System.Runtime.threadpool-completed-items-count", - "System.Runtime.monitor-lock-contention-count", - "System.Runtime.gen-1-gc-count", - "System.Runtime.gen-0-gc-count", - "System.Runtime.exception-count", - "System.Runtime.time-in-gc", - "System.Runtime.threadpool-thread-count", - "System.Runtime.gen-1-size", - "System.Runtime.threadpool-queue-length", - "System.Runtime.gen-2-size", - "System.Runtime.gc-heap-size", - "System.Runtime.assembly-count", - "System.Runtime.gen-0-size", - "System.Runtime.cpu-usage", - "System.Runtime.active-timer-count", - "System.Runtime.loh-size", - "System.Runtime.working-set" - ]; - - [Fact] - public async Task EventCounterListenerGetsMetricsTest() - { - var options = new MetricsObserverOptions - { - EventCounterEvents = true, - EventCounterIntervalSec = 1 - }; - - TestOptionsMonitor optionsMonitor = TestOptionsMonitor.Create(options); - - using var listener = new EventCounterListener(optionsMonitor, NullLogger.Instance); - SteeltoeMetrics.InstrumentationName = Guid.NewGuid().ToString(); - - var exporter = new MetricsExporter(_exporterOptions); - using AggregationManager aggregationManager = GetTestMetrics(exporter); - aggregationManager.Start(); - await Task.Delay(2000); - - (MetricsCollection> metricSamples, _) = exporter.Export(); - - foreach (string metric in _metrics) - { - List>> summary = metricSamples.Where(pair => pair.Key == metric).ToList(); - Assert.True(summary != null, $"Summary was null for {metric}"); - Assert.True(summary.Count > 0, $"Summary was empty for {metric}"); - } - } - - [Fact] - public async Task EventCounterListenerGetsMetricsWithExclusionsTest() - { - SteeltoeMetrics.InstrumentationName = Guid.NewGuid().ToString(); - - List exclusions = - [ - "alloc-rate", - "threadpool-completed-items-count", - "gen-1-gc-count", - "gen-1-size" - ]; - - var options = new MetricsObserverOptions - { - EventCounterEvents = true, - EventCounterIntervalSec = 1 - }; - - foreach (string exclusion in exclusions) - { - options.ExcludedMetrics.Add(exclusion); - } - - TestOptionsMonitor optionsMonitor = TestOptionsMonitor.Create(options); - using var listener = new EventCounterListener(optionsMonitor, NullLogger.Instance); - - var exporter = new MetricsExporter(_exporterOptions); - using AggregationManager aggregationManager = GetTestMetrics(exporter); - aggregationManager.Start(); - await Task.Delay(2000); - - (MetricsCollection> metricSamples, _) = exporter.Export(); - - foreach (string metric in _metrics) - { - List>> summary = metricSamples.Where(pair => pair.Key == metric).ToList(); - - if (!exclusions.Contains(metric.Replace("System.Runtime.", string.Empty, StringComparison.Ordinal))) - { - Assert.True(summary.Count > 0, $"Expected metrics for {metric}"); - } - else - { - Assert.True(summary.Count == 0, $"Expected no metrics for {metric}"); - } - } - } - - [Fact] - public async Task EventCounterListenerGetsMetricsWithInclusionsTest() - { - SteeltoeMetrics.InstrumentationName = Guid.NewGuid().ToString(); - - List inclusions = ["cpu-usage"]; - - var options = new MetricsObserverOptions - { - EventCounterEvents = true, - EventCounterIntervalSec = 1 - }; - - foreach (string inclusion in inclusions) - { - options.IncludedMetrics.Add(inclusion); - } - - TestOptionsMonitor optionsMonitor = TestOptionsMonitor.Create(options); - using var listener = new EventCounterListener(optionsMonitor, NullLogger.Instance); - - var exporter = new MetricsExporter(_exporterOptions); - using AggregationManager aggregationManager = GetTestMetrics(exporter); - aggregationManager.Start(); - await Task.Delay(2000); - - (MetricsCollection> metricSamples, _) = exporter.Export(); - - foreach (string metric in _metrics) - { - List>> summary = metricSamples.Where(pair => pair.Key == metric).ToList(); - - if (inclusions.Contains(metric["System.Runtime.".Length..])) - { - Assert.True(summary.Count > 0, $"Expected metrics for {metric}"); - } - else - { - Assert.True(summary.Count == 0, $"Expected no metrics for {metric}"); - } - } - } -} diff --git a/src/Management/test/Endpoint.Test/BaseTest.cs b/src/Management/test/Endpoint.Test/BaseTest.cs index 7190ae3267..8a69e73009 100644 --- a/src/Management/test/Endpoint.Test/BaseTest.cs +++ b/src/Management/test/Endpoint.Test/BaseTest.cs @@ -9,8 +9,6 @@ using Microsoft.Extensions.Options; using Steeltoe.Common.Extensions; using Steeltoe.Management.Endpoint.Actuators.Health; -using Steeltoe.Management.Endpoint.Actuators.Metrics; -using Steeltoe.Management.Endpoint.Actuators.Metrics.SystemDiagnosticsMetrics; namespace Steeltoe.Management.Endpoint.Test; @@ -42,33 +40,6 @@ protected string Serialize(T value) return JsonSerializer.Serialize(value, SerializerOptions); } - internal AggregationManager GetTestMetrics(MetricsExporter exporter) - { - var aggregationManager = new AggregationManager(100, 100, exporter.AddMetrics, (_, _) => - { - }, (_, _) => - { - }, _ => - { - }, _ => - { - }, _ => - { - }, () => - { - }, exception => throw exception, () => - { - }, () => - { - }, exception => throw exception); - - aggregationManager.Include(SteeltoeMetrics.InstrumentationName); - - exporter.SetCollect(aggregationManager.Collect); - - return aggregationManager; - } - protected static IOptionsMonitor GetOptionsMonitorFromSettings() { return GetOptionsMonitorFromSettings([]); diff --git a/src/Management/test/Endpoint.Test/ContentNegotiation/ContentNegotiationTest.cs b/src/Management/test/Endpoint.Test/ContentNegotiation/ContentNegotiationTest.cs index 45aabb7210..5d4cee59f5 100644 --- a/src/Management/test/Endpoint.Test/ContentNegotiation/ContentNegotiationTest.cs +++ b/src/Management/test/Endpoint.Test/ContentNegotiation/ContentNegotiationTest.cs @@ -26,7 +26,6 @@ public static TheoryData EndpointMiddlew (EndpointName.Hypermedia, "http://localhost/actuator"), (EndpointName.Cloudfoundry, "http://localhost/cloudfoundryapplication"), (EndpointName.Info, "http://localhost/actuator/info"), - (EndpointName.Metrics, "http://localhost/actuator/metrics"), (EndpointName.Loggers, "http://localhost/actuator/loggers"), (EndpointName.Health, "http://localhost/actuator/health"), (EndpointName.HttpExchanges, "http://localhost/actuator/httpexchanges"), diff --git a/src/Management/test/Endpoint.Test/ContentNegotiation/EndpointName.cs b/src/Management/test/Endpoint.Test/ContentNegotiation/EndpointName.cs index 6c5ea30474..fc45d2e293 100644 --- a/src/Management/test/Endpoint.Test/ContentNegotiation/EndpointName.cs +++ b/src/Management/test/Endpoint.Test/ContentNegotiation/EndpointName.cs @@ -9,7 +9,6 @@ public enum EndpointName Cloudfoundry, Hypermedia, Info, - Metrics, Loggers, Health, HttpExchanges, diff --git a/src/Management/test/Endpoint.Test/ContentNegotiation/MetricsStartup.cs b/src/Management/test/Endpoint.Test/ContentNegotiation/MetricsStartup.cs index 571ee9cef3..25f3feb75c 100644 --- a/src/Management/test/Endpoint.Test/ContentNegotiation/MetricsStartup.cs +++ b/src/Management/test/Endpoint.Test/ContentNegotiation/MetricsStartup.cs @@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Steeltoe.Management.Endpoint.Actuators.Hypermedia; -using Steeltoe.Management.Endpoint.Actuators.Metrics; namespace Steeltoe.Management.Endpoint.Test.ContentNegotiation; @@ -14,7 +13,6 @@ public sealed class MetricsStartup public void ConfigureServices(IServiceCollection services) { services.AddHypermediaActuator(); - services.AddMetricsActuator(); } public void Configure(IApplicationBuilder app) diff --git a/src/Management/test/Endpoint.Test/ContentNegotiation/TestStartupExtensions.cs b/src/Management/test/Endpoint.Test/ContentNegotiation/TestStartupExtensions.cs index 0cfda31829..11eb4c5045 100644 --- a/src/Management/test/Endpoint.Test/ContentNegotiation/TestStartupExtensions.cs +++ b/src/Management/test/Endpoint.Test/ContentNegotiation/TestStartupExtensions.cs @@ -15,7 +15,6 @@ internal static IWebHostBuilder UseStartupForEndpoint(this IWebHostBuilder build EndpointName.Cloudfoundry => builder.UseStartup(), EndpointName.Hypermedia => builder.UseStartup(), EndpointName.Info => builder.UseStartup(), - EndpointName.Metrics => builder.UseStartup(), EndpointName.Loggers => builder.UseStartup(), EndpointName.Health => builder.UseStartup(), EndpointName.HttpExchanges => builder.UseStartup(), diff --git a/src/Management/test/Endpoint.Test/ManagementHostBuilderExtensionsTest.cs b/src/Management/test/Endpoint.Test/ManagementHostBuilderExtensionsTest.cs index 31e0aa97c1..eb45556706 100755 --- a/src/Management/test/Endpoint.Test/ManagementHostBuilderExtensionsTest.cs +++ b/src/Management/test/Endpoint.Test/ManagementHostBuilderExtensionsTest.cs @@ -25,7 +25,6 @@ using Steeltoe.Management.Endpoint.Actuators.Hypermedia; using Steeltoe.Management.Endpoint.Actuators.Info; using Steeltoe.Management.Endpoint.Actuators.Loggers; -using Steeltoe.Management.Endpoint.Actuators.Metrics; using Steeltoe.Management.Endpoint.Actuators.Refresh; using Steeltoe.Management.Endpoint.Actuators.RouteMappings; using Steeltoe.Management.Endpoint.Actuators.Services; @@ -370,32 +369,6 @@ public async Task AddMappingsActuator_IHostBuilder_IStartupFilterFires() Assert.Equal(HttpStatusCode.OK, response.StatusCode); } - [Fact] - public void AddMetricsActuator_IHostBuilder() - { - IHostBuilder hostBuilder = TestHostBuilderFactory.Create(); - hostBuilder.AddMetricsActuator(); - using IHost host = hostBuilder.Build(); - - host.Services.GetService().Should().NotBeNull(); - host.Services.GetServices().OfType().Should().ContainSingle(); - } - - [Fact] - public async Task AddMetricsActuator_IHostBuilder_IStartupFilterFires() - { - IHostBuilder hostBuilder = TestHostBuilderFactory.CreateWeb(); - hostBuilder.ConfigureWebHost(ConfigureWebHostWithAllActuatorsExposed); - hostBuilder.AddMetricsActuator(); - - using IHost host = await hostBuilder.StartAsync(); - using HttpClient httpClient = host.GetTestClient(); - - var requestUri = new Uri("/actuator/metrics", UriKind.Relative); - HttpResponseMessage response = await httpClient.GetAsync(requestUri); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - [Fact] public void AddRefreshActuator_IHostBuilder() { diff --git a/src/Management/test/Endpoint.Test/ManagementWebApplicationBuilderExtensionsTest.cs b/src/Management/test/Endpoint.Test/ManagementWebApplicationBuilderExtensionsTest.cs index af63cbfbfb..04e832a2ff 100755 --- a/src/Management/test/Endpoint.Test/ManagementWebApplicationBuilderExtensionsTest.cs +++ b/src/Management/test/Endpoint.Test/ManagementWebApplicationBuilderExtensionsTest.cs @@ -24,7 +24,6 @@ using Steeltoe.Management.Endpoint.Actuators.Hypermedia; using Steeltoe.Management.Endpoint.Actuators.Info; using Steeltoe.Management.Endpoint.Actuators.Loggers; -using Steeltoe.Management.Endpoint.Actuators.Metrics; using Steeltoe.Management.Endpoint.Actuators.Refresh; using Steeltoe.Management.Endpoint.Actuators.RouteMappings; using Steeltoe.Management.Endpoint.Actuators.Services; @@ -250,24 +249,6 @@ public async Task AddMappingsActuator_WebApplicationBuilder_IStartupFilterFires( Assert.Equal(HttpStatusCode.OK, response.StatusCode); } - [Fact] - public async Task AddMetricsActuator_WebApplicationBuilder_IStartupFilterFires() - { - WebApplicationBuilder hostBuilder = GetTestServerWithAllActuatorsExposed(); - hostBuilder.AddMetricsActuator(); - - await using WebApplication host = hostBuilder.Build(); - host.UseRouting(); - await host.StartAsync(); - - Assert.Single(host.Services.GetServices()); - Assert.Single(host.Services.GetServices().Where(filter => filter is AllActuatorsStartupFilter)); - - using HttpClient httpClient = host.GetTestClient(); - HttpResponseMessage response = await httpClient.GetAsync(new Uri("/actuator/metrics", UriKind.Relative)); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - [Fact] public async Task AddRefreshActuator_WebApplicationBuilder_IStartupFilterFires() { diff --git a/src/Management/test/Endpoint.Test/ManagementWebHostBuilderExtensionsTest.cs b/src/Management/test/Endpoint.Test/ManagementWebHostBuilderExtensionsTest.cs index 91d6743b0c..dc870b9af7 100755 --- a/src/Management/test/Endpoint.Test/ManagementWebHostBuilderExtensionsTest.cs +++ b/src/Management/test/Endpoint.Test/ManagementWebHostBuilderExtensionsTest.cs @@ -24,7 +24,6 @@ using Steeltoe.Management.Endpoint.Actuators.Hypermedia; using Steeltoe.Management.Endpoint.Actuators.Info; using Steeltoe.Management.Endpoint.Actuators.Loggers; -using Steeltoe.Management.Endpoint.Actuators.Metrics; using Steeltoe.Management.Endpoint.Actuators.Refresh; using Steeltoe.Management.Endpoint.Actuators.RouteMappings; using Steeltoe.Management.Endpoint.Actuators.Services; @@ -340,32 +339,6 @@ public async Task AddMappingsActuator_IWebHostBuilder_IStartupFilterFires() Assert.Equal(HttpStatusCode.OK, response.StatusCode); } - [Fact] - public void AddMetricsActuator_IWebHostBuilder() - { - IWebHostBuilder hostBuilder = TestWebHostBuilderFactory.Create(); - hostBuilder.AddMetricsActuator(); - using IWebHost host = hostBuilder.Build(); - - host.Services.GetService().Should().NotBeNull(); - host.Services.GetServices().OfType().Should().ContainSingle(); - } - - [Fact] - public async Task AddMetricsActuator_IWebHostBuilder_IStartupFilterFires() - { - IWebHostBuilder hostBuilder = GetWebHostBuilderWithAllActuatorsExposed(); - hostBuilder.AddMetricsActuator(); - - using IWebHost host = hostBuilder.Build(); - await host.StartAsync(); - using HttpClient httpClient = host.GetTestClient(); - - var requestUri = new Uri("/actuator/metrics", UriKind.Relative); - HttpResponseMessage response = await httpClient.GetAsync(requestUri); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - [Fact] public void AddRefreshActuator_IWebHostBuilder() {