From 4984b950f2eb9f0d199b4b40565eccf95243d54f Mon Sep 17 00:00:00 2001 From: Wiktor Kopec Date: Fri, 1 Oct 2021 15:46:30 -0700 Subject: [PATCH 1/6] Use a singular option for all counter intervals --- documentation/openapi.json | 39 -------------- documentation/schema.json | 54 ++++++++----------- .../GlobalCounterOptions.cs | 17 ++++++ .../GlobalCounterOptionsDefaults.cs | 15 ++++++ .../MetricsOptions.cs | 6 --- .../MetricsOptionsDefaults.cs | 2 - .../OptionsDisplayStrings.Designer.cs | 27 ---------- .../OptionsDisplayStrings.resx | 12 ----- .../Controllers/DiagController.Metrics.cs | 12 ++--- .../Controllers/DiagController.cs | 17 ++++-- .../Metrics/EventCounterSettingsFactory.cs | 15 +++--- .../Metrics/MetricsService.cs | 6 ++- .../Strings.Designer.cs | 9 ++++ .../Strings.resx | 3 ++ .../Validation/CounterValidator.cs | 37 +++++++++++++ .../CollectionRuleOptionsTests.cs | 16 +----- .../Actions/CollectTraceAction.cs | 6 ++- .../Options/Actions/ActionOptionsConstants.cs | 5 -- .../Actions/CollectTraceOptions.Validate.cs | 11 ++++ .../Options/Actions/CollectTraceOptions.cs | 7 --- .../Actions/CollectTraceOptionsDefaults.cs | 1 - .../Options/Triggers/EventCounterOptions.cs | 7 --- .../Triggers/EventCounterOptionsDefaults.cs | 1 - .../Triggers/TriggerOptionsConstants.cs | 5 -- .../Triggers/EventCounterTriggerFactory.cs | 6 ++- src/Tools/dotnet-monitor/ConfigurationKeys.cs | 2 + .../DiagnosticsMonitorCommandHandler.cs | 12 ++++- src/Tools/dotnet-monitor/RootOptions.cs | 2 + .../ServiceCollectionExtensions.cs | 5 ++ 29 files changed, 175 insertions(+), 182 deletions(-) create mode 100644 src/Microsoft.Diagnostics.Monitoring.Options/GlobalCounterOptions.cs create mode 100644 src/Microsoft.Diagnostics.Monitoring.Options/GlobalCounterOptionsDefaults.cs create mode 100644 src/Microsoft.Diagnostics.Monitoring.WebApi/Validation/CounterValidator.cs diff --git a/documentation/openapi.json b/documentation/openapi.json index 81a3b5b5107..2c93cdfc3f6 100644 --- a/documentation/openapi.json +++ b/documentation/openapi.json @@ -385,19 +385,6 @@ "default": 30 } }, - { - "name": "metricsIntervalSeconds", - "in": "query", - "description": "The reporting interval (in seconds) for event counters.", - "schema": { - "maximum": 2147483647, - "minimum": 1, - "type": "integer", - "description": "The reporting interval (in seconds) for event counters.", - "format": "int32", - "default": 1 - } - }, { "name": "egressProvider", "in": "query", @@ -852,19 +839,6 @@ "default": 30 } }, - { - "name": "metricsIntervalSeconds", - "in": "query", - "description": "The reporting interval (in seconds) for event counters.", - "schema": { - "maximum": 2147483647, - "minimum": 1, - "type": "integer", - "description": "The reporting interval (in seconds) for event counters.", - "format": "int32", - "default": 5 - } - }, { "name": "egressProvider", "in": "query", @@ -943,19 +917,6 @@ "default": 30 } }, - { - "name": "metricsIntervalSeconds", - "in": "query", - "description": "The reporting interval (in seconds) for event counters.", - "schema": { - "maximum": 2147483647, - "minimum": 1, - "type": "integer", - "description": "The reporting interval (in seconds) for event counters.", - "format": "int32", - "default": 5 - } - }, { "name": "egressProvider", "in": "query", diff --git a/documentation/schema.json b/documentation/schema.json index def16943dc4..1f16329c80c 100644 --- a/documentation/schema.json +++ b/documentation/schema.json @@ -37,6 +37,17 @@ "$ref": "#/definitions/CollectionRuleOptions" } }, + "GlobalCounter": { + "default": {}, + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/definitions/GlobalCounterOptions" + } + ] + }, "CorsConfiguration": { "default": {}, "oneOf": [ @@ -723,6 +734,18 @@ } } }, + "GlobalCounterOptions": { + "type": "object", + "additionalProperties": false, + "properties": { + "IntervalSeconds": { + "type": "integer", + "format": "int32", + "maximum": 86400.0, + "minimum": 1.0 + } + } + }, "CorsConfigurationOptions": { "type": "object", "additionalProperties": false, @@ -933,15 +956,6 @@ ], "description": "Endpoints that expose prometheus metrics. Defaults to http://localhost:52325." }, - "UpdateIntervalSeconds": { - "type": [ - "integer", - "null" - ], - "description": "How often metrics are collected.", - "format": "int32", - "default": 10 - }, "MetricCount": { "type": [ "integer", @@ -1168,17 +1182,6 @@ } ] }, - "MetricsIntervalSeconds": { - "type": [ - "integer", - "null" - ], - "description": "The amount of time (in seconds) between the collection of each sample of the counter. Only applicable when Profile contains Metrics.", - "format": "int32", - "default": 1, - "maximum": 86400.0, - "minimum": 1.0 - }, "Providers": { "type": [ "array", @@ -1520,17 +1523,6 @@ "description": "The sliding time window in which the counter must maintain its value as specified by the threshold levels in GreaterThan and LessThan.", "format": "time-span", "default": "00:01:00" - }, - "Frequency": { - "type": [ - "integer", - "null" - ], - "description": "The amount of time (in seconds) between the collection of each sample of the counter.", - "format": "int32", - "default": 5, - "maximum": 86400.0, - "minimum": 1.0 } } }, diff --git a/src/Microsoft.Diagnostics.Monitoring.Options/GlobalCounterOptions.cs b/src/Microsoft.Diagnostics.Monitoring.Options/GlobalCounterOptions.cs new file mode 100644 index 00000000000..0feb8d7b946 --- /dev/null +++ b/src/Microsoft.Diagnostics.Monitoring.Options/GlobalCounterOptions.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text; + +namespace Microsoft.Diagnostics.Monitoring.WebApi +{ + public class GlobalCounterOptions + { + [Range(1, 3600 * 24)] + public int IntervalSeconds { get; set; } + } +} diff --git a/src/Microsoft.Diagnostics.Monitoring.Options/GlobalCounterOptionsDefaults.cs b/src/Microsoft.Diagnostics.Monitoring.Options/GlobalCounterOptionsDefaults.cs new file mode 100644 index 00000000000..61e9df4b6e3 --- /dev/null +++ b/src/Microsoft.Diagnostics.Monitoring.Options/GlobalCounterOptionsDefaults.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Diagnostics.Monitoring.WebApi +{ + internal static class GlobalCounterOptionsDefaults + { + public const int IntervalSeconds = 5; + } +} diff --git a/src/Microsoft.Diagnostics.Monitoring.Options/MetricsOptions.cs b/src/Microsoft.Diagnostics.Monitoring.Options/MetricsOptions.cs index f55e59544f7..bf8dabb7b7f 100644 --- a/src/Microsoft.Diagnostics.Monitoring.Options/MetricsOptions.cs +++ b/src/Microsoft.Diagnostics.Monitoring.Options/MetricsOptions.cs @@ -27,12 +27,6 @@ public class MetricsOptions Description = nameof(OptionsDisplayStrings.DisplayAttributeDescription_MetricsOptions_Endpoints))] public string Endpoints { get; set; } - [Display( - ResourceType = typeof(OptionsDisplayStrings), - Description = nameof(OptionsDisplayStrings.DisplayAttributeDescription_MetricsOptions_UpdateIntervalSeconds))] - [DefaultValue(MetricsOptionsDefaults.UpdateIntervalSeconds)] - public int? UpdateIntervalSeconds { get; set; } - [Display( ResourceType = typeof(OptionsDisplayStrings), Description = nameof(OptionsDisplayStrings.DisplayAttributeDescription_MetricsOptions_MetricCount))] diff --git a/src/Microsoft.Diagnostics.Monitoring.Options/MetricsOptionsDefaults.cs b/src/Microsoft.Diagnostics.Monitoring.Options/MetricsOptionsDefaults.cs index 2d3ddc10e94..bcb35600d3e 100644 --- a/src/Microsoft.Diagnostics.Monitoring.Options/MetricsOptionsDefaults.cs +++ b/src/Microsoft.Diagnostics.Monitoring.Options/MetricsOptionsDefaults.cs @@ -8,8 +8,6 @@ internal class MetricsOptionsDefaults { public const bool Enabled = true; - public const int UpdateIntervalSeconds = 10; - public const int MetricCount = 3; public const bool IncludeDefaultProviders = true; diff --git a/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.Designer.cs b/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.Designer.cs index 056af949451..4d98ef73a3d 100644 --- a/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.Designer.cs +++ b/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.Designer.cs @@ -476,15 +476,6 @@ public static string DisplayAttributeDescription_CollectTraceOptions_Egress { } } - /// - /// Looks up a localized string similar to The amount of time (in seconds) between the collection of each sample of the counter. Only applicable when Profile contains Metrics.. - /// - public static string DisplayAttributeDescription_CollectTraceOptions_MetricsIntervalSeconds { - get { - return ResourceManager.GetString("DisplayAttributeDescription_CollectTraceOptions_MetricsIntervalSeconds", resourceCulture); - } - } - /// /// Looks up a localized string similar to Use a predefined set of event providers and settings to capture in the trace. More than one profile may be specified at the same time. Either Profile or Providers must be specified, but not both.. /// @@ -683,15 +674,6 @@ public static string DisplayAttributeDescription_EventCounterOptions_CounterName } } - /// - /// Looks up a localized string similar to The amount of time (in seconds) between the collection of each sample of the counter.. - /// - public static string DisplayAttributeDescription_EventCounterOptions_Frequency { - get { - return ResourceManager.GetString("DisplayAttributeDescription_EventCounterOptions_Frequency", resourceCulture); - } - } - /// /// Looks up a localized string similar to The threshold level the counter must maintain (or higher) for the specified duration. Either GreaterThan or LessThan (or both) must be specified.. /// @@ -900,15 +882,6 @@ public static string DisplayAttributeDescription_MetricsOptions_Providers { } } - /// - /// Looks up a localized string similar to How often metrics are collected.. - /// - public static string DisplayAttributeDescription_MetricsOptions_UpdateIntervalSeconds { - get { - return ResourceManager.GetString("DisplayAttributeDescription_MetricsOptions_UpdateIntervalSeconds", resourceCulture); - } - } - /// /// Looks up a localized string similar to The public key used to sign the JWT (JSON Web Token) used for authentication. This field is a JSON Web Key serialized as JSON encoded with base64Url encoding. The JWK must have a kty field of RSA or EC and should not have the private key information.. /// diff --git a/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.resx b/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.resx index 184d96d81a8..cd499f8fc62 100644 --- a/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.resx +++ b/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.resx @@ -289,10 +289,6 @@ The name of the egress provider to which the trace is egressed. The description provided for the Egress parameter on CollectTraceOptions. - - The amount of time (in seconds) between the collection of each sample of the counter. Only applicable when Profile contains Metrics. - The description provided for the MetricsIntervalSeconds parameter on CollectTraceOptions. - Use a predefined set of event providers and settings to capture in the trace. More than one profile may be specified at the same time. Either Profile or Providers must be specified, but not both. The description provided for the Profile parameter on CollectTraceOptions. @@ -385,10 +381,6 @@ The name of the counter to monitor. The description provided for the CounterName parameter on EventCounterOptions. - - The amount of time (in seconds) between the collection of each sample of the counter. - The description provided for the Frequency parameter on EventCounterOptions. - The threshold level the counter must maintain (or higher) for the specified duration. Either GreaterThan or LessThan (or both) must be specified. The description provided for the GreaterThan parameter on EventCounterOptions. @@ -481,10 +473,6 @@ Providers for custom metrics. The description provided for the Providers parameter on MetricsOptions. - - How often metrics are collected. - The description provided for the UpdateIntervalSeconds parameter on MetricsOptions. - The public key used to sign the JWT (JSON Web Token) used for authentication. This field is a JSON Web Key serialized as JSON encoded with base64Url encoding. The JWK must have a kty field of RSA or EC and should not have the private key information. The description provided for the PublicKey parameter on MonitorApiKeyOptions. diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.Metrics.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.Metrics.cs index f9b3de7c228..45a128061a8 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.Metrics.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.Metrics.cs @@ -28,7 +28,6 @@ partial class DiagController /// The Runtime instance cookie used to identify the target process. /// Process name used to identify the target process. /// The duration of the metrics session (in seconds). - /// The reporting interval (in seconds) for event counters. /// The egress provider to which the metrics are saved. [HttpGet("livemetrics", Name = nameof(CaptureMetrics))] [ProducesWithProblemDetails(ContentTypes.ApplicationJsonSequence)] @@ -45,8 +44,6 @@ public Task CaptureMetrics( string name = null, [FromQuery][Range(-1, int.MaxValue)] int durationSeconds = 30, - [FromQuery][Range(1, int.MaxValue)] - int metricsIntervalSeconds = 5, [FromQuery] string egressProvider = null) { @@ -60,9 +57,9 @@ public Task CaptureMetrics( { var client = new DiagnosticsClient(processInfo.EndpointInfo.Endpoint); EventPipeCounterPipelineSettings settings = EventCounterSettingsFactory.CreateSettings( + _counterOptions.CurrentValue, includeDefaults: true, - durationSeconds: durationSeconds, - refreshInterval: metricsIntervalSeconds); + durationSeconds: durationSeconds); await using EventCounterPipeline eventCounterPipeline = new EventCounterPipeline(client, settings, @@ -89,7 +86,6 @@ public Task CaptureMetrics( /// The Runtime instance cookie used to identify the target process. /// Process name used to identify the target process. /// The duration of the metrics session (in seconds). - /// The reporting interval (in seconds) for event counters. /// The egress provider to which the metrics are saved. [HttpPost("livemetrics", Name = nameof(CaptureMetricsCustom))] [ProducesWithProblemDetails(ContentTypes.ApplicationJsonSequence)] @@ -108,8 +104,6 @@ public Task CaptureMetricsCustom( string name = null, [FromQuery][Range(-1, int.MaxValue)] int durationSeconds = 30, - [FromQuery][Range(1, int.MaxValue)] - int metricsIntervalSeconds = 5, [FromQuery] string egressProvider = null) { @@ -123,8 +117,8 @@ public Task CaptureMetricsCustom( { var client = new DiagnosticsClient(processInfo.EndpointInfo.Endpoint); EventPipeCounterPipelineSettings settings = EventCounterSettingsFactory.CreateSettings( + _counterOptions.CurrentValue, durationSeconds, - metricsIntervalSeconds, configuration); await using EventCounterPipeline eventCounterPipeline = new EventCounterPipeline(client, diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.cs index 916631f5e6c..dbf3b99c7a9 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Diagnostics.Monitoring.EventPipe; +using Microsoft.Diagnostics.Monitoring.WebApi.Validation; using Microsoft.Diagnostics.NETCore.Client; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -41,6 +42,7 @@ public partial class DiagController : ControllerBase private readonly ILogger _logger; private readonly IDiagnosticServices _diagnosticServices; private readonly IOptions _diagnosticPortOptions; + private readonly IOptionsMonitor _counterOptions; private readonly EgressOperationStore _operationsStore; private readonly IDumpService _dumpService; @@ -52,6 +54,7 @@ public DiagController(ILogger logger, _diagnosticPortOptions = serviceProvider.GetService>(); _operationsStore = serviceProvider.GetRequiredService(); _dumpService = serviceProvider.GetRequiredService(); + _counterOptions = serviceProvider.GetRequiredService>(); } /// @@ -280,7 +283,6 @@ public Task CaptureGcDump( /// Process name used to identify the target process. /// The profiles enabled for the trace session. /// The duration of the trace session (in seconds). - /// The reporting interval (in seconds) for event counters. /// The egress provider to which the trace is saved. [HttpGet("trace", Name = nameof(CaptureTrace))] [ProducesWithProblemDetails(ContentTypes.ApplicationOctetStream)] @@ -302,8 +304,6 @@ public Task CaptureTrace( Models.TraceProfile profile = DefaultTraceProfiles, [FromQuery][Range(-1, int.MaxValue)] int durationSeconds = 30, - [FromQuery][Range(1, int.MaxValue)] - int metricsIntervalSeconds = 1, [FromQuery] string egressProvider = null) { @@ -313,7 +313,7 @@ public Task CaptureTrace( { TimeSpan duration = Utilities.ConvertSecondsToTimeSpan(durationSeconds); - var aggregateConfiguration = Utilities.GetTraceConfiguration(profile, metricsIntervalSeconds); + var aggregateConfiguration = Utilities.GetTraceConfiguration(profile, _counterOptions.CurrentValue.IntervalSeconds); return StartTrace(processInfo, aggregateConfiguration, duration, egressProvider); }, processKey, Utilities.ArtifactType_Trace); @@ -355,6 +355,15 @@ public Task CaptureTraceCustom( return InvokeForProcess(processInfo => { + foreach(Models.EventPipeProvider provider in configuration.Providers) + { + if (!CounterValidator.ValidateProviders(_counterOptions.CurrentValue, + provider, out string errorMessage)) + { + throw new InvalidOperationException(errorMessage); + } + } + TimeSpan duration = Utilities.ConvertSecondsToTimeSpan(durationSeconds); var traceConfiguration = Utilities.GetTraceConfiguration(configuration.Providers, configuration.RequestRundown, configuration.BufferSizeInMB); diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Metrics/EventCounterSettingsFactory.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Metrics/EventCounterSettingsFactory.cs index 33d0f71d56c..7029f5736da 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Metrics/EventCounterSettingsFactory.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Metrics/EventCounterSettingsFactory.cs @@ -17,26 +17,25 @@ namespace Microsoft.Diagnostics.Monitoring.WebApi /// internal static class EventCounterSettingsFactory { - public static EventPipeCounterPipelineSettings CreateSettings(bool includeDefaults, - int durationSeconds, - int refreshInterval) + public static EventPipeCounterPipelineSettings CreateSettings(GlobalCounterOptions counterOptions, bool includeDefaults, + int durationSeconds) { - return CreateSettings(includeDefaults, durationSeconds, refreshInterval, () => new List(0)); + return CreateSettings(includeDefaults, durationSeconds, counterOptions.IntervalSeconds, () => new List(0)); } - public static EventPipeCounterPipelineSettings CreateSettings(MetricsOptions options) + public static EventPipeCounterPipelineSettings CreateSettings(GlobalCounterOptions counterOptions, MetricsOptions options) { return CreateSettings(options.IncludeDefaultProviders.GetValueOrDefault(MetricsOptionsDefaults.IncludeDefaultProviders), - Timeout.Infinite, options.UpdateIntervalSeconds.GetValueOrDefault(MetricsOptionsDefaults.UpdateIntervalSeconds), + Timeout.Infinite, counterOptions.IntervalSeconds, () => ConvertCounterGroups(options.Providers)); } - public static EventPipeCounterPipelineSettings CreateSettings(int durationSeconds, int refreshInterval, + public static EventPipeCounterPipelineSettings CreateSettings(GlobalCounterOptions counterOptions, int durationSeconds, Models.EventMetricsConfiguration configuration) { return CreateSettings(configuration.IncludeDefaultProviders, durationSeconds, - refreshInterval, + counterOptions.IntervalSeconds, () => ConvertCounterGroups(configuration.Providers)); } diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Metrics/MetricsService.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Metrics/MetricsService.cs index 2b0a1245a5b..4a4de8651e3 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Metrics/MetricsService.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Metrics/MetricsService.cs @@ -23,14 +23,17 @@ internal sealed class MetricsService : BackgroundService private readonly IDiagnosticServices _services; private readonly MetricsStoreService _store; private IOptionsMonitor _optionsMonitor; + private IOptionsMonitor _counterOptions; public MetricsService(IDiagnosticServices services, IOptionsMonitor optionsMonitor, + IOptionsMonitor counterOptions, MetricsStoreService metricsStore) { _store = metricsStore; _services = services; _optionsMonitor = optionsMonitor; + _counterOptions = counterOptions; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) @@ -45,12 +48,13 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) var client = new DiagnosticsClient(pi.EndpointInfo.Endpoint); MetricsOptions options = _optionsMonitor.CurrentValue; + GlobalCounterOptions counterOptions = _counterOptions.CurrentValue; using var optionsTokenSource = new CancellationTokenSource(); //If metric options change, we need to cancel the existing metrics pipeline and restart with the new settings. using IDisposable monitorListener = _optionsMonitor.OnChange((_, _) => optionsTokenSource.SafeCancel()); - EventPipeCounterPipelineSettings counterSettings = EventCounterSettingsFactory.CreateSettings(options); + EventPipeCounterPipelineSettings counterSettings = EventCounterSettingsFactory.CreateSettings(counterOptions, options); _counterPipeline = new EventCounterPipeline(client, counterSettings, loggers: new[] { new MetricsLogger(_store.MetricsStore) }); diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Strings.Designer.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Strings.Designer.cs index 01f5e3350e2..250fd8afa02 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Strings.Designer.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Strings.Designer.cs @@ -87,6 +87,15 @@ internal static string ErrorMessage_InvalidMetricCount { } } + /// + /// Looks up a localized string similar to Custom trace metric provider '{0}' must use the global counter interval '{1}'. + /// + internal static string ErrorMessage_InvalidMetricInterval { + get { + return ResourceManager.GetString("ErrorMessage_InvalidMetricInterval", resourceCulture); + } + } + /// /// Looks up a localized string similar to Metrics was not enabled.. /// diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Strings.resx b/src/Microsoft.Diagnostics.Monitoring.WebApi/Strings.resx index b0ec3fede0e..5391d98f4b7 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Strings.resx +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Strings.resx @@ -129,6 +129,9 @@ Invalid metric count. Gets a string similar to "Invalid metric count.". + + Custom trace metric provider '{0}' must use the global counter interval '{1}' + Metrics was not enabled. Gets a string similar to "Metrics was not enabled.". diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Validation/CounterValidator.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Validation/CounterValidator.cs new file mode 100644 index 00000000000..4c97e796a9c --- /dev/null +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Validation/CounterValidator.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Diagnostics.Monitoring.WebApi.Models; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Globalization; + +namespace Microsoft.Diagnostics.Monitoring.WebApi.Validation +{ + internal static class CounterValidator + { + public static bool ValidateProviders(GlobalCounterOptions counterOptions, + EventPipeProvider provider, + out string errorMessage) + { + errorMessage = null; + + if (provider.Arguments.TryGetValue("EventCounterIntervalSec", out string intervalValue)) + { + if (int.TryParse(intervalValue, out int intervalSeconds) && + intervalSeconds != counterOptions.IntervalSeconds) + { + errorMessage = string.Format(CultureInfo.CurrentCulture, + Strings.ErrorMessage_InvalidMetricInterval, + provider.Name, + counterOptions.IntervalSeconds); + return false; + } + } + + return true; + } + } +} diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CollectionRuleOptionsTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CollectionRuleOptionsTests.cs index 823e3f0e846..354eb9e0cec 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CollectionRuleOptionsTests.cs +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CollectionRuleOptionsTests.cs @@ -146,7 +146,6 @@ public Task CollectionRuleOptions_EventCounterTrigger_RoundTrip() const double ExpectedGreaterThan = 0.5; const double ExpectedLessThan = 0.75; TimeSpan ExpectedDuration = TimeSpan.FromSeconds(30); - const int ExpectedFrequency = 5; return ValidateSuccess( rootOptions => @@ -159,7 +158,6 @@ public Task CollectionRuleOptions_EventCounterTrigger_RoundTrip() eventCounterOptions.GreaterThan = ExpectedGreaterThan; eventCounterOptions.LessThan = ExpectedLessThan; eventCounterOptions.SlidingWindowDuration = ExpectedDuration; - eventCounterOptions.Frequency = ExpectedFrequency; }, ruleOptions => { @@ -169,7 +167,6 @@ public Task CollectionRuleOptions_EventCounterTrigger_RoundTrip() Assert.Equal(ExpectedGreaterThan, eventCounterOptions.GreaterThan); Assert.Equal(ExpectedLessThan, eventCounterOptions.LessThan); Assert.Equal(ExpectedDuration, eventCounterOptions.SlidingWindowDuration); - Assert.Equal(ExpectedFrequency, eventCounterOptions.Frequency); }); } @@ -183,7 +180,6 @@ public Task CollectionRuleOptions_EventCounterTrigger_PropertyValidation() .SetEventCounterTrigger(out EventCounterOptions eventCounterOptions); eventCounterOptions.SlidingWindowDuration = TimeSpan.FromSeconds(-1); - eventCounterOptions.Frequency = -1; }, ex => { @@ -191,13 +187,11 @@ public Task CollectionRuleOptions_EventCounterTrigger_PropertyValidation() // Property validation failures will short-circuit the remainder of the validation // rules, thus only observe 4 errors when one might expect 5 (the fifth being that // either GreaterThan or LessThan should be specified). - Assert.Equal(4, failures.Length); + Assert.Equal(3, failures.Length); VerifyRequiredMessage(failures, 0, nameof(EventCounterOptions.ProviderName)); VerifyRequiredMessage(failures, 1, nameof(EventCounterOptions.CounterName)); VerifyRangeMessage(failures, 2, nameof(EventCounterOptions.SlidingWindowDuration), TriggerOptionsConstants.SlidingWindowDuration_MinValue, TriggerOptionsConstants.SlidingWindowDuration_MaxValue); - VerifyRangeMessage(failures, 3, nameof(EventCounterOptions.Frequency), - TriggerOptionsConstants.Frequency_MinValue_String, TriggerOptionsConstants.Frequency_MaxValue_String); }); } @@ -488,7 +482,6 @@ public Task CollectionRuleOptions_CollectTraceAction_RoundTrip_Profile() const TraceProfile ExpectedProfile = TraceProfile.Logs; const string ExpectedEgressProvider = "TmpEgressProvider"; TimeSpan ExpectedDuration = TimeSpan.FromSeconds(45); - const int ExpectedMetricsIntervalSeconds = 3; return ValidateSuccess( rootOptions => @@ -498,7 +491,6 @@ public Task CollectionRuleOptions_CollectTraceAction_RoundTrip_Profile() .AddCollectTraceAction(ExpectedProfile, ExpectedEgressProvider, out CollectTraceOptions collectTraceOptions); collectTraceOptions.Duration = ExpectedDuration; - collectTraceOptions.MetricsIntervalSeconds = ExpectedMetricsIntervalSeconds; rootOptions.AddFileSystemEgress(ExpectedEgressProvider, "/tmp"); }, @@ -507,7 +499,6 @@ public Task CollectionRuleOptions_CollectTraceAction_RoundTrip_Profile() CollectTraceOptions collectTraceOptions = ruleOptions.VerifyCollectTraceAction(0, ExpectedProfile, ExpectedEgressProvider); Assert.Equal(ExpectedDuration, collectTraceOptions.Duration); - Assert.Equal(ExpectedMetricsIntervalSeconds, collectTraceOptions.MetricsIntervalSeconds); }); } @@ -567,15 +558,12 @@ public Task CollectionRuleOptions_CollectTraceAction_PropertyValidation() collectTraceOptions.BufferSizeMegabytes = 2048; collectTraceOptions.Duration = TimeSpan.FromDays(7); - collectTraceOptions.MetricsIntervalSeconds = (int)TimeSpan.FromDays(3).TotalSeconds; }, ex => { string[] failures = ex.Failures.ToArray(); - Assert.Equal(5, failures.Length); + Assert.Equal(4, failures.Length); VerifyEnumDataTypeMessage(failures, 0, nameof(CollectTraceOptions.Profile)); - VerifyRangeMessage(failures, 1, nameof(CollectTraceOptions.MetricsIntervalSeconds), - ActionOptionsConstants.MetricsIntervalSeconds_MinValue_String, ActionOptionsConstants.MetricsIntervalSeconds_MaxValue_String); VerifyRangeMessage(failures, 2, nameof(CollectTraceOptions.BufferSizeMegabytes), ActionOptionsConstants.BufferSizeMegabytes_MinValue_String, ActionOptionsConstants.BufferSizeMegabytes_MaxValue_String); VerifyRangeMessage(failures, 3, nameof(CollectTraceOptions.Duration), diff --git a/src/Tools/dotnet-monitor/CollectionRules/Actions/CollectTraceAction.cs b/src/Tools/dotnet-monitor/CollectionRules/Actions/CollectTraceAction.cs index 98ec2b6ed07..b7ed37a9012 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/Actions/CollectTraceAction.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/Actions/CollectTraceAction.cs @@ -6,6 +6,8 @@ using Microsoft.Diagnostics.Monitoring.WebApi; using Microsoft.Diagnostics.Monitoring.WebApi.Models; using Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Options.Actions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -42,11 +44,13 @@ private sealed class CollectTraceAction : CollectionRuleActionBase { private readonly IServiceProvider _serviceProvider; + private readonly IOptionsMonitor _counterOptions; public CollectTraceAction(IServiceProvider serviceProvider, IEndpointInfo endpointInfo, CollectTraceOptions options) : base(endpointInfo, options) { _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + _counterOptions = _serviceProvider.GetRequiredService>(); } protected override async Task ExecuteCoreAsync( @@ -61,7 +65,7 @@ protected override async Task ExecuteCoreAsync( if (Options.Profile.HasValue) { TraceProfile profile = Options.Profile.Value; - int metricsIntervalSeconds = Options.MetricsIntervalSeconds.GetValueOrDefault(CollectTraceOptionsDefaults.MetricsIntervalSeconds); + int metricsIntervalSeconds = _counterOptions.CurrentValue.IntervalSeconds; configuration = Utils.GetTraceConfiguration(profile, metricsIntervalSeconds); } diff --git a/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/ActionOptionsConstants.cs b/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/ActionOptionsConstants.cs index e28e19de47c..41a4085c85e 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/ActionOptionsConstants.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/ActionOptionsConstants.cs @@ -16,10 +16,5 @@ internal static class ActionOptionsConstants public const string Duration_MaxValue = "1.00:00:00"; // 1 day public const string Duration_MinValue = "00:00:01"; // 1 second - - public const int MetricsIntervalSeconds_MaxValue = 24 * 60 * 60; // 1 day - public static readonly string MetricsIntervalSeconds_MaxValue_String = MetricsIntervalSeconds_MaxValue.ToString(CultureInfo.InvariantCulture); - public const int MetricsIntervalSeconds_MinValue = 1; // 1 second - public static readonly string MetricsIntervalSeconds_MinValue_String = MetricsIntervalSeconds_MinValue.ToString(CultureInfo.InvariantCulture); } } diff --git a/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptions.Validate.cs b/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptions.Validate.cs index b8bae3c05af..dfe84724ede 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptions.Validate.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptions.Validate.cs @@ -2,7 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.Diagnostics.Monitoring.WebApi; using Microsoft.Diagnostics.Monitoring.WebApi.Models; +using Microsoft.Diagnostics.Monitoring.WebApi.Validation; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; @@ -50,6 +54,13 @@ IEnumerable IValidatableObject.Validate(ValidationContext vali Validator.TryValidateObject(provider, providerContext, results, validateAllProperties: true); + IOptionsMonitor counterOptions = validationContext.GetRequiredService>(); + if (!CounterValidator.ValidateProviders(counterOptions.CurrentValue, + provider, out string errorMessage)) + { + results.Add(new ValidationResult(errorMessage, new[] { providerContext.MemberName })); + } + index++; } } diff --git a/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptions.cs b/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptions.cs index 06b5df605d1..b8fa7776a2e 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptions.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptions.cs @@ -24,13 +24,6 @@ internal sealed partial class CollectTraceOptions [EnumDataType(typeof(TraceProfile))] public TraceProfile? Profile { get; set; } - [Display( - ResourceType = typeof(OptionsDisplayStrings), - Description = nameof(OptionsDisplayStrings.DisplayAttributeDescription_CollectTraceOptions_MetricsIntervalSeconds))] - [Range(ActionOptionsConstants.MetricsIntervalSeconds_MinValue, ActionOptionsConstants.MetricsIntervalSeconds_MaxValue)] - [DefaultValue(CollectTraceOptionsDefaults.MetricsIntervalSeconds)] - public int? MetricsIntervalSeconds { get; set; } - [Display( ResourceType = typeof(OptionsDisplayStrings), Description = nameof(OptionsDisplayStrings.DisplayAttributeDescription_CollectTraceOptions_Providers))] diff --git a/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptionsDefaults.cs b/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptionsDefaults.cs index ad8e453a1cb..cac1393d229 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptionsDefaults.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptionsDefaults.cs @@ -6,7 +6,6 @@ namespace Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Options.Actions { internal static class CollectTraceOptionsDefaults { - public const int MetricsIntervalSeconds = 1; public const bool RequestRundown = true; public const int BufferSizeMegabytes = 256; public const string Duration = "00:00:30"; diff --git a/src/Tools/dotnet-monitor/CollectionRules/Options/Triggers/EventCounterOptions.cs b/src/Tools/dotnet-monitor/CollectionRules/Options/Triggers/EventCounterOptions.cs index 26f2d673f50..d8fe9ead8fb 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/Options/Triggers/EventCounterOptions.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/Options/Triggers/EventCounterOptions.cs @@ -42,12 +42,5 @@ internal sealed partial class EventCounterOptions [Range(typeof(TimeSpan), TriggerOptionsConstants.SlidingWindowDuration_MinValue, TriggerOptionsConstants.SlidingWindowDuration_MaxValue)] [DefaultValue(EventCounterOptionsDefaults.SlidingWindowDuration)] public TimeSpan? SlidingWindowDuration { get; set; } - - [Display( - ResourceType = typeof(OptionsDisplayStrings), - Description = nameof(OptionsDisplayStrings.DisplayAttributeDescription_EventCounterOptions_Frequency))] - [Range(TriggerOptionsConstants.CounterFrequency_MinValue, TriggerOptionsConstants.CounterFrequency_MaxValue)] - [DefaultValue(EventCounterOptionsDefaults.Frequency)] - public int? Frequency { get; set; } } } diff --git a/src/Tools/dotnet-monitor/CollectionRules/Options/Triggers/EventCounterOptionsDefaults.cs b/src/Tools/dotnet-monitor/CollectionRules/Options/Triggers/EventCounterOptionsDefaults.cs index fa0eab17e78..39c9bb7347b 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/Options/Triggers/EventCounterOptionsDefaults.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/Options/Triggers/EventCounterOptionsDefaults.cs @@ -7,6 +7,5 @@ namespace Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Options.Triggers internal static class EventCounterOptionsDefaults { public const string SlidingWindowDuration = TriggerOptionsConstants.SlidingWindowDuration_Default; - public const int Frequency = 5; } } diff --git a/src/Tools/dotnet-monitor/CollectionRules/Options/Triggers/TriggerOptionsConstants.cs b/src/Tools/dotnet-monitor/CollectionRules/Options/Triggers/TriggerOptionsConstants.cs index 57f52f90fd6..a18a084c9a0 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/Options/Triggers/TriggerOptionsConstants.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/Options/Triggers/TriggerOptionsConstants.cs @@ -9,11 +9,6 @@ namespace Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Options.Triggers // Constants for trigger options allowing reuse among multiple trigger and for tests to verify ranges. internal static class TriggerOptionsConstants { - public const int CounterFrequency_MaxValue = 24*60*60; // 1 day - public static readonly string Frequency_MaxValue_String = CounterFrequency_MaxValue.ToString(CultureInfo.InvariantCulture); - public const int CounterFrequency_MinValue = 1; // 1 second - public static readonly string Frequency_MinValue_String = CounterFrequency_MinValue.ToString(CultureInfo.InvariantCulture); - public const string SlidingWindowDuration_Default = "00:01:00"; // 1 minute public const string SlidingWindowDuration_MaxValue = "1.00:00:00"; // 1 day public const string SlidingWindowDuration_MinValue = "00:00:01"; // 1 second diff --git a/src/Tools/dotnet-monitor/CollectionRules/Triggers/EventCounterTriggerFactory.cs b/src/Tools/dotnet-monitor/CollectionRules/Triggers/EventCounterTriggerFactory.cs index 254e6185c3a..4e81090b1d6 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/Triggers/EventCounterTriggerFactory.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/Triggers/EventCounterTriggerFactory.cs @@ -6,6 +6,7 @@ using Microsoft.Diagnostics.Monitoring.EventPipe.Triggers.EventCounter; using Microsoft.Diagnostics.Monitoring.WebApi; using Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Options.Triggers; +using Microsoft.Extensions.Options; using System; namespace Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Triggers @@ -18,13 +19,16 @@ internal sealed class EventCounterTriggerFactory : { private readonly EventPipeTriggerFactory _eventPipeTriggerFactory; private readonly ITraceEventTriggerFactory _traceEventTriggerFactory; + private readonly IOptionsMonitor _counterOptions; public EventCounterTriggerFactory( + IOptionsMonitor counterOptions, EventPipeTriggerFactory eventPipeTriggerFactory, ITraceEventTriggerFactory traceEventTriggerFactory) { _eventPipeTriggerFactory = eventPipeTriggerFactory; _traceEventTriggerFactory = traceEventTriggerFactory; + _counterOptions = counterOptions; } /// @@ -33,7 +37,7 @@ public ICollectionRuleTrigger Create(IEndpointInfo endpointInfo, Action callback EventCounterTriggerSettings settings = new() { ProviderName = options.ProviderName, - CounterIntervalSeconds = options.Frequency.GetValueOrDefault(EventCounterOptionsDefaults.Frequency), + CounterIntervalSeconds = _counterOptions.CurrentValue.IntervalSeconds, CounterName = options.CounterName, GreaterThan = options.GreaterThan, LessThan = options.LessThan, diff --git a/src/Tools/dotnet-monitor/ConfigurationKeys.cs b/src/Tools/dotnet-monitor/ConfigurationKeys.cs index a88d17ac4e1..fafde96fb64 100644 --- a/src/Tools/dotnet-monitor/ConfigurationKeys.cs +++ b/src/Tools/dotnet-monitor/ConfigurationKeys.cs @@ -25,5 +25,7 @@ internal static class ConfigurationKeys public const string DefaultProcess = nameof(DefaultProcess); public const string Logging = nameof(Logging); + + public const string GlobalCounter = nameof(RootOptions.GlobalCounter); } } diff --git a/src/Tools/dotnet-monitor/DiagnosticsMonitorCommandHandler.cs b/src/Tools/dotnet-monitor/DiagnosticsMonitorCommandHandler.cs index 47ab6146248..7161af0f219 100644 --- a/src/Tools/dotnet-monitor/DiagnosticsMonitorCommandHandler.cs +++ b/src/Tools/dotnet-monitor/DiagnosticsMonitorCommandHandler.cs @@ -141,6 +141,7 @@ public static IHostBuilder CreateHostBuilder(IConsole console, string[] urls, st //Note these are in precedence order. ConfigureEndpointInfoSource(builder, diagnosticPort); ConfigureMetricsEndpoint(builder, metrics, metricUrls); + ConfigureGlobalMetrics(builder); builder.ConfigureStorageDefaults(); builder.AddCommandLine(new[] { "--urls", ConfigurationHelper.JoinValue(urls) }); @@ -236,6 +237,8 @@ public static IHostBuilder CreateHostBuilder(IConsole console, string[] urls, st services.Configure(context.Configuration.GetSection(ConfigurationKeys.DiagnosticPort)); services.AddSingleton, DiagnosticPortValidateOptions>(); + services.ConfigureGlobalCounter(context.Configuration); + services.AddSingleton(); services.AddHostedService(); services.AddSingleton(); @@ -313,12 +316,19 @@ private static void ConfigureMetricsEndpoint(IConfigurationBuilder builder, bool { {ConfigurationPath.Combine(ConfigurationKeys.Metrics, nameof(MetricsOptions.Endpoints)), string.Join(';', metricEndpoints)}, {ConfigurationPath.Combine(ConfigurationKeys.Metrics, nameof(MetricsOptions.Enabled)), enableMetrics.ToString()}, - {ConfigurationPath.Combine(ConfigurationKeys.Metrics, nameof(MetricsOptions.UpdateIntervalSeconds)), MetricsOptionsDefaults.UpdateIntervalSeconds.ToString()}, {ConfigurationPath.Combine(ConfigurationKeys.Metrics, nameof(MetricsOptions.MetricCount)), MetricsOptionsDefaults.MetricCount.ToString()}, {ConfigurationPath.Combine(ConfigurationKeys.Metrics, nameof(MetricsOptions.IncludeDefaultProviders)), MetricsOptionsDefaults.IncludeDefaultProviders.ToString()} }); } + private static void ConfigureGlobalMetrics(IConfigurationBuilder builder) + { + builder.AddInMemoryCollection(new Dictionary + { + {ConfigurationPath.Combine(ConfigurationKeys.GlobalCounter, nameof(GlobalCounterOptions.IntervalSeconds)), GlobalCounterOptionsDefaults.IntervalSeconds.ToString() } + }); + } + private static void ConfigureEndpointInfoSource(IConfigurationBuilder builder, string diagnosticPort) { DiagnosticPortConnectionMode connectionMode = GetConnectionMode(diagnosticPort); diff --git a/src/Tools/dotnet-monitor/RootOptions.cs b/src/Tools/dotnet-monitor/RootOptions.cs index ce0e4ecdb15..9df7506698d 100644 --- a/src/Tools/dotnet-monitor/RootOptions.cs +++ b/src/Tools/dotnet-monitor/RootOptions.cs @@ -15,6 +15,8 @@ internal partial class RootOptions public IDictionary CollectionRules { get; } = new Dictionary(0); + public GlobalCounterOptions GlobalCounter { get; set; } + public CorsConfigurationOptions CorsConfiguration { get; set; } public DiagnosticPortOptions DiagnosticPort { get; set; } diff --git a/src/Tools/dotnet-monitor/ServiceCollectionExtensions.cs b/src/Tools/dotnet-monitor/ServiceCollectionExtensions.cs index b6470ce6608..8f2fcca740b 100644 --- a/src/Tools/dotnet-monitor/ServiceCollectionExtensions.cs +++ b/src/Tools/dotnet-monitor/ServiceCollectionExtensions.cs @@ -30,6 +30,11 @@ namespace Microsoft.Diagnostics.Tools.Monitor { internal static class ServiceCollectionExtensions { + public static IServiceCollection ConfigureGlobalCounter(this IServiceCollection services, IConfiguration configuration) + { + return ConfigureOptions(services, configuration, ConfigurationKeys.GlobalCounter); + } + public static IServiceCollection ConfigureMetrics(this IServiceCollection services, IConfiguration configuration) { return ConfigureOptions(services, configuration, ConfigurationKeys.Metrics); From 37dbeba2c4a1e908b4ac28f67fc42ee716d0d977 Mon Sep 17 00:00:00 2001 From: Wiktor Kopec Date: Fri, 1 Oct 2021 16:45:20 -0700 Subject: [PATCH 2/6] Fix unit tests --- .../GlobalCounterOptions.cs | 8 +++++++- .../Controllers/DiagController.cs | 2 +- .../Metrics/EventCounterSettingsFactory.cs | 6 +++--- .../Validation/CounterValidator.cs | 6 +++--- .../CollectionRuleOptionsTests.cs | 6 +++--- .../TestHostHelper.cs | 1 + .../CollectionRules/Actions/CollectTraceAction.cs | 2 +- .../Triggers/EventCounterTriggerFactory.cs | 2 +- src/Tools/dotnet-monitor/ConfigurationJsonWriter.cs | 1 + 9 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.Diagnostics.Monitoring.Options/GlobalCounterOptions.cs b/src/Microsoft.Diagnostics.Monitoring.Options/GlobalCounterOptions.cs index 0feb8d7b946..de439eecaab 100644 --- a/src/Microsoft.Diagnostics.Monitoring.Options/GlobalCounterOptions.cs +++ b/src/Microsoft.Diagnostics.Monitoring.Options/GlobalCounterOptions.cs @@ -12,6 +12,12 @@ namespace Microsoft.Diagnostics.Monitoring.WebApi public class GlobalCounterOptions { [Range(1, 3600 * 24)] - public int IntervalSeconds { get; set; } + public int? IntervalSeconds { get; set; } + } + + internal static class GlobalCounterOptionsExtensions + { + public static int GetIntervalSeconds(this GlobalCounterOptions options) => + options.IntervalSeconds.GetValueOrDefault(GlobalCounterOptionsDefaults.IntervalSeconds); } } diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.cs index dbf3b99c7a9..8e8da7b67ff 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.cs @@ -313,7 +313,7 @@ public Task CaptureTrace( { TimeSpan duration = Utilities.ConvertSecondsToTimeSpan(durationSeconds); - var aggregateConfiguration = Utilities.GetTraceConfiguration(profile, _counterOptions.CurrentValue.IntervalSeconds); + var aggregateConfiguration = Utilities.GetTraceConfiguration(profile, _counterOptions.CurrentValue.GetIntervalSeconds()); return StartTrace(processInfo, aggregateConfiguration, duration, egressProvider); }, processKey, Utilities.ArtifactType_Trace); diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Metrics/EventCounterSettingsFactory.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Metrics/EventCounterSettingsFactory.cs index 7029f5736da..a9a340a927d 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Metrics/EventCounterSettingsFactory.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Metrics/EventCounterSettingsFactory.cs @@ -20,13 +20,13 @@ internal static class EventCounterSettingsFactory public static EventPipeCounterPipelineSettings CreateSettings(GlobalCounterOptions counterOptions, bool includeDefaults, int durationSeconds) { - return CreateSettings(includeDefaults, durationSeconds, counterOptions.IntervalSeconds, () => new List(0)); + return CreateSettings(includeDefaults, durationSeconds, counterOptions.GetIntervalSeconds(), () => new List(0)); } public static EventPipeCounterPipelineSettings CreateSettings(GlobalCounterOptions counterOptions, MetricsOptions options) { return CreateSettings(options.IncludeDefaultProviders.GetValueOrDefault(MetricsOptionsDefaults.IncludeDefaultProviders), - Timeout.Infinite, counterOptions.IntervalSeconds, + Timeout.Infinite, counterOptions.GetIntervalSeconds(), () => ConvertCounterGroups(options.Providers)); } @@ -35,7 +35,7 @@ public static EventPipeCounterPipelineSettings CreateSettings(GlobalCounterOptio { return CreateSettings(configuration.IncludeDefaultProviders, durationSeconds, - counterOptions.IntervalSeconds, + counterOptions.GetIntervalSeconds(), () => ConvertCounterGroups(configuration.Providers)); } diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Validation/CounterValidator.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Validation/CounterValidator.cs index 4c97e796a9c..c55830f11c2 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Validation/CounterValidator.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Validation/CounterValidator.cs @@ -18,15 +18,15 @@ public static bool ValidateProviders(GlobalCounterOptions counterOptions, { errorMessage = null; - if (provider.Arguments.TryGetValue("EventCounterIntervalSec", out string intervalValue)) + if (provider.Arguments?.TryGetValue("EventCounterIntervalSec", out string intervalValue) == true) { if (int.TryParse(intervalValue, out int intervalSeconds) && - intervalSeconds != counterOptions.IntervalSeconds) + intervalSeconds != counterOptions.GetIntervalSeconds()) { errorMessage = string.Format(CultureInfo.CurrentCulture, Strings.ErrorMessage_InvalidMetricInterval, provider.Name, - counterOptions.IntervalSeconds); + counterOptions.GetIntervalSeconds()); return false; } } diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CollectionRuleOptionsTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CollectionRuleOptionsTests.cs index 354eb9e0cec..c9f8bec016a 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CollectionRuleOptionsTests.cs +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CollectionRuleOptionsTests.cs @@ -564,11 +564,11 @@ public Task CollectionRuleOptions_CollectTraceAction_PropertyValidation() string[] failures = ex.Failures.ToArray(); Assert.Equal(4, failures.Length); VerifyEnumDataTypeMessage(failures, 0, nameof(CollectTraceOptions.Profile)); - VerifyRangeMessage(failures, 2, nameof(CollectTraceOptions.BufferSizeMegabytes), + VerifyRangeMessage(failures, 1, nameof(CollectTraceOptions.BufferSizeMegabytes), ActionOptionsConstants.BufferSizeMegabytes_MinValue_String, ActionOptionsConstants.BufferSizeMegabytes_MaxValue_String); - VerifyRangeMessage(failures, 3, nameof(CollectTraceOptions.Duration), + VerifyRangeMessage(failures, 2, nameof(CollectTraceOptions.Duration), ActionOptionsConstants.Duration_MinValue, ActionOptionsConstants.Duration_MaxValue); - VerifyRequiredMessage(failures, 4, nameof(CollectTraceOptions.Egress)); + VerifyRequiredMessage(failures, 3, nameof(CollectTraceOptions.Egress)); }); } diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/TestHostHelper.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/TestHostHelper.cs index 5f0323a9770..6090fc9c597 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/TestHostHelper.cs +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/TestHostHelper.cs @@ -77,6 +77,7 @@ public static IHost CreateHost( }) .ConfigureServices((HostBuilderContext context, IServiceCollection services) => { + services.ConfigureGlobalCounter(context.Configuration); services.ConfigureCollectionRules(); services.ConfigureEgress(); diff --git a/src/Tools/dotnet-monitor/CollectionRules/Actions/CollectTraceAction.cs b/src/Tools/dotnet-monitor/CollectionRules/Actions/CollectTraceAction.cs index b7ed37a9012..a0202931ed4 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/Actions/CollectTraceAction.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/Actions/CollectTraceAction.cs @@ -65,7 +65,7 @@ protected override async Task ExecuteCoreAsync( if (Options.Profile.HasValue) { TraceProfile profile = Options.Profile.Value; - int metricsIntervalSeconds = _counterOptions.CurrentValue.IntervalSeconds; + int metricsIntervalSeconds = _counterOptions.CurrentValue.GetIntervalSeconds(); configuration = Utils.GetTraceConfiguration(profile, metricsIntervalSeconds); } diff --git a/src/Tools/dotnet-monitor/CollectionRules/Triggers/EventCounterTriggerFactory.cs b/src/Tools/dotnet-monitor/CollectionRules/Triggers/EventCounterTriggerFactory.cs index 4e81090b1d6..306afa58881 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/Triggers/EventCounterTriggerFactory.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/Triggers/EventCounterTriggerFactory.cs @@ -37,7 +37,7 @@ public ICollectionRuleTrigger Create(IEndpointInfo endpointInfo, Action callback EventCounterTriggerSettings settings = new() { ProviderName = options.ProviderName, - CounterIntervalSeconds = _counterOptions.CurrentValue.IntervalSeconds, + CounterIntervalSeconds = _counterOptions.CurrentValue.GetIntervalSeconds(), CounterName = options.CounterName, GreaterThan = options.GreaterThan, LessThan = options.LessThan, diff --git a/src/Tools/dotnet-monitor/ConfigurationJsonWriter.cs b/src/Tools/dotnet-monitor/ConfigurationJsonWriter.cs index 9acea5f3a95..560a64f898c 100644 --- a/src/Tools/dotnet-monitor/ConfigurationJsonWriter.cs +++ b/src/Tools/dotnet-monitor/ConfigurationJsonWriter.cs @@ -59,6 +59,7 @@ public void Write(IConfiguration configuration, bool full) } //No sensitive information + ProcessChildSection(configuration, ConfigurationKeys.GlobalCounter, includeChildSections: true); ProcessChildSection(configuration, ConfigurationKeys.CollectionRules, includeChildSections: true); ProcessChildSection(configuration, ConfigurationKeys.CorsConfiguration, includeChildSections: true); ProcessChildSection(configuration, ConfigurationKeys.DiagnosticPort, includeChildSections: true); From 497a161d204f49bb2e0cabb5e22a03056731080b Mon Sep 17 00:00:00 2001 From: Wiktor Kopec Date: Fri, 1 Oct 2021 17:19:47 -0700 Subject: [PATCH 3/6] Pr feedback --- documentation/schema.json | 7 ++++++- .../GlobalCounterOptions.cs | 10 +++++++++- .../OptionsDisplayStrings.Designer.cs | 9 +++++++++ .../OptionsDisplayStrings.resx | 3 +++ .../Controllers/DiagController.cs | 2 +- .../Options/Actions/CollectTraceOptions.Validate.cs | 2 +- 6 files changed, 29 insertions(+), 4 deletions(-) diff --git a/documentation/schema.json b/documentation/schema.json index 1f16329c80c..7c1ae06a875 100644 --- a/documentation/schema.json +++ b/documentation/schema.json @@ -739,8 +739,13 @@ "additionalProperties": false, "properties": { "IntervalSeconds": { - "type": "integer", + "type": [ + "integer", + "null" + ], + "description": "Determines the metrics interval for all dotnet-monitor scenarios. This includes prometheus metrics, live metrics, triggers, and traces.", "format": "int32", + "default": 5, "maximum": 86400.0, "minimum": 1.0 } diff --git a/src/Microsoft.Diagnostics.Monitoring.Options/GlobalCounterOptions.cs b/src/Microsoft.Diagnostics.Monitoring.Options/GlobalCounterOptions.cs index de439eecaab..0de5d9d4a3b 100644 --- a/src/Microsoft.Diagnostics.Monitoring.Options/GlobalCounterOptions.cs +++ b/src/Microsoft.Diagnostics.Monitoring.Options/GlobalCounterOptions.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Text; @@ -11,7 +12,14 @@ namespace Microsoft.Diagnostics.Monitoring.WebApi { public class GlobalCounterOptions { - [Range(1, 3600 * 24)] + public const int IntervalMinSeconds = 1; + public const int IntervalMaxSeconds = 60 * 60 * 24; // One day + + [Display( + ResourceType = typeof(OptionsDisplayStrings), + Description = nameof(OptionsDisplayStrings.DisplayAttributeDescription_GlobalCounterOptions_IntervalSeconds))] + [Range(IntervalMinSeconds, IntervalMaxSeconds)] + [DefaultValue(GlobalCounterOptionsDefaults.IntervalSeconds)] public int? IntervalSeconds { get; set; } } diff --git a/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.Designer.cs b/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.Designer.cs index 4d98ef73a3d..2d5349556fd 100644 --- a/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.Designer.cs +++ b/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.Designer.cs @@ -747,6 +747,15 @@ public static string DisplayAttributeDescription_FileSystemEgressProviderOptions } } + /// + /// Looks up a localized string similar to Determines the metrics interval for all dotnet-monitor scenarios. This includes prometheus metrics, live metrics, triggers, and traces.. + /// + public static string DisplayAttributeDescription_GlobalCounterOptions_IntervalSeconds { + get { + return ResourceManager.GetString("DisplayAttributeDescription_GlobalCounterOptions_IntervalSeconds", resourceCulture); + } + } + /// /// Looks up a localized string similar to Gets or sets JsonWriterOptions.. /// diff --git a/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.resx b/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.resx index cd499f8fc62..a31374b9f94 100644 --- a/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.resx +++ b/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.resx @@ -544,4 +544,7 @@ Wait for the current action to complete before starting the next action. The description provided for the WaitForCompletion parameter on CollectionRuleActionOptions. + + Determines the metrics interval for all dotnet-monitor scenarios. This includes prometheus metrics, live metrics, triggers, and traces. + \ No newline at end of file diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.cs index 8e8da7b67ff..a605edd4a7c 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.cs @@ -360,7 +360,7 @@ public Task CaptureTraceCustom( if (!CounterValidator.ValidateProviders(_counterOptions.CurrentValue, provider, out string errorMessage)) { - throw new InvalidOperationException(errorMessage); + throw new ValidationException(errorMessage); } } diff --git a/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptions.Validate.cs b/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptions.Validate.cs index dfe84724ede..1a186961a40 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptions.Validate.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptions.Validate.cs @@ -58,7 +58,7 @@ IEnumerable IValidatableObject.Validate(ValidationContext vali if (!CounterValidator.ValidateProviders(counterOptions.CurrentValue, provider, out string errorMessage)) { - results.Add(new ValidationResult(errorMessage, new[] { providerContext.MemberName })); + results.Add(new ValidationResult(errorMessage, new[] { nameof(EventPipeProvider.Arguments) })); } index++; From 9ac77bc7688641f4889fbb61b4e8600141e41c68 Mon Sep 17 00:00:00 2001 From: Wiktor Kopec Date: Sat, 2 Oct 2021 22:18:34 -0700 Subject: [PATCH 4/6] Pr feedback --- .../Controllers/DiagController.cs | 2 +- .../Validation/CounterValidator.cs | 2 +- .../Options/Actions/CollectTraceOptions.Validate.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.cs index a605edd4a7c..aaa54817c92 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Controllers/DiagController.cs @@ -357,7 +357,7 @@ public Task CaptureTraceCustom( { foreach(Models.EventPipeProvider provider in configuration.Providers) { - if (!CounterValidator.ValidateProviders(_counterOptions.CurrentValue, + if (!CounterValidator.ValidateProvider(_counterOptions.CurrentValue, provider, out string errorMessage)) { throw new ValidationException(errorMessage); diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Validation/CounterValidator.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Validation/CounterValidator.cs index c55830f11c2..6408b08213d 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Validation/CounterValidator.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Validation/CounterValidator.cs @@ -12,7 +12,7 @@ namespace Microsoft.Diagnostics.Monitoring.WebApi.Validation { internal static class CounterValidator { - public static bool ValidateProviders(GlobalCounterOptions counterOptions, + public static bool ValidateProvider(GlobalCounterOptions counterOptions, EventPipeProvider provider, out string errorMessage) { diff --git a/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptions.Validate.cs b/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptions.Validate.cs index 1a186961a40..a8928b2f5d1 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptions.Validate.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/Options/Actions/CollectTraceOptions.Validate.cs @@ -55,7 +55,7 @@ IEnumerable IValidatableObject.Validate(ValidationContext vali Validator.TryValidateObject(provider, providerContext, results, validateAllProperties: true); IOptionsMonitor counterOptions = validationContext.GetRequiredService>(); - if (!CounterValidator.ValidateProviders(counterOptions.CurrentValue, + if (!CounterValidator.ValidateProvider(counterOptions.CurrentValue, provider, out string errorMessage)) { results.Add(new ValidationResult(errorMessage, new[] { nameof(EventPipeProvider.Arguments) })); From a5d6bfbe220d4fee30f4625874ab42966482170a Mon Sep 17 00:00:00 2001 From: Wiktor Kopec Date: Sun, 3 Oct 2021 01:06:50 -0700 Subject: [PATCH 5/6] Use float for interval parsing validation --- .../Validation/CounterValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Validation/CounterValidator.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Validation/CounterValidator.cs index 6408b08213d..8d3ad5373a9 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Validation/CounterValidator.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Validation/CounterValidator.cs @@ -20,7 +20,7 @@ public static bool ValidateProvider(GlobalCounterOptions counterOptions, if (provider.Arguments?.TryGetValue("EventCounterIntervalSec", out string intervalValue) == true) { - if (int.TryParse(intervalValue, out int intervalSeconds) && + if (float.TryParse(intervalValue, out float intervalSeconds) && intervalSeconds != counterOptions.GetIntervalSeconds()) { errorMessage = string.Format(CultureInfo.CurrentCulture, From b6e837f5d714fe58c6209a7fa8091d437afe0b39 Mon Sep 17 00:00:00 2001 From: Wiktor Kopec Date: Sun, 3 Oct 2021 12:00:46 -0700 Subject: [PATCH 6/6] CI Unit test fix attempt --- .../CollectionRuleOptionsTests.cs | 2 +- .../CollectionRulePipelineTests.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CollectionRuleOptionsTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CollectionRuleOptionsTests.cs index c9f8bec016a..2d47973ffd5 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CollectionRuleOptionsTests.cs +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CollectionRuleOptionsTests.cs @@ -185,7 +185,7 @@ public Task CollectionRuleOptions_EventCounterTrigger_PropertyValidation() { string[] failures = ex.Failures.ToArray(); // Property validation failures will short-circuit the remainder of the validation - // rules, thus only observe 4 errors when one might expect 5 (the fifth being that + // rules, thus only observe 3 errors when one might expect 4 (the fourth being that // either GreaterThan or LessThan should be specified). Assert.Equal(3, failures.Length); VerifyRequiredMessage(failures, 0, nameof(EventCounterOptions.ProviderName)); diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CollectionRulePipelineTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CollectionRulePipelineTests.cs index 33d00437635..acd402844a2 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CollectionRulePipelineTests.cs +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CollectionRulePipelineTests.cs @@ -258,7 +258,7 @@ public Task CollectionRulePipeline_ActionCountLimitUnlimitedDurationTest(TargetF public Task CollectionRulePipeline_ActionCountLimitSlidingDurationTest(TargetFrameworkMoniker appTfm) { const int ExpectedActionExecutionCount = 3; - TimeSpan SlidingWindowDuration = TimeSpan.FromSeconds(3); + TimeSpan SlidingWindowDuration = TimeSpan.FromSeconds(5); ManualTriggerService triggerService = new(); CallbackActionService callbackService = new(_outputHelper); @@ -284,7 +284,7 @@ public Task CollectionRulePipeline_ActionCountLimitSlidingDurationTest(TargetFra await startedTask.WithCancellation(cancellationSource.Token); - await ManualTriggerBurstAsync(triggerService); + await ManualTriggerBurstAsync(triggerService, count: 5); // Action list should have been executed the expected number of times VerifyExecutionCount(callbackService, ExpectedActionExecutionCount); @@ -292,7 +292,7 @@ public Task CollectionRulePipeline_ActionCountLimitSlidingDurationTest(TargetFra // Wait for existing execution times to fall out of sliding window. await Task.Delay(SlidingWindowDuration * 1.2); - await ManualTriggerBurstAsync(triggerService); + await ManualTriggerBurstAsync(triggerService, count: 5); // Expect total action invocation count to be twice the limit VerifyExecutionCount(callbackService, 2 * ExpectedActionExecutionCount); @@ -325,9 +325,9 @@ private void VerifyExecutionCount(CallbackActionService service, int expectedCou Assert.Equal(expectedCount, service.ExecutionTimestamps.Count); } - private async Task ManualTriggerBurstAsync(ManualTriggerService service) + private async Task ManualTriggerBurstAsync(ManualTriggerService service, int count = 10) { - for (int i = 0; i < 10; i++) + for (int i = 0; i < count; i++) { service.NotifySubscribers(); await Task.Delay(TimeSpan.FromMilliseconds(100));