Skip to content

Commit a26a1f6

Browse files
authored
Use single instance of WebhostMetricsLogger (#5084)
1 parent 6fc46c7 commit a26a1f6

File tree

19 files changed

+318
-34
lines changed

19 files changed

+318
-34
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using Microsoft.Azure.WebJobs.Script.WebHost;
5+
using Microsoft.Extensions.Options;
6+
using Microsoft.Extensions.Primitives;
7+
8+
namespace Microsoft.Azure.WebJobs.Script.Configuration
9+
{
10+
// An implementation of IOptionsChangeTokenSource<TOptions> that automatically signals its change token when specialization occurs.
11+
internal class SpecializationChangeTokenSource<TOptions> : IOptionsChangeTokenSource<TOptions>
12+
{
13+
private readonly IOptionsChangeTokenSource<StandbyOptions> _standbyChangeTokenSource;
14+
15+
public SpecializationChangeTokenSource(IOptionsChangeTokenSource<StandbyOptions> standbyChangeTokenSource)
16+
{
17+
// When standby occurs, we also want compat options to re-evaluate.
18+
_standbyChangeTokenSource = standbyChangeTokenSource;
19+
}
20+
21+
public string Name { get; }
22+
23+
public IChangeToken GetChangeToken()
24+
{
25+
return _standbyChangeTokenSource.GetChangeToken();
26+
}
27+
}
28+
}

src/WebJobs.Script.WebHost/DependencyInjection/DependencyValidator/DependencyValidator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ private static ExpectedDependencyBuilder CreateExpectedDependencies()
2929
expected.ExpectNone<IEventGenerator>();
3030

3131
expected.Expect<ILoggerFactory, ScriptLoggerFactory>();
32-
expected.Expect<IMetricsLogger, WebHostMetricsLogger>();
32+
expected.ExpectFactory<IMetricsLogger, NonDisposableMetricsLogger>();
3333

3434
expected.Expect<IWebJobsExceptionHandler, WebScriptHostExceptionHandler>();
3535

src/WebJobs.Script.WebHost/Diagnostics/MetricsEventManager.cs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
using System.Linq;
99
using System.Threading;
1010
using System.Threading.Tasks;
11+
using Microsoft.Azure.WebJobs.Script.Configuration;
1112
using Microsoft.Azure.WebJobs.Script.Description;
1213
using Microsoft.Azure.WebJobs.Script.Diagnostics;
1314
using Microsoft.Azure.WebJobs.Script.WebHost.Metrics;
1415
using Microsoft.Azure.WebJobs.Script.WebHost.Models;
16+
using Microsoft.Extensions.Options;
1517

1618
namespace Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics
1719
{
@@ -25,17 +27,14 @@ public class MetricsEventManager : IDisposable
2527
private readonly int _functionActivityFlushIntervalSeconds;
2628
private readonly Timer _metricsFlushTimer;
2729
private readonly object _functionActivityTrackerLockObject = new object();
28-
private static string appName;
29-
private static string subscriptionId;
3030
private bool _disposed;
3131
private IMetricsPublisher _metricsPublisher;
32+
private IOptionsMonitor<AppServiceOptions> _appServiceOptions;
3233

33-
public MetricsEventManager(IEnvironment environment, IEventGenerator generator, int functionActivityFlushIntervalSeconds, IMetricsPublisher metricsPublisher, int metricsFlushIntervalMS = DefaultFlushIntervalMS)
34+
public MetricsEventManager(IOptionsMonitor<AppServiceOptions> appServiceOptions, IEventGenerator generator, int functionActivityFlushIntervalSeconds, IMetricsPublisher metricsPublisher, int metricsFlushIntervalMS = DefaultFlushIntervalMS)
3435
{
3536
// we read these in the ctor (not static ctor) since it can change on the fly
36-
appName = GetNormalizedString(environment.GetAzureWebsiteUniqueSlotName());
37-
subscriptionId = environment.GetSubscriptionId() ?? string.Empty;
38-
37+
_appServiceOptions = appServiceOptions;
3938
_eventGenerator = generator;
4039
_functionActivityFlushIntervalSeconds = functionActivityFlushIntervalSeconds;
4140
QueuedEvents = new ConcurrentDictionary<string, SystemMetricEvent>(StringComparer.OrdinalIgnoreCase);
@@ -163,7 +162,7 @@ internal void FunctionStarted(FunctionStartedEvent startedEvent)
163162
{
164163
if (instance == null)
165164
{
166-
instance = new FunctionActivityTracker(_eventGenerator, _metricsPublisher, _functionActivityFlushIntervalSeconds);
165+
instance = new FunctionActivityTracker(_appServiceOptions, _eventGenerator, _metricsPublisher, _functionActivityFlushIntervalSeconds);
167166
}
168167
instance.FunctionStarted(startedEvent);
169168
}
@@ -201,7 +200,7 @@ internal void HostStarted(ScriptHost scriptHost)
201200
}
202201

203202
_eventGenerator.LogFunctionDetailsEvent(
204-
appName,
203+
_appServiceOptions.CurrentValue.AppName,
205204
GetNormalizedString(function.Name),
206205
function.Metadata != null ? SerializeBindings(function.Metadata.InputBindings) : GetNormalizedString(null),
207206
function.Metadata != null ? SerializeBindings(function.Metadata.OutputBindings) : GetNormalizedString(null),
@@ -277,11 +276,12 @@ protected internal virtual void WriteMetricEvents(SystemMetricEvent[] metricEven
277276
throw new ArgumentNullException(nameof(metricEvents));
278277
}
279278

279+
AppServiceOptions currentAppServiceOptions = _appServiceOptions.CurrentValue;
280280
foreach (SystemMetricEvent metricEvent in metricEvents)
281281
{
282282
_eventGenerator.LogFunctionMetricEvent(
283-
subscriptionId,
284-
appName,
283+
currentAppServiceOptions.SubscriptionId,
284+
currentAppServiceOptions.AppName,
285285
metricEvent.FunctionName ?? string.Empty,
286286
metricEvent.EventName.ToLowerInvariant(),
287287
metricEvent.Average,
@@ -330,11 +330,13 @@ private class FunctionActivityTracker : IDisposable
330330
private ConcurrentQueue<FunctionMetrics> _functionMetricsQueue = new ConcurrentQueue<FunctionMetrics>();
331331
private Dictionary<string, RunningFunctionInfo> _runningFunctions = new Dictionary<string, RunningFunctionInfo>();
332332
private bool _disposed = false;
333+
private IOptionsMonitor<AppServiceOptions> _appServiceOptions;
333334
private IMetricsPublisher _metricsPublisher;
334335

335-
internal FunctionActivityTracker(IEventGenerator generator, IMetricsPublisher metricsPublisher, int functionActivityFlushInterval)
336+
internal FunctionActivityTracker(IOptionsMonitor<AppServiceOptions> appServiceOptions, IEventGenerator generator, IMetricsPublisher metricsPublisher, int functionActivityFlushInterval)
336337
{
337338
MetricsEventGenerator = generator;
339+
_appServiceOptions = appServiceOptions;
338340
_functionActivityFlushInterval = functionActivityFlushInterval;
339341
_metricsPublisher = metricsPublisher;
340342
Task.Run(
@@ -477,7 +479,7 @@ private void RaiseFunctionMetricEvent(RunningFunctionInfo runningFunctionInfo, i
477479

478480
MetricsEventGenerator.LogFunctionExecutionEvent(
479481
_executionId,
480-
appName,
482+
_appServiceOptions.CurrentValue.AppName,
481483
concurrency,
482484
runningFunctionInfo.Name,
483485
runningFunctionInfo.InvocationId.ToString(),
@@ -522,7 +524,7 @@ group item by item.FunctionName into functionGroups
522524

523525
foreach (var functionEvent in aggregatedEventsPerFunction)
524526
{
525-
MetricsEventGenerator.LogFunctionExecutionAggregateEvent(appName, functionEvent.FunctionName, (long)functionEvent.TotalExectionTimeInMs, (long)functionEvent.StartedCount, (long)functionEvent.SucceededCount, (long)functionEvent.FailedCount);
527+
MetricsEventGenerator.LogFunctionExecutionAggregateEvent(_appServiceOptions.CurrentValue.AppName, functionEvent.FunctionName, (long)functionEvent.TotalExectionTimeInMs, (long)functionEvent.StartedCount, (long)functionEvent.SucceededCount, (long)functionEvent.FailedCount);
526528
}
527529
}
528530

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using Microsoft.Azure.WebJobs.Script.Diagnostics;
5+
6+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics
7+
{
8+
/**
9+
* This decorator is required to enforce a single instance of WebHostMetricsLogger across webhost and jobhost and also not letting it get disposed during jobhost restarts. The host DI implementation disposes of registered instances, which is a behavior that cannot be changed.
10+
*/
11+
internal class NonDisposableMetricsLogger : IMetricsLogger
12+
{
13+
private readonly IMetricsLogger _metricsLogger;
14+
15+
public NonDisposableMetricsLogger(IMetricsLogger metricsLogger)
16+
{
17+
_metricsLogger = metricsLogger;
18+
}
19+
20+
public object BeginEvent(string eventName, string functionName = null, string data = null)
21+
{
22+
return _metricsLogger.BeginEvent(eventName, functionName, data);
23+
}
24+
25+
public void BeginEvent(MetricEvent metricEvent)
26+
{
27+
_metricsLogger.BeginEvent(metricEvent);
28+
}
29+
30+
public void EndEvent(MetricEvent metricEvent)
31+
{
32+
_metricsLogger.EndEvent(metricEvent);
33+
}
34+
35+
public void EndEvent(object eventHandle)
36+
{
37+
_metricsLogger.EndEvent(eventHandle);
38+
}
39+
40+
public void LogEvent(MetricEvent metricEvent)
41+
{
42+
_metricsLogger.LogEvent(metricEvent);
43+
}
44+
45+
public void LogEvent(string eventName, string functionName = null, string data = null)
46+
{
47+
_metricsLogger.LogEvent(eventName, functionName, data);
48+
}
49+
}
50+
}

src/WebJobs.Script.WebHost/Diagnostics/WebHostMetricsLogger.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
5+
using Microsoft.Azure.WebJobs.Script.Configuration;
56
using Microsoft.Azure.WebJobs.Script.Diagnostics;
67
using Microsoft.Azure.WebJobs.Script.WebHost.Metrics;
8+
using Microsoft.Extensions.Options;
79

810
namespace Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics
911
{
@@ -12,8 +14,8 @@ public class WebHostMetricsLogger : IMetricsLogger, IDisposable
1214
private readonly MetricsEventManager _metricsEventManager;
1315
private bool disposed = false;
1416

15-
public WebHostMetricsLogger(IEnvironment environment, IEventGenerator eventGenerator, IMetricsPublisher metricsPublisher)
16-
: this(environment, eventGenerator, metricsPublisher, 5)
17+
public WebHostMetricsLogger(IOptionsMonitor<AppServiceOptions> appServiceOptions, IEventGenerator eventGenerator, IMetricsPublisher metricsPublisher)
18+
: this(appServiceOptions, eventGenerator, metricsPublisher, 5)
1719
{
1820
}
1921

@@ -22,9 +24,9 @@ public WebHostMetricsLogger(MetricsEventManager eventManager)
2224
_metricsEventManager = eventManager;
2325
}
2426

25-
protected WebHostMetricsLogger(IEnvironment environment, IEventGenerator eventGenerator, IMetricsPublisher metricsPublisher, int metricEventIntervalInSeconds)
27+
protected WebHostMetricsLogger(IOptionsMonitor<AppServiceOptions> appServiceOptions, IEventGenerator eventGenerator, IMetricsPublisher metricsPublisher, int metricEventIntervalInSeconds)
2628
{
27-
_metricsEventManager = new MetricsEventManager(environment, eventGenerator, metricEventIntervalInSeconds, metricsPublisher);
29+
_metricsEventManager = new MetricsEventManager(appServiceOptions, eventGenerator, metricEventIntervalInSeconds, metricsPublisher);
2830
}
2931

3032
public object BeginEvent(string eventName, string functionName = null, string data = null)

src/WebJobs.Script.WebHost/WebHostServiceCollectionExtensions.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Microsoft.AspNetCore.Authorization;
77
using Microsoft.Azure.WebJobs.Extensions.Http;
88
using Microsoft.Azure.WebJobs.Script.Config;
9+
using Microsoft.Azure.WebJobs.Script.Configuration;
910
using Microsoft.Azure.WebJobs.Script.Diagnostics;
1011
using Microsoft.Azure.WebJobs.Script.Middleware;
1112
using Microsoft.Azure.WebJobs.Script.Rpc;
@@ -74,7 +75,6 @@ public static void AddWebJobsScriptHost(this IServiceCollection services, IConfi
7475
services.AddSingleton<IScriptWebHostEnvironment, ScriptWebHostEnvironment>();
7576
services.TryAddSingleton<IStandbyManager, StandbyManager>();
7677
services.TryAddSingleton<IScriptHostBuilder, DefaultScriptHostBuilder>();
77-
services.AddSingleton<IMetricsLogger, WebHostMetricsLogger>();
7878

7979
// Linux container services
8080
services.AddLinuxContainerServices();
@@ -133,6 +133,7 @@ public static void AddWebJobsScriptHost(this IServiceCollection services, IConfi
133133
services.ConfigureOptions<ScriptApplicationHostOptionsSetup>();
134134
services.ConfigureOptions<StandbyOptionsSetup>();
135135
services.ConfigureOptions<LanguageWorkerOptionsSetup>();
136+
services.ConfigureOptionsWithChangeTokenSource<AppServiceOptions, AppServiceOptionsSetup, SpecializationChangeTokenSource<AppServiceOptions>>();
136137

137138
services.TryAddSingleton<IDependencyValidator, DependencyValidator>();
138139
services.TryAddSingleton<IJobHostMiddlewarePipeline>(s => DefaultMiddlewarePipeline.Empty);
@@ -186,5 +187,16 @@ private static void AddLinuxContainerServices(this IServiceCollection services)
186187
return new NullMetricsPublisher(nullMetricsLogger);
187188
});
188189
}
190+
191+
private static IServiceCollection ConfigureOptionsWithChangeTokenSource<TOptions, TOptionsSetup, TOptionsChangeTokenSource>(this IServiceCollection services)
192+
where TOptions : class
193+
where TOptionsSetup : class, IConfigureOptions<TOptions>
194+
where TOptionsChangeTokenSource : class, IOptionsChangeTokenSource<TOptions>
195+
{
196+
services.ConfigureOptions<TOptionsSetup>();
197+
services.AddSingleton<IOptionsChangeTokenSource<TOptions>, TOptionsChangeTokenSource>();
198+
199+
return services;
200+
}
189201
}
190202
}

src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public static IHostBuilder AddWebScriptHost(this IHostBuilder builder, IServiceP
101101
}
102102

103103
// Logging and diagnostics
104-
services.AddSingleton<IMetricsLogger, WebHostMetricsLogger>();
104+
services.AddSingleton<IMetricsLogger>(a => new NonDisposableMetricsLogger(metricsLogger));
105105
services.AddSingleton<IEventCollectorProvider, FunctionInstanceLogCollectorProvider>();
106106

107107
// Hosted services
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.Azure.WebJobs.Script.Configuration
5+
{
6+
public class AppServiceOptions
7+
{
8+
public string AppName { get; set; }
9+
10+
public string SubscriptionId { get; set; }
11+
}
12+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using Microsoft.Extensions.Options;
5+
6+
namespace Microsoft.Azure.WebJobs.Script.Configuration
7+
{
8+
internal class AppServiceOptionsSetup : IConfigureOptions<AppServiceOptions>
9+
{
10+
private readonly IEnvironment _environment;
11+
12+
public AppServiceOptionsSetup(IEnvironment environment)
13+
{
14+
_environment = environment;
15+
}
16+
17+
public void Configure(AppServiceOptions options)
18+
{
19+
options.AppName = _environment.GetAzureWebsiteUniqueSlotName() ?? string.Empty;
20+
options.SubscriptionId = _environment.GetSubscriptionId() ?? string.Empty;
21+
}
22+
}
23+
}

test/WebJobs.Script.Tests.Integration/Diagnostics/MetricsEventManagerTests.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
using System.Text;
88
using System.Threading;
99
using System.Threading.Tasks;
10+
using Microsoft.Azure.WebJobs.Script.Configuration;
1011
using Microsoft.Azure.WebJobs.Script.Description;
1112
using Microsoft.Azure.WebJobs.Script.Diagnostics;
1213
using Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics;
1314
using Microsoft.Azure.WebJobs.Script.WebHost.Metrics;
1415
using Microsoft.Azure.WebJobs.Script.WebHost.Models;
16+
using Microsoft.Extensions.Options;
1517
using Moq;
1618
using Moq.Protected;
1719
using Xunit;
@@ -77,7 +79,9 @@ public MetricsEventManagerTests()
7779
});
7880

7981
var mockMetricsPublisher = new Mock<IMetricsPublisher>();
80-
_metricsEventManager = new MetricsEventManager(new TestEnvironment(), mockEventGenerator.Object, MinimumLongRunningDurationInMs / 1000, mockMetricsPublisher.Object);
82+
var testAppServiceOptions = new Mock<IOptionsMonitor<AppServiceOptions>>();
83+
testAppServiceOptions.Setup(a => a.CurrentValue).Returns(new AppServiceOptions { AppName = "RandomAppName", SubscriptionId = Guid.NewGuid().ToString() });
84+
_metricsEventManager = new MetricsEventManager(testAppServiceOptions.Object, mockEventGenerator.Object, MinimumLongRunningDurationInMs / 1000, mockMetricsPublisher.Object);
8185
_metricsLogger = new WebHostMetricsLogger(_metricsEventManager);
8286
}
8387

@@ -385,7 +389,9 @@ public async Task TimerFlush_CalledOnExpectedInterval()
385389
{
386390
int flushInterval = 10;
387391
Mock<IEventGenerator> mockGenerator = new Mock<IEventGenerator>();
388-
Mock<MetricsEventManager> mockEventManager = new Mock<MetricsEventManager>(new TestEnvironment(), mockGenerator.Object, flushInterval, null, flushInterval) { CallBase = true };
392+
var testAppServiceOptions = new Mock<IOptionsMonitor<AppServiceOptions>>();
393+
testAppServiceOptions.Setup(a => a.CurrentValue).Returns(new AppServiceOptions { AppName = "RandomAppName", SubscriptionId = Guid.NewGuid().ToString() });
394+
Mock<MetricsEventManager> mockEventManager = new Mock<MetricsEventManager>(testAppServiceOptions.Object, mockGenerator.Object, flushInterval, null, flushInterval) { CallBase = true };
389395
MetricsEventManager eventManager = mockEventManager.Object;
390396

391397
int numFlushes = 0;

0 commit comments

Comments
 (0)