From 56e2104d2fd11936816f879815cb3ec13353ef55 Mon Sep 17 00:00:00 2001 From: Alexey Rodionov Date: Thu, 22 Sep 2022 17:17:19 -0700 Subject: [PATCH 1/8] Target base scale support --- .../Executors/JobHostContextFactory.cs | 11 +- .../WebJobsServiceCollectionExtensions.cs | 1 + .../Listeners/HostListenerFactory.cs | 50 ++++- .../Scale/ITargetScaler.cs | 27 +++ .../Scale/ITargetScalerManager.cs | 30 +++ .../Scale/ITargetScalerProvider.cs | 24 +++ .../Scale/TargetScaleManager.cs | 44 ++++ .../Scale/TargetScaleStatusContext.cs | 25 +++ .../Scale/TargetScalerDescriptor.cs | 36 ++++ .../Scale/TargetScalerResult.cs | 16 ++ .../HostListenerFactoryTests.cs | 193 ++++++++++++++++-- .../PublicSurfaceTests.cs | 8 +- 12 files changed, 440 insertions(+), 25 deletions(-) create mode 100644 src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScaler.cs create mode 100644 src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerManager.cs create mode 100644 src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerProvider.cs create mode 100644 src/Microsoft.Azure.WebJobs.Host/Scale/TargetScaleManager.cs create mode 100644 src/Microsoft.Azure.WebJobs.Host/Scale/TargetScaleStatusContext.cs create mode 100644 src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs create mode 100644 src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerResult.cs diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/JobHostContextFactory.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/JobHostContextFactory.cs index 7af8b5084..22a03a5a2 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Executors/JobHostContextFactory.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/JobHostContextFactory.cs @@ -51,6 +51,8 @@ internal class JobHostContextFactory : IJobHostContextFactory private readonly IScaleMonitorManager _monitorManager; private readonly IDrainModeManager _drainModeManager; private readonly IApplicationLifetime _applicationLifetime; + private readonly IOptions _concurrencyOptions; + private readonly ITargetScalerManager _targetScalerManager; public JobHostContextFactory( IDashboardLoggingSetup dashboardLoggingSetup, @@ -73,7 +75,9 @@ public JobHostContextFactory( IAsyncCollector eventCollector, IScaleMonitorManager monitorManager, IDrainModeManager drainModeManager, - IApplicationLifetime applicationLifetime) + IApplicationLifetime applicationLifetime, + IOptions concurrencyOptions, + ITargetScalerManager targetScalerManager) { _dashboardLoggingSetup = dashboardLoggingSetup; _functionExecutor = functionExecutor; @@ -96,6 +100,8 @@ public JobHostContextFactory( _monitorManager = monitorManager; _drainModeManager = drainModeManager; _applicationLifetime = applicationLifetime; + _concurrencyOptions = concurrencyOptions; + _targetScalerManager = targetScalerManager; } public async Task Create(JobHost host, CancellationToken shutdownToken, CancellationToken cancellationToken) @@ -124,7 +130,8 @@ public async Task Create(JobHost host, CancellationToken shutdow // they are started). host.OnHostInitialized(); }; - IListenerFactory functionsListenerFactory = new HostListenerFactory(functions.ReadAll(), _singletonManager, _activator, _nameResolver, _loggerFactory, _monitorManager, listenersCreatedCallback, _jobHostOptions.Value.AllowPartialHostStartup, _drainModeManager); + IListenerFactory functionsListenerFactory = new HostListenerFactory(functions.ReadAll(), _singletonManager, _activator, _nameResolver, _loggerFactory, + _concurrencyOptions, _monitorManager, _targetScalerManager, listenersCreatedCallback, _jobHostOptions.Value.AllowPartialHostStartup, _drainModeManager); string hostId = await _hostIdProvider.GetHostIdAsync(cancellationToken); bool dashboardLoggingEnabled = _dashboardLoggingSetup.Setup(functions, functionsListenerFactory, out IFunctionExecutor hostCallExecutor, diff --git a/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsServiceCollectionExtensions.cs b/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsServiceCollectionExtensions.cs index 48eb866aa..68624900e 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsServiceCollectionExtensions.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsServiceCollectionExtensions.cs @@ -88,6 +88,7 @@ public static IWebJobsBuilder AddWebJobs(this IServiceCollection services, Actio services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Microsoft.Azure.WebJobs.Host/Listeners/HostListenerFactory.cs b/src/Microsoft.Azure.WebJobs.Host/Listeners/HostListenerFactory.cs index b3ec20a11..1ab99ecc2 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Listeners/HostListenerFactory.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Listeners/HostListenerFactory.cs @@ -14,6 +14,7 @@ using Microsoft.Azure.WebJobs.Logging; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Microsoft.Azure.WebJobs.Host.Listeners { @@ -29,12 +30,16 @@ internal class HostListenerFactory : IListenerFactory private readonly ILogger _logger; private readonly bool _allowPartialHostStartup; private readonly Action _listenersCreatedCallback; + private readonly IOptions _concurrencyOptions; private readonly IScaleMonitorManager _monitorManager; + private readonly ITargetScalerManager _targetScalerManager; private readonly IDrainModeManager _drainModeManager; public HostListenerFactory(IEnumerable functionDefinitions, SingletonManager singletonManager, IJobActivator activator, - INameResolver nameResolver, ILoggerFactory loggerFactory, IScaleMonitorManager monitorManager, Action listenersCreatedCallback, bool allowPartialHostStartup = false, IDrainModeManager drainModeManager = null) + INameResolver nameResolver, ILoggerFactory loggerFactory, IOptions concurrencyOptions, + IScaleMonitorManager monitorManager, ITargetScalerManager targetScalerManager, Action listenersCreatedCallback, + bool allowPartialHostStartup = false, IDrainModeManager drainModeManager = null) { _functionDefinitions = functionDefinitions; _singletonManager = singletonManager; @@ -43,7 +48,9 @@ public HostListenerFactory(IEnumerable functionDefinitions, _loggerFactory = loggerFactory; _logger = _loggerFactory?.CreateLogger(LogCategories.Startup); _allowPartialHostStartup = allowPartialHostStartup; + _concurrencyOptions = concurrencyOptions; _monitorManager = monitorManager; + _targetScalerManager = targetScalerManager; _listenersCreatedCallback = listenersCreatedCallback; _drainModeManager = drainModeManager; } @@ -70,7 +77,18 @@ public async Task CreateAsync(CancellationToken cancellationToken) IListener listener = await listenerFactory.CreateAsync(cancellationToken); - RegisterScaleMonitor(listener, _monitorManager); + if (_concurrencyOptions != null && _concurrencyOptions.Value != null && _concurrencyOptions.Value.DynamicConcurrencyEnabled) + { + if (!RegisterTargetScaler(listener, _targetScalerManager)) + { + // if an extension does not implement ITargetScaler try to register IScaleMonitor + RegisterScaleMonitor(listener, _monitorManager); + } + } + else + { + RegisterScaleMonitor(listener, _monitorManager); + } // if the listener is a Singleton, wrap it with our SingletonListener SingletonAttribute singletonAttribute = SingletonManager.GetListenerSingletonOrNull(listener.GetType(), functionDefinition.Descriptor); @@ -123,6 +141,34 @@ internal static void RegisterScaleMonitor(IListener listener, IScaleMonitorManag } } + internal static bool RegisterTargetScaler(IListener listener, ITargetScalerManager targetScalerManager) + { + bool result = false; + if (listener is ITargetScaler targetScaler) + { + targetScalerManager.Register(targetScaler); + result = true; + } + else if (listener is ITargetScalerProvider targetScalerProvider) + { + var scaler = ((ITargetScalerProvider)listener).GetTargetScaler(); + targetScalerManager.Register(scaler); + result = true; + } + else if (listener is IEnumerable) + { + // for composite listeners, we need to check all the inner listeners + foreach (var innerListener in ((IEnumerable)listener)) + { + if (RegisterTargetScaler(innerListener, targetScalerManager)) + { + result = true; + } + } + } + return result; + } + internal static bool IsDisabled(MethodInfo method, INameResolver nameResolver, IJobActivator activator, IConfiguration configuration) { // First try to resolve disabled state by setting diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScaler.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScaler.cs new file mode 100644 index 000000000..c548118dd --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScaler.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading.Tasks; + +namespace Microsoft.Azure.WebJobs.Host.Scale +{ + /// + /// Interface defining a target scaler that can participate in Azure Functions scaling decisions by + /// taking current metrics, and scaling based on those metrics. + /// + public interface ITargetScaler + { + /// + /// Returns the for this target scaler. + /// + TargetScalerDescriptor TargetScalerDescriptor { get; } + + /// + /// Return the current scale result based on the specified context. + /// + /// The to use to determine + /// the scale result. + /// The scale result. + Task GetScaleResultAsync(TargetScaleStatusContext context); + } +} diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerManager.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerManager.cs new file mode 100644 index 000000000..6085cbea0 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerManager.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.Azure.WebJobs.Host.Scale +{ + /// + /// Manager for registering and accessing instances for + /// a instance. + /// + public interface ITargetScalerManager + { + /// + /// Register an instance. + /// + /// The monitor instance to register. + void Register(ITargetScaler monitor); + + /// + /// Get all registered target scale instances. + /// + /// + /// Should only be called after the host has been started and all + /// instances are registered. + /// + /// The collection of target scaler intances. + IEnumerable GetTargetScalers(); + } +} diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerProvider.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerProvider.cs new file mode 100644 index 000000000..14d2e06f0 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerProvider.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Azure.WebJobs.Host.Scale +{ + /// + /// Provider interface for returning an instance. + /// + /// + /// Listeners can implement directly, but in some + /// cases the decoupling afforded by this interface is needed. + /// + public interface ITargetScalerProvider + { + /// + /// Gets the instance. + /// + /// The instance. + ITargetScaler GetTargetScaler(); + } +} diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScaleManager.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScaleManager.cs new file mode 100644 index 000000000..35ea88062 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScaleManager.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.Azure.WebJobs.Host.Scale +{ + internal class TargetScalerManager : ITargetScalerManager + { + private readonly List _targetScalers = new List(); + private object _syncRoot = new object(); + + public TargetScalerManager() + { + } + + public TargetScalerManager(IEnumerable targetScalers) + { + // add any initial target scalers coming from DI + // additional monitors can be added at runtime + // via Register + _targetScalers.AddRange(targetScalers); + } + + public void Register(ITargetScaler targetScaler) + { + lock (_syncRoot) + { + if (!_targetScalers.Contains(targetScaler)) + { + _targetScalers.Add(targetScaler); + } + } + } + + public IEnumerable GetTargetScalers() + { + lock (_syncRoot) + { + return _targetScalers.AsReadOnly(); + } + } + } +} diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScaleStatusContext.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScaleStatusContext.cs new file mode 100644 index 000000000..89a31a51f --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScaleStatusContext.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.Azure.WebJobs.Host.Scale +{ + /// + /// Context used by to decide + /// scale result. + /// + public class TargetScaleStatusContext + { + /// + /// The current worker dyanimc worker concurrency. + /// + /// + /// Value is reolved in the implementation if null. + /// + public int? InstanceConcurrency { get; set; } + + /// + /// The current worker count for the host application. + /// + public int WorkerCount { get; set; } + } +} diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs new file mode 100644 index 000000000..a85646475 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.Azure.WebJobs.Host.Scale +{ + + /// + /// Metadata descriptor for an . + /// + public class TargetScalerDescriptor + { + public TargetScalerDescriptor(string id, string functionId) + { + Id = id; + FunctionId = functionId; + } + + /// + /// Gets the unique ID for the monitor. + /// + /// + /// This should be constant. It is used to correlate persisted metrics samples + /// with their corresponding monitor instance. E.g. for a QueueTrigger, this might + /// be of the form "{FunctionId}-QueueTrigger-{QueueName}". + /// + public string Id { get; } + + /// + /// Gets the unique function id. + /// + /// + /// This should be constant. It is used to get persisted dynamic concurrency value. + /// + public string FunctionId { get; } + } +} diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerResult.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerResult.cs new file mode 100644 index 000000000..844c8f7d2 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerResult.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.Azure.WebJobs.Host.Scale +{ + /// + /// Represents result for target base scale. + /// + public class TargetScalerResult + { + /// + /// Gets or sets the target worker count. + /// + public int WorkerCountDifference { get; set; } + } +} diff --git a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs index 1f08d3b5b..a9923f1a8 100644 --- a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs +++ b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs @@ -16,6 +16,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Moq; using Xunit; @@ -37,36 +38,67 @@ public HostListenerFactoryTests() DisableProvider_Instance.Method = null; } - [Fact] - public async Task CreateAsync_RegistersScaleMonitors() + [Theory] + [InlineData(false, typeof(TestListener_Monitor), typeof(TestListener_Monitor), 2, 0)] + [InlineData(false, typeof(TestListener_Monitor), typeof(TestListener_TargetScaler), 1, 0)] + [InlineData(false, typeof(TestListener_Monitor), typeof(TestListener_TargetScalerProvider), 1, 0)] + [InlineData(false, typeof(TestListener_TargetScaler), typeof(TestListener_TargetScalerProvider), 0, 0)] + [InlineData(false, typeof(TestListener_Monitor), typeof(TestListener_MonitorAndTargetScaler), 2, 0)] + [InlineData(true, typeof(TestListener_Monitor), typeof(TestListener_Monitor), 2, 0)] + [InlineData(true, typeof(TestListener_Monitor), typeof(TestListener_TargetScaler), 1, 1)] + [InlineData(true, typeof(TestListener_Monitor), typeof(TestListener_TargetScalerProvider), 1, 1)] + [InlineData(true, typeof(TestListener_TargetScaler), typeof(TestListener_TargetScalerProvider), 0, 2)] + [InlineData(true, typeof(TestListener_Monitor), typeof(TestListener_MonitorAndTargetScaler), 1, 1)] + public async Task CreateAsync_RegistersScaleMonitorsAndTargetScaler(bool dynamicConcurrencyEnabled, + Type listenerType1, Type listenerType2, + int expectedMonitors, int expectedTargetScalers) { - Mock mockFunctionDefinition = new Mock(); - Mock mockInstanceFactory = new Mock(MockBehavior.Strict); - Mock mockListenerFactory = new Mock(MockBehavior.Strict); - var testListener = new TestListener_Monitor(); - mockListenerFactory.Setup(p => p.CreateAsync(It.IsAny())).ReturnsAsync(testListener); - SingletonManager singletonManager = new SingletonManager(); - ILoggerFactory loggerFactory = new LoggerFactory(); TestLoggerProvider loggerProvider = new TestLoggerProvider(); loggerFactory.AddProvider(loggerProvider); + // Adding a ScaleMonitor + Mock mockFunctionDefinition1 = new Mock(); + Mock mockInstanceFactory1 = new Mock(MockBehavior.Strict); + Mock mockListenerFactory1 = new Mock(MockBehavior.Strict); + var testListener1 = (IListener)Activator.CreateInstance(listenerType1); + mockListenerFactory1.Setup(p => p.CreateAsync(It.IsAny())).ReturnsAsync(testListener1); + var method1 = typeof(Functions1).GetMethod("TestJob", BindingFlags.Public | BindingFlags.Static); + FunctionDescriptor descriptor1 = FunctionIndexer.FromMethod(method1, _configuration, _jobActivator); + FunctionDefinition definition1 = new FunctionDefinition(descriptor1, mockInstanceFactory1.Object, mockListenerFactory1.Object); + + // Adding a TargetScaler + Mock mockFunctionDefinition2 = new Mock(); + Mock mockInstanceFactory2 = new Mock(MockBehavior.Strict); + Mock mockListenerFactory2 = new Mock(MockBehavior.Strict); + var testListener2 = (IListener)Activator.CreateInstance(listenerType2); + var testListererTargetScaler = new TestListener_TargetScaler(); + mockListenerFactory2.Setup(p => p.CreateAsync(It.IsAny())).ReturnsAsync(testListener2); + var method2 = typeof(Functions1).GetMethod("TestJob", BindingFlags.Public | BindingFlags.Static); + FunctionDescriptor descriptor2 = FunctionIndexer.FromMethod(method2, _configuration, _jobActivator); + FunctionDefinition definition2 = new FunctionDefinition(descriptor2, mockInstanceFactory2.Object, mockListenerFactory2.Object); + List functions = new List(); - var method = typeof(Functions1).GetMethod("TestJob", BindingFlags.Public | BindingFlags.Static); - FunctionDescriptor descriptor = FunctionIndexer.FromMethod(method, _configuration, _jobActivator); - FunctionDefinition definition = new FunctionDefinition(descriptor, mockInstanceFactory.Object, mockListenerFactory.Object); - functions.Add(definition); + functions.Add(definition1); + functions.Add(definition2); var monitorManager = new ScaleMonitorManager(); + var targetScalerManager = new TargetScalerManager(); var drainModeManagerMock = new Mock(); - HostListenerFactory factory = new HostListenerFactory(functions, singletonManager, _jobActivator, null, loggerFactory, monitorManager, () => { }, false, drainModeManagerMock.Object); + IOptions concurrenyOptions = Options.Create(new ConcurrencyOptions + { + DynamicConcurrencyEnabled = dynamicConcurrencyEnabled + }); + SingletonManager singletonManager = new SingletonManager(); + HostListenerFactory factory = new HostListenerFactory(functions, singletonManager, _jobActivator, null, loggerFactory, concurrenyOptions, monitorManager, targetScalerManager, () => { }, false, drainModeManagerMock.Object); IListener listener = await factory.CreateAsync(CancellationToken.None); - var innerListeners = ((IEnumerable)listener).ToArray(); - var monitors = monitorManager.GetMonitors().ToArray(); - Assert.Single(monitors); - Assert.Same(testListener, monitors[0]); + var taretScalers = targetScalerManager.GetTargetScalers().ToArray(); + + Assert.Equal(monitors.Count(), expectedMonitors); + + Assert.Equal(taretScalers.Count(), expectedTargetScalers); } [Fact] @@ -98,6 +130,35 @@ public void RegisterScaleMonitor_Succeeds() Assert.Same(testListenerMonitorProvider.GetMonitor(), monitors[1]); } + [Fact] + public void RegisterTargetScaler_Succeeds() + { + // listener is a direct target scaler + var manager = new TargetScalerManager(); + var testListener = new TestListener_TargetScaler(); + HostListenerFactory.RegisterTargetScaler(testListener, manager); + var targetScalers = manager.GetTargetScalers().ToArray(); + Assert.Single(targetScalers); + Assert.Same(testListener, targetScalers[0]); + + // listener is a target scaler provider + manager = new TargetScalerManager(); + var providerListener = new TestListener_TargetScalerProvider(); + HostListenerFactory.RegisterTargetScaler(providerListener, manager); + targetScalers = manager.GetTargetScalers().ToArray(); + Assert.Single(targetScalers); + Assert.Same(providerListener.GetTargetScaler(), targetScalers[0]); + + // listener is composite, so we expect recursion + manager = new TargetScalerManager(); + var compositListener = new CompositeListener(testListener, providerListener); + HostListenerFactory.RegisterTargetScaler(compositListener, manager); + targetScalers = manager.GetTargetScalers().ToArray(); + Assert.Equal(2, targetScalers.Length); + Assert.Same(testListener, targetScalers[0]); + Assert.Same(providerListener.GetTargetScaler(), targetScalers[1]); + } + [Theory] [InlineData(typeof(Functions1), "DisabledAtParameterLevel")] [InlineData(typeof(Functions1), "DisabledAtMethodLevel")] @@ -138,8 +199,10 @@ public async Task CreateAsync_SkipsDisabledFunctions(Type jobType, string method // Create the composite listener - this will fail if any of the // function definitions indicate that they are not disabled var monitorManagerMock = new Mock(MockBehavior.Strict); + var targetScalerManagerMock = new Mock(MockBehavior.Strict); + var concurrencyOptionsMock = new Mock>(MockBehavior.Strict); var drainModeManagerMock = new Mock(); - HostListenerFactory factory = new HostListenerFactory(functions, singletonManager, _jobActivator, null, loggerFactory, monitorManagerMock.Object, () => { }, false, drainModeManagerMock.Object); + HostListenerFactory factory = new HostListenerFactory(functions, singletonManager, _jobActivator, null, loggerFactory, concurrencyOptionsMock.Object, monitorManagerMock.Object, targetScalerManagerMock.Object, () => { }, false, drainModeManagerMock.Object); IListener listener = await factory.CreateAsync(CancellationToken.None); @@ -196,7 +259,7 @@ public void IsDisabledBySetting_BindsSettingName(string settingName, bool disabl { "Disable_TestJob", "False" }, }) .Build(); - + Mock mockNameResolver = new Mock(MockBehavior.Strict); mockNameResolver.Setup(p => p.Resolve("Test")).Returns("TestValue"); @@ -405,5 +468,95 @@ public ScaleStatus GetScaleStatus(ScaleStatusContext context) } } } + + public class TestListener_TargetScaler : IListener, ITargetScaler + { + public TargetScalerDescriptor TargetScalerDescriptor => throw new NotImplementedException(); + + public void Cancel() + { + throw new NotImplementedException(); + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + public Task GetScaleResultAsync(TargetScaleStatusContext context) + { + throw new NotImplementedException(); + } + + public Task StartAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } + + public class TestListener_TargetScalerProvider : IListener, ITargetScalerProvider + { + private readonly ITargetScaler _targetScaler; + + public TestListener_TargetScalerProvider() + { + _targetScaler = new TestListener_TargetScalerImpl(); + } + + public void Cancel() + { + throw new NotImplementedException(); + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + public ITargetScaler GetTargetScaler() + { + return _targetScaler; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public class TestListener_TargetScalerImpl : ITargetScaler + { + public TargetScalerDescriptor TargetScalerDescriptor => throw new NotImplementedException(); + + public Task GetMetricsAsync() + { + throw new NotImplementedException(); + } + + public Task GetScaleResultAsync(TargetScaleStatusContext context) + { + throw new NotImplementedException(); + } + } + } + + public class TestListener_MonitorAndTargetScaler : TestListener_Monitor, ITargetScaler + { + public TargetScalerDescriptor TargetScalerDescriptor => throw new NotImplementedException(); + + public Task GetScaleResultAsync(TargetScaleStatusContext context) + { + throw new NotImplementedException(); + } + } } } diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs index 27021688d..89351f2e7 100644 --- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs +++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs @@ -297,7 +297,13 @@ public void WebJobs_Host_VerifyPublicSurfaceArea() "FunctionActivityStatus", "IFunctionActivityStatusProvider", "SupportsRetryAttribute", - "AppServicesHostingUtility" + "AppServicesHostingUtility", + "ITargetScaler", + "ITargetScalerManager", + "ITargetScalerProvider", + "TargetScalerDescriptor", + "TargetScalerResult", + "TargetScaleStatusContext" }; TestHelpers.AssertPublicTypes(expected, assembly); From 23cb0eec8fd751f67cc4c34a01ae358c70f471a8 Mon Sep 17 00:00:00 2001 From: Alexey Rodionov Date: Fri, 23 Sep 2022 11:52:39 -0700 Subject: [PATCH 2/8] Regsiter only if TARGET_BASED_SCALING_ENABLED=true --- .../Executors/JobHostContextFactory.cs | 2 +- .../Listeners/HostListenerFactory.cs | 18 ++++++------ .../Scale/ITargetScaler.cs | 4 +-- ...ScaleManager.cs => TargetScalerManager.cs} | 0 ...ontext.cs => TargetScalerStatusContext.cs} | 4 +-- .../HostListenerFactoryTests.cs | 28 ++++++++++--------- .../PublicSurfaceTests.cs | 2 +- 7 files changed, 29 insertions(+), 29 deletions(-) rename src/Microsoft.Azure.WebJobs.Host/Scale/{TargetScaleManager.cs => TargetScalerManager.cs} (100%) rename src/Microsoft.Azure.WebJobs.Host/Scale/{TargetScaleStatusContext.cs => TargetScalerStatusContext.cs} (89%) diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/JobHostContextFactory.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/JobHostContextFactory.cs index 22a03a5a2..a55180ae8 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Executors/JobHostContextFactory.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/JobHostContextFactory.cs @@ -131,7 +131,7 @@ public async Task Create(JobHost host, CancellationToken shutdow host.OnHostInitialized(); }; IListenerFactory functionsListenerFactory = new HostListenerFactory(functions.ReadAll(), _singletonManager, _activator, _nameResolver, _loggerFactory, - _concurrencyOptions, _monitorManager, _targetScalerManager, listenersCreatedCallback, _jobHostOptions.Value.AllowPartialHostStartup, _drainModeManager); + _monitorManager, _targetScalerManager, listenersCreatedCallback, _jobHostOptions.Value.AllowPartialHostStartup, _drainModeManager); string hostId = await _hostIdProvider.GetHostIdAsync(cancellationToken); bool dashboardLoggingEnabled = _dashboardLoggingSetup.Setup(functions, functionsListenerFactory, out IFunctionExecutor hostCallExecutor, diff --git a/src/Microsoft.Azure.WebJobs.Host/Listeners/HostListenerFactory.cs b/src/Microsoft.Azure.WebJobs.Host/Listeners/HostListenerFactory.cs index 1ab99ecc2..e618125a1 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Listeners/HostListenerFactory.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Listeners/HostListenerFactory.cs @@ -22,6 +22,7 @@ internal class HostListenerFactory : IListenerFactory { private static readonly MethodInfo JobActivatorCreateMethod = typeof(IJobActivator).GetMethod("CreateInstance", BindingFlags.Public | BindingFlags.Instance).GetGenericMethodDefinition(); private const string IsDisabledFunctionName = "IsDisabled"; + private const string TargetBaseScalingEnabled = "TARGET_BASED_SCALING_ENABLED"; private readonly IEnumerable _functionDefinitions; private readonly SingletonManager _singletonManager; private readonly IJobActivator _activator; @@ -30,16 +31,13 @@ internal class HostListenerFactory : IListenerFactory private readonly ILogger _logger; private readonly bool _allowPartialHostStartup; private readonly Action _listenersCreatedCallback; - private readonly IOptions _concurrencyOptions; private readonly IScaleMonitorManager _monitorManager; private readonly ITargetScalerManager _targetScalerManager; private readonly IDrainModeManager _drainModeManager; public HostListenerFactory(IEnumerable functionDefinitions, SingletonManager singletonManager, IJobActivator activator, - INameResolver nameResolver, ILoggerFactory loggerFactory, IOptions concurrencyOptions, - IScaleMonitorManager monitorManager, ITargetScalerManager targetScalerManager, Action listenersCreatedCallback, - bool allowPartialHostStartup = false, IDrainModeManager drainModeManager = null) + INameResolver nameResolver, ILoggerFactory loggerFactory, IScaleMonitorManager monitorManager, ITargetScalerManager targetScalerManager, Action listenersCreatedCallback, bool allowPartialHostStartup = false, IDrainModeManager drainModeManager = null) { _functionDefinitions = functionDefinitions; _singletonManager = singletonManager; @@ -48,7 +46,6 @@ public HostListenerFactory(IEnumerable functionDefinitions, _loggerFactory = loggerFactory; _logger = _loggerFactory?.CreateLogger(LogCategories.Startup); _allowPartialHostStartup = allowPartialHostStartup; - _concurrencyOptions = concurrencyOptions; _monitorManager = monitorManager; _targetScalerManager = targetScalerManager; _listenersCreatedCallback = listenersCreatedCallback; @@ -77,15 +74,16 @@ public async Task CreateAsync(CancellationToken cancellationToken) IListener listener = await listenerFactory.CreateAsync(cancellationToken); - if (_concurrencyOptions != null && _concurrencyOptions.Value != null && _concurrencyOptions.Value.DynamicConcurrencyEnabled) + bool targetScalerRegistered = false; + if (bool.TryParse(Environment.GetEnvironmentVariable(TargetBaseScalingEnabled), out bool isTargetBaseScalingEnabled)) { - if (!RegisterTargetScaler(listener, _targetScalerManager)) + if (isTargetBaseScalingEnabled) { - // if an extension does not implement ITargetScaler try to register IScaleMonitor - RegisterScaleMonitor(listener, _monitorManager); + targetScalerRegistered = RegisterTargetScaler(listener, _targetScalerManager); } } - else + + if (!targetScalerRegistered) { RegisterScaleMonitor(listener, _monitorManager); } diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScaler.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScaler.cs index c548118dd..c4ce96f3f 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScaler.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScaler.cs @@ -19,9 +19,9 @@ public interface ITargetScaler /// /// Return the current scale result based on the specified context. /// - /// The to use to determine + /// The to use to determine /// the scale result. /// The scale result. - Task GetScaleResultAsync(TargetScaleStatusContext context); + Task GetScaleResultAsync(TargetScalerStatusContext context); } } diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScaleManager.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerManager.cs similarity index 100% rename from src/Microsoft.Azure.WebJobs.Host/Scale/TargetScaleManager.cs rename to src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerManager.cs diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScaleStatusContext.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerStatusContext.cs similarity index 89% rename from src/Microsoft.Azure.WebJobs.Host/Scale/TargetScaleStatusContext.cs rename to src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerStatusContext.cs index 89a31a51f..a9c7a842b 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScaleStatusContext.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerStatusContext.cs @@ -4,10 +4,10 @@ namespace Microsoft.Azure.WebJobs.Host.Scale { /// - /// Context used by to decide + /// Context used by to decide /// scale result. /// - public class TargetScaleStatusContext + public class TargetScalerStatusContext { /// /// The current worker dyanimc worker concurrency. diff --git a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs index a9923f1a8..183a4a56f 100644 --- a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs +++ b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs @@ -49,15 +49,20 @@ public HostListenerFactoryTests() [InlineData(true, typeof(TestListener_Monitor), typeof(TestListener_TargetScalerProvider), 1, 1)] [InlineData(true, typeof(TestListener_TargetScaler), typeof(TestListener_TargetScalerProvider), 0, 2)] [InlineData(true, typeof(TestListener_Monitor), typeof(TestListener_MonitorAndTargetScaler), 1, 1)] - public async Task CreateAsync_RegistersScaleMonitorsAndTargetScaler(bool dynamicConcurrencyEnabled, + public async Task CreateAsync_RegistersScaleMonitorsAndTargetScaler(bool tbsEnabled, Type listenerType1, Type listenerType2, int expectedMonitors, int expectedTargetScalers) { + if (tbsEnabled) + { + Environment.SetEnvironmentVariable("TARGET_BASED_SCALING_ENABLED", tbsEnabled.ToString()); + } + ILoggerFactory loggerFactory = new LoggerFactory(); TestLoggerProvider loggerProvider = new TestLoggerProvider(); loggerFactory.AddProvider(loggerProvider); - // Adding a ScaleMonitor + // Adding a testListener1 Mock mockFunctionDefinition1 = new Mock(); Mock mockInstanceFactory1 = new Mock(MockBehavior.Strict); Mock mockListenerFactory1 = new Mock(MockBehavior.Strict); @@ -67,7 +72,7 @@ public async Task CreateAsync_RegistersScaleMonitorsAndTargetScaler(bool dynamic FunctionDescriptor descriptor1 = FunctionIndexer.FromMethod(method1, _configuration, _jobActivator); FunctionDefinition definition1 = new FunctionDefinition(descriptor1, mockInstanceFactory1.Object, mockListenerFactory1.Object); - // Adding a TargetScaler + // Adding a testListener2 Mock mockFunctionDefinition2 = new Mock(); Mock mockInstanceFactory2 = new Mock(MockBehavior.Strict); Mock mockListenerFactory2 = new Mock(MockBehavior.Strict); @@ -85,12 +90,8 @@ public async Task CreateAsync_RegistersScaleMonitorsAndTargetScaler(bool dynamic var monitorManager = new ScaleMonitorManager(); var targetScalerManager = new TargetScalerManager(); var drainModeManagerMock = new Mock(); - IOptions concurrenyOptions = Options.Create(new ConcurrencyOptions - { - DynamicConcurrencyEnabled = dynamicConcurrencyEnabled - }); SingletonManager singletonManager = new SingletonManager(); - HostListenerFactory factory = new HostListenerFactory(functions, singletonManager, _jobActivator, null, loggerFactory, concurrenyOptions, monitorManager, targetScalerManager, () => { }, false, drainModeManagerMock.Object); + HostListenerFactory factory = new HostListenerFactory(functions, singletonManager, _jobActivator, null, loggerFactory, monitorManager, targetScalerManager, () => { }, false, drainModeManagerMock.Object); IListener listener = await factory.CreateAsync(CancellationToken.None); var monitors = monitorManager.GetMonitors().ToArray(); @@ -99,6 +100,8 @@ public async Task CreateAsync_RegistersScaleMonitorsAndTargetScaler(bool dynamic Assert.Equal(monitors.Count(), expectedMonitors); Assert.Equal(taretScalers.Count(), expectedTargetScalers); + + Environment.SetEnvironmentVariable("TARGET_BASED_SCALING_ENABLED", null); } [Fact] @@ -200,9 +203,8 @@ public async Task CreateAsync_SkipsDisabledFunctions(Type jobType, string method // function definitions indicate that they are not disabled var monitorManagerMock = new Mock(MockBehavior.Strict); var targetScalerManagerMock = new Mock(MockBehavior.Strict); - var concurrencyOptionsMock = new Mock>(MockBehavior.Strict); var drainModeManagerMock = new Mock(); - HostListenerFactory factory = new HostListenerFactory(functions, singletonManager, _jobActivator, null, loggerFactory, concurrencyOptionsMock.Object, monitorManagerMock.Object, targetScalerManagerMock.Object, () => { }, false, drainModeManagerMock.Object); + HostListenerFactory factory = new HostListenerFactory(functions, singletonManager, _jobActivator, null, loggerFactory, monitorManagerMock.Object, targetScalerManagerMock.Object, () => { }, false, drainModeManagerMock.Object); IListener listener = await factory.CreateAsync(CancellationToken.None); @@ -483,7 +485,7 @@ public void Dispose() throw new NotImplementedException(); } - public Task GetScaleResultAsync(TargetScaleStatusContext context) + public Task GetScaleResultAsync(TargetScalerStatusContext context) { throw new NotImplementedException(); } @@ -542,7 +544,7 @@ public Task GetMetricsAsync() throw new NotImplementedException(); } - public Task GetScaleResultAsync(TargetScaleStatusContext context) + public Task GetScaleResultAsync(TargetScalerStatusContext context) { throw new NotImplementedException(); } @@ -553,7 +555,7 @@ public class TestListener_MonitorAndTargetScaler : TestListener_Monitor, ITarget { public TargetScalerDescriptor TargetScalerDescriptor => throw new NotImplementedException(); - public Task GetScaleResultAsync(TargetScaleStatusContext context) + public Task GetScaleResultAsync(TargetScalerStatusContext context) { throw new NotImplementedException(); } diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs index 89351f2e7..133c60f3b 100644 --- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs +++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs @@ -303,7 +303,7 @@ public void WebJobs_Host_VerifyPublicSurfaceArea() "ITargetScalerProvider", "TargetScalerDescriptor", "TargetScalerResult", - "TargetScaleStatusContext" + "TargetScalerStatusContext" }; TestHelpers.AssertPublicTypes(expected, assembly); From dcb973cb2e9da27e83a90471e139af8cf2dccfc2 Mon Sep 17 00:00:00 2001 From: Alexey Rodionov Date: Wed, 28 Sep 2022 12:21:39 -0700 Subject: [PATCH 3/8] Fixing comments --- .../Executors/JobHostContextFactory.cs | 4 +- .../WebJobsServiceCollectionExtensions.cs | 1 - .../Listeners/HostListenerFactory.cs | 27 +------ .../Scale/ITargetScaler.cs | 8 +- .../Scale/ITargetScalerManager.cs | 6 +- .../Scale/ITargetScalerProvider.cs | 3 - ...tatusContext.cs => TargetScalerContext.cs} | 11 +-- .../Scale/TargetScalerDescriptor.cs | 21 +++--- .../Scale/TargetScalerResult.cs | 4 +- .../HostListenerFactoryTests.cs | 74 +------------------ .../PublicSurfaceTests.cs | 2 +- 11 files changed, 32 insertions(+), 129 deletions(-) rename src/Microsoft.Azure.WebJobs.Host/Scale/{TargetScalerStatusContext.cs => TargetScalerContext.cs} (62%) diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/JobHostContextFactory.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/JobHostContextFactory.cs index a55180ae8..44f703f89 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Executors/JobHostContextFactory.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/JobHostContextFactory.cs @@ -51,7 +51,6 @@ internal class JobHostContextFactory : IJobHostContextFactory private readonly IScaleMonitorManager _monitorManager; private readonly IDrainModeManager _drainModeManager; private readonly IApplicationLifetime _applicationLifetime; - private readonly IOptions _concurrencyOptions; private readonly ITargetScalerManager _targetScalerManager; public JobHostContextFactory( @@ -100,7 +99,6 @@ public JobHostContextFactory( _monitorManager = monitorManager; _drainModeManager = drainModeManager; _applicationLifetime = applicationLifetime; - _concurrencyOptions = concurrencyOptions; _targetScalerManager = targetScalerManager; } @@ -130,7 +128,7 @@ public async Task Create(JobHost host, CancellationToken shutdow // they are started). host.OnHostInitialized(); }; - IListenerFactory functionsListenerFactory = new HostListenerFactory(functions.ReadAll(), _singletonManager, _activator, _nameResolver, _loggerFactory, + IListenerFactory functionsListenerFactory = new HostListenerFactory(functions.ReadAll(), _singletonManager, _activator, _nameResolver, _loggerFactory, _monitorManager, _targetScalerManager, listenersCreatedCallback, _jobHostOptions.Value.AllowPartialHostStartup, _drainModeManager); string hostId = await _hostIdProvider.GetHostIdAsync(cancellationToken); diff --git a/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsServiceCollectionExtensions.cs b/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsServiceCollectionExtensions.cs index 68624900e..c52dd651f 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsServiceCollectionExtensions.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsServiceCollectionExtensions.cs @@ -123,7 +123,6 @@ public static IWebJobsBuilder AddWebJobs(this IServiceCollection services, Actio services.AddSingleton, LoggerFilterOptionsFormatter>(); // Concurrency management - services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddEnumerable(ServiceDescriptor.Singleton()); diff --git a/src/Microsoft.Azure.WebJobs.Host/Listeners/HostListenerFactory.cs b/src/Microsoft.Azure.WebJobs.Host/Listeners/HostListenerFactory.cs index e618125a1..276c5cb17 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Listeners/HostListenerFactory.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Listeners/HostListenerFactory.cs @@ -35,7 +35,6 @@ internal class HostListenerFactory : IListenerFactory private readonly ITargetScalerManager _targetScalerManager; private readonly IDrainModeManager _drainModeManager; - public HostListenerFactory(IEnumerable functionDefinitions, SingletonManager singletonManager, IJobActivator activator, INameResolver nameResolver, ILoggerFactory loggerFactory, IScaleMonitorManager monitorManager, ITargetScalerManager targetScalerManager, Action listenersCreatedCallback, bool allowPartialHostStartup = false, IDrainModeManager drainModeManager = null) { @@ -74,20 +73,9 @@ public async Task CreateAsync(CancellationToken cancellationToken) IListener listener = await listenerFactory.CreateAsync(cancellationToken); - bool targetScalerRegistered = false; - if (bool.TryParse(Environment.GetEnvironmentVariable(TargetBaseScalingEnabled), out bool isTargetBaseScalingEnabled)) - { - if (isTargetBaseScalingEnabled) - { - targetScalerRegistered = RegisterTargetScaler(listener, _targetScalerManager); - } - } + RegisterScaleMonitor(listener, _monitorManager); + RegisterTargetScaler(listener, _targetScalerManager); - if (!targetScalerRegistered) - { - RegisterScaleMonitor(listener, _monitorManager); - } - // if the listener is a Singleton, wrap it with our SingletonListener SingletonAttribute singletonAttribute = SingletonManager.GetListenerSingletonOrNull(listener.GetType(), functionDefinition.Descriptor); if (singletonAttribute != null) @@ -139,32 +127,25 @@ internal static void RegisterScaleMonitor(IListener listener, IScaleMonitorManag } } - internal static bool RegisterTargetScaler(IListener listener, ITargetScalerManager targetScalerManager) + internal static void RegisterTargetScaler(IListener listener, ITargetScalerManager targetScalerManager) { - bool result = false; if (listener is ITargetScaler targetScaler) { targetScalerManager.Register(targetScaler); - result = true; } else if (listener is ITargetScalerProvider targetScalerProvider) { var scaler = ((ITargetScalerProvider)listener).GetTargetScaler(); targetScalerManager.Register(scaler); - result = true; } else if (listener is IEnumerable) { // for composite listeners, we need to check all the inner listeners foreach (var innerListener in ((IEnumerable)listener)) { - if (RegisterTargetScaler(innerListener, targetScalerManager)) - { - result = true; - } + RegisterTargetScaler(innerListener, targetScalerManager); } } - return result; } internal static bool IsDisabled(MethodInfo method, INameResolver nameResolver, IJobActivator activator, IConfiguration configuration) diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScaler.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScaler.cs index c4ce96f3f..bcb4bf69c 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScaler.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScaler.cs @@ -6,8 +6,8 @@ namespace Microsoft.Azure.WebJobs.Host.Scale { /// - /// Interface defining a target scaler that can participate in Azure Functions scaling decisions by - /// taking current metrics, and scaling based on those metrics. + /// Interface defining an Azure Functions scaler that makes scale decisions based on current + /// event source metrics and function concurrency. /// public interface ITargetScaler { @@ -19,9 +19,9 @@ public interface ITargetScaler /// /// Return the current scale result based on the specified context. /// - /// The to use to determine + /// The to use to determine /// the scale result. /// The scale result. - Task GetScaleResultAsync(TargetScalerStatusContext context); + Task GetScaleResultAsync(TargetScalerContext context); } } diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerManager.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerManager.cs index 6085cbea0..2682bce21 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerManager.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerManager.cs @@ -14,11 +14,11 @@ public interface ITargetScalerManager /// /// Register an instance. /// - /// The monitor instance to register. - void Register(ITargetScaler monitor); + /// The target scaler instance to register. + void Register(ITargetScaler scaler); /// - /// Get all registered target scale instances. + /// Get all registered target scaler instances. /// /// /// Should only be called after the host has been started and all diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerProvider.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerProvider.cs index 14d2e06f0..a08ed0355 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerProvider.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerProvider.cs @@ -1,9 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System.Collections; -using System.Collections.Generic; - namespace Microsoft.Azure.WebJobs.Host.Scale { /// diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerStatusContext.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerContext.cs similarity index 62% rename from src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerStatusContext.cs rename to src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerContext.cs index a9c7a842b..cfead5dac 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerStatusContext.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerContext.cs @@ -4,22 +4,17 @@ namespace Microsoft.Azure.WebJobs.Host.Scale { /// - /// Context used by to decide + /// Context used by to decide /// scale result. /// - public class TargetScalerStatusContext + public class TargetScalerContext { /// /// The current worker dyanimc worker concurrency. /// /// - /// Value is reolved in the implementation if null. + /// When not specified, the scaler will determine the concurrency based on configuration. /// public int? InstanceConcurrency { get; set; } - - /// - /// The current worker count for the host application. - /// - public int WorkerCount { get; set; } } } diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs index a85646475..3986eed23 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs @@ -9,28 +9,27 @@ namespace Microsoft.Azure.WebJobs.Host.Scale /// public class TargetScalerDescriptor { - public TargetScalerDescriptor(string id, string functionId) - { - Id = id; - FunctionId = functionId; - } - /// - /// Gets the unique ID for the monitor. + /// Gets or sets the unique ID for the monitor. /// /// /// This should be constant. It is used to correlate persisted metrics samples /// with their corresponding monitor instance. E.g. for a QueueTrigger, this might /// be of the form "{FunctionId}-QueueTrigger-{QueueName}". /// - public string Id { get; } + public string Id { get; set; } + + /// + /// Gets the ID of the function associated with this scaler. + /// + public string FunctionId { get; set; } /// - /// Gets the unique function id. + /// Get or set configuation key name. /// /// - /// This should be constant. It is used to get persisted dynamic concurrency value. + /// It is used to determinate if ScaleContorller suppots targed base scale on particular stamp. /// - public string FunctionId { get; } + public string ConfigurationKeyName { get; set; } } } diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerResult.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerResult.cs index 844c8f7d2..2427a22d2 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerResult.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerResult.cs @@ -4,13 +4,13 @@ namespace Microsoft.Azure.WebJobs.Host.Scale { /// - /// Represents result for target base scale. + /// Represents the scale result of a target scaler. /// public class TargetScalerResult { /// /// Gets or sets the target worker count. /// - public int WorkerCountDifference { get; set; } + public int TargetWorkerCount { get; set; } } } diff --git a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs index 183a4a56f..e609c3dda 100644 --- a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs +++ b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs @@ -38,73 +38,7 @@ public HostListenerFactoryTests() DisableProvider_Instance.Method = null; } - [Theory] - [InlineData(false, typeof(TestListener_Monitor), typeof(TestListener_Monitor), 2, 0)] - [InlineData(false, typeof(TestListener_Monitor), typeof(TestListener_TargetScaler), 1, 0)] - [InlineData(false, typeof(TestListener_Monitor), typeof(TestListener_TargetScalerProvider), 1, 0)] - [InlineData(false, typeof(TestListener_TargetScaler), typeof(TestListener_TargetScalerProvider), 0, 0)] - [InlineData(false, typeof(TestListener_Monitor), typeof(TestListener_MonitorAndTargetScaler), 2, 0)] - [InlineData(true, typeof(TestListener_Monitor), typeof(TestListener_Monitor), 2, 0)] - [InlineData(true, typeof(TestListener_Monitor), typeof(TestListener_TargetScaler), 1, 1)] - [InlineData(true, typeof(TestListener_Monitor), typeof(TestListener_TargetScalerProvider), 1, 1)] - [InlineData(true, typeof(TestListener_TargetScaler), typeof(TestListener_TargetScalerProvider), 0, 2)] - [InlineData(true, typeof(TestListener_Monitor), typeof(TestListener_MonitorAndTargetScaler), 1, 1)] - public async Task CreateAsync_RegistersScaleMonitorsAndTargetScaler(bool tbsEnabled, - Type listenerType1, Type listenerType2, - int expectedMonitors, int expectedTargetScalers) - { - if (tbsEnabled) - { - Environment.SetEnvironmentVariable("TARGET_BASED_SCALING_ENABLED", tbsEnabled.ToString()); - } - - ILoggerFactory loggerFactory = new LoggerFactory(); - TestLoggerProvider loggerProvider = new TestLoggerProvider(); - loggerFactory.AddProvider(loggerProvider); - - // Adding a testListener1 - Mock mockFunctionDefinition1 = new Mock(); - Mock mockInstanceFactory1 = new Mock(MockBehavior.Strict); - Mock mockListenerFactory1 = new Mock(MockBehavior.Strict); - var testListener1 = (IListener)Activator.CreateInstance(listenerType1); - mockListenerFactory1.Setup(p => p.CreateAsync(It.IsAny())).ReturnsAsync(testListener1); - var method1 = typeof(Functions1).GetMethod("TestJob", BindingFlags.Public | BindingFlags.Static); - FunctionDescriptor descriptor1 = FunctionIndexer.FromMethod(method1, _configuration, _jobActivator); - FunctionDefinition definition1 = new FunctionDefinition(descriptor1, mockInstanceFactory1.Object, mockListenerFactory1.Object); - - // Adding a testListener2 - Mock mockFunctionDefinition2 = new Mock(); - Mock mockInstanceFactory2 = new Mock(MockBehavior.Strict); - Mock mockListenerFactory2 = new Mock(MockBehavior.Strict); - var testListener2 = (IListener)Activator.CreateInstance(listenerType2); - var testListererTargetScaler = new TestListener_TargetScaler(); - mockListenerFactory2.Setup(p => p.CreateAsync(It.IsAny())).ReturnsAsync(testListener2); - var method2 = typeof(Functions1).GetMethod("TestJob", BindingFlags.Public | BindingFlags.Static); - FunctionDescriptor descriptor2 = FunctionIndexer.FromMethod(method2, _configuration, _jobActivator); - FunctionDefinition definition2 = new FunctionDefinition(descriptor2, mockInstanceFactory2.Object, mockListenerFactory2.Object); - - List functions = new List(); - functions.Add(definition1); - functions.Add(definition2); - - var monitorManager = new ScaleMonitorManager(); - var targetScalerManager = new TargetScalerManager(); - var drainModeManagerMock = new Mock(); - SingletonManager singletonManager = new SingletonManager(); - HostListenerFactory factory = new HostListenerFactory(functions, singletonManager, _jobActivator, null, loggerFactory, monitorManager, targetScalerManager, () => { }, false, drainModeManagerMock.Object); - IListener listener = await factory.CreateAsync(CancellationToken.None); - - var monitors = monitorManager.GetMonitors().ToArray(); - var taretScalers = targetScalerManager.GetTargetScalers().ToArray(); - - Assert.Equal(monitors.Count(), expectedMonitors); - - Assert.Equal(taretScalers.Count(), expectedTargetScalers); - - Environment.SetEnvironmentVariable("TARGET_BASED_SCALING_ENABLED", null); - } - - [Fact] + [Fact] public void RegisterScaleMonitor_Succeeds() { // listener is a direct monitor @@ -485,7 +419,7 @@ public void Dispose() throw new NotImplementedException(); } - public Task GetScaleResultAsync(TargetScalerStatusContext context) + public Task GetScaleResultAsync(TargetScalerContext context) { throw new NotImplementedException(); } @@ -544,7 +478,7 @@ public Task GetMetricsAsync() throw new NotImplementedException(); } - public Task GetScaleResultAsync(TargetScalerStatusContext context) + public Task GetScaleResultAsync(TargetScalerContext context) { throw new NotImplementedException(); } @@ -555,7 +489,7 @@ public class TestListener_MonitorAndTargetScaler : TestListener_Monitor, ITarget { public TargetScalerDescriptor TargetScalerDescriptor => throw new NotImplementedException(); - public Task GetScaleResultAsync(TargetScalerStatusContext context) + public Task GetScaleResultAsync(TargetScalerContext context) { throw new NotImplementedException(); } diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs index 133c60f3b..7bf8c0362 100644 --- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs +++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs @@ -303,7 +303,7 @@ public void WebJobs_Host_VerifyPublicSurfaceArea() "ITargetScalerProvider", "TargetScalerDescriptor", "TargetScalerResult", - "TargetScalerStatusContext" + "TargetScalerContext" }; TestHelpers.AssertPublicTypes(expected, assembly); From 01ec08ffe6a93ecc218afd9e5c6cddea90af9902 Mon Sep 17 00:00:00 2001 From: Alexey Rodionov Date: Wed, 28 Sep 2022 13:23:16 -0700 Subject: [PATCH 4/8] Fix IConcurrencyStatusRepository DI --- .../Executors/JobHostContextFactory.cs | 1 - .../Hosting/WebJobsServiceCollectionExtensions.cs | 1 + .../Listeners/HostListenerFactory.cs | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/JobHostContextFactory.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/JobHostContextFactory.cs index 44f703f89..3b1686078 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Executors/JobHostContextFactory.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/JobHostContextFactory.cs @@ -75,7 +75,6 @@ public JobHostContextFactory( IScaleMonitorManager monitorManager, IDrainModeManager drainModeManager, IApplicationLifetime applicationLifetime, - IOptions concurrencyOptions, ITargetScalerManager targetScalerManager) { _dashboardLoggingSetup = dashboardLoggingSetup; diff --git a/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsServiceCollectionExtensions.cs b/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsServiceCollectionExtensions.cs index c52dd651f..68624900e 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsServiceCollectionExtensions.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsServiceCollectionExtensions.cs @@ -123,6 +123,7 @@ public static IWebJobsBuilder AddWebJobs(this IServiceCollection services, Actio services.AddSingleton, LoggerFilterOptionsFormatter>(); // Concurrency management + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddEnumerable(ServiceDescriptor.Singleton()); diff --git a/src/Microsoft.Azure.WebJobs.Host/Listeners/HostListenerFactory.cs b/src/Microsoft.Azure.WebJobs.Host/Listeners/HostListenerFactory.cs index 276c5cb17..ce5fe838f 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Listeners/HostListenerFactory.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Listeners/HostListenerFactory.cs @@ -22,7 +22,6 @@ internal class HostListenerFactory : IListenerFactory { private static readonly MethodInfo JobActivatorCreateMethod = typeof(IJobActivator).GetMethod("CreateInstance", BindingFlags.Public | BindingFlags.Instance).GetGenericMethodDefinition(); private const string IsDisabledFunctionName = "IsDisabled"; - private const string TargetBaseScalingEnabled = "TARGET_BASED_SCALING_ENABLED"; private readonly IEnumerable _functionDefinitions; private readonly SingletonManager _singletonManager; private readonly IJobActivator _activator; From 35fdfaee29d3c9e40ade0b90b096bb9e0fe0873e Mon Sep 17 00:00:00 2001 From: Alexey Rodionov Date: Thu, 29 Sep 2022 16:31:44 -0700 Subject: [PATCH 5/8] Removing Id in TargetScalerDescriptor --- .../Scale/TargetScalerDescriptor.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs index 3986eed23..dd3c1385c 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs @@ -9,20 +9,15 @@ namespace Microsoft.Azure.WebJobs.Host.Scale /// public class TargetScalerDescriptor { - /// - /// Gets or sets the unique ID for the monitor. - /// - /// - /// This should be constant. It is used to correlate persisted metrics samples - /// with their corresponding monitor instance. E.g. for a QueueTrigger, this might - /// be of the form "{FunctionId}-QueueTrigger-{QueueName}". - /// - public string Id { get; set; } + public TargetScalerDescriptor(string functionId) + { + FunctionId = functionId; + } /// /// Gets the ID of the function associated with this scaler. /// - public string FunctionId { get; set; } + public string FunctionId { get; } /// /// Get or set configuation key name. From 4b6ad410163cebdcc9beede0ec18df749a448ec6 Mon Sep 17 00:00:00 2001 From: Alexey Rodionov Date: Fri, 30 Sep 2022 13:34:09 -0700 Subject: [PATCH 6/8] Adding CreateAsync_RegistersTargetScalers test --- .../HostListenerFactoryTests.cs | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs index e609c3dda..7a467c5a9 100644 --- a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs +++ b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs @@ -38,7 +38,73 @@ public HostListenerFactoryTests() DisableProvider_Instance.Method = null; } - [Fact] + [Fact] + public async Task CreateAsync_RegistersScaleMonitors() + { + Mock mockFunctionDefinition = new Mock(); + Mock mockInstanceFactory = new Mock(MockBehavior.Strict); + Mock mockListenerFactory = new Mock(MockBehavior.Strict); + var testListener = new TestListener_Monitor(); + mockListenerFactory.Setup(p => p.CreateAsync(It.IsAny())).ReturnsAsync(testListener); + SingletonManager singletonManager = new SingletonManager(); + + ILoggerFactory loggerFactory = new LoggerFactory(); + TestLoggerProvider loggerProvider = new TestLoggerProvider(); + loggerFactory.AddProvider(loggerProvider); + + List functions = new List(); + var method = typeof(Functions1).GetMethod("TestJob", BindingFlags.Public | BindingFlags.Static); + FunctionDescriptor descriptor = FunctionIndexer.FromMethod(method, _configuration, _jobActivator); + FunctionDefinition definition = new FunctionDefinition(descriptor, mockInstanceFactory.Object, mockListenerFactory.Object); + functions.Add(definition); + + var monitorManager = new ScaleMonitorManager(); + var targetScaleManager = new TargetScalerManager(); + var drainModeManagerMock = new Mock(); + HostListenerFactory factory = new HostListenerFactory(functions, singletonManager, _jobActivator, null, loggerFactory, monitorManager, targetScaleManager, () => { }, false, drainModeManagerMock.Object); + IListener listener = await factory.CreateAsync(CancellationToken.None); + + var innerListeners = ((IEnumerable)listener).ToArray(); + + var monitors = monitorManager.GetMonitors().ToArray(); + Assert.Single(monitors); + Assert.Same(testListener, monitors[0]); + } + + [Fact] + public async Task CreateAsync_RegistersTargetScalers() + { + Mock mockFunctionDefinition = new Mock(); + Mock mockInstanceFactory = new Mock(MockBehavior.Strict); + Mock mockListenerFactory = new Mock(MockBehavior.Strict); + var testListener = new TestListener_TargetScaler(); + mockListenerFactory.Setup(p => p.CreateAsync(It.IsAny())).ReturnsAsync(testListener); + SingletonManager singletonManager = new SingletonManager(); + + ILoggerFactory loggerFactory = new LoggerFactory(); + TestLoggerProvider loggerProvider = new TestLoggerProvider(); + loggerFactory.AddProvider(loggerProvider); + + List functions = new List(); + var method = typeof(Functions1).GetMethod("TestJob", BindingFlags.Public | BindingFlags.Static); + FunctionDescriptor descriptor = FunctionIndexer.FromMethod(method, _configuration, _jobActivator); + FunctionDefinition definition = new FunctionDefinition(descriptor, mockInstanceFactory.Object, mockListenerFactory.Object); + functions.Add(definition); + + var monitorManager = new ScaleMonitorManager(); + var targetScaleManager = new TargetScalerManager(); + var drainModeManagerMock = new Mock(); + HostListenerFactory factory = new HostListenerFactory(functions, singletonManager, _jobActivator, null, loggerFactory, monitorManager, targetScaleManager, () => { }, false, drainModeManagerMock.Object); + IListener listener = await factory.CreateAsync(CancellationToken.None); + + var innerListeners = ((IEnumerable)listener).ToArray(); + + var targetScalers = targetScaleManager.GetTargetScalers().ToArray(); + Assert.Single(targetScalers); + Assert.Same(testListener, targetScalers[0]); + } + + [Fact] public void RegisterScaleMonitor_Succeeds() { // listener is a direct monitor From 9003c3011533624391e5ed18bea3a1bef42c4400 Mon Sep 17 00:00:00 2001 From: Alexey Rodionov Date: Fri, 30 Sep 2022 14:50:38 -0700 Subject: [PATCH 7/8] Removing ConfigurationKeyName --- .../Scale/TargetScalerDescriptor.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs index dd3c1385c..3cdc325d2 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs @@ -18,13 +18,5 @@ public TargetScalerDescriptor(string functionId) /// Gets the ID of the function associated with this scaler. /// public string FunctionId { get; } - - /// - /// Get or set configuation key name. - /// - /// - /// It is used to determinate if ScaleContorller suppots targed base scale on particular stamp. - /// - public string ConfigurationKeyName { get; set; } } } From ac25b4869f984f7d9f74f536b563cb9b17c1b01c Mon Sep 17 00:00:00 2001 From: Alexey Rodionov Date: Thu, 6 Oct 2022 13:06:51 -0700 Subject: [PATCH 8/8] Fix some strings --- src/Microsoft.Azure.WebJobs.Host/Scale/IScaleMonitorManager.cs | 2 +- src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerManager.cs | 2 +- src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerContext.cs | 2 +- .../Scale/TargetScalerDescriptor.cs | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/IScaleMonitorManager.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/IScaleMonitorManager.cs index e1269d2fe..a78517198 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Scale/IScaleMonitorManager.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/IScaleMonitorManager.cs @@ -24,7 +24,7 @@ public interface IScaleMonitorManager /// Should only be called after the host has been started and all /// instances are registered. /// - /// The collection of monitor intances. + /// The collection of monitor instances. IEnumerable GetMonitors(); } } diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerManager.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerManager.cs index 2682bce21..076801afb 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerManager.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/ITargetScalerManager.cs @@ -24,7 +24,7 @@ public interface ITargetScalerManager /// Should only be called after the host has been started and all /// instances are registered. /// - /// The collection of target scaler intances. + /// The collection of target scaler instances. IEnumerable GetTargetScalers(); } } diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerContext.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerContext.cs index cfead5dac..478fd0732 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerContext.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerContext.cs @@ -10,7 +10,7 @@ namespace Microsoft.Azure.WebJobs.Host.Scale public class TargetScalerContext { /// - /// The current worker dyanimc worker concurrency. + /// The current concurrency for the target function. /// /// /// When not specified, the scaler will determine the concurrency based on configuration. diff --git a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs index 3cdc325d2..4ae9aebb5 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Scale/TargetScalerDescriptor.cs @@ -3,7 +3,6 @@ namespace Microsoft.Azure.WebJobs.Host.Scale { - /// /// Metadata descriptor for an . ///