Skip to content

Commit 0537263

Browse files
Francisco-Gaminoazfuncghkshyju
authored
4.9.300 Hotfix (#11004)
* Update version to 4.1039.300 * Clear release notes * 4.9.300 Hotfix (#11001) * Enhancement to memory metrics reporting (#10984) * Improved memory metrics reporting using CGroup data for Linux consumption. * Minor logging improvements. * Removed OS check as it is not needed since this publisher will be enabled only for Linux. * Log only when IsCGroupMemoryMetricsEnabled property value changes. * Update LinuxContainerLegionMetricsPublisher to use CGroup memory metrics * Reordering the location of property before method, to fix SA 1201 Stylecop warning. * Fixes circular dependency when resolving LinuxContainerLegionMetricsPublisher (#10991) * Avoid circular dependency when resolving LinuxContainerLegionMetricsPublisher. * Revert launchsettings.json chane. * Adding a placeholder mode host startup test for Linux consumption on legion * Update test to verify SKU specific services are resolved from DI container. * Resolve conflict in release notes * Switch to Type check instead of type name check, in the test. * release notes insanity fix. --------- Co-authored-by: Azure Functions Release <[email protected]> Co-authored-by: Shyju Krishnankutty <[email protected]>
1 parent 6351fc2 commit 0537263

16 files changed

+283
-108
lines changed

release_notes.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
<!-- Please add your release notes in the following format:
44
- My change description (#PR)
55
-->
6-
- Add JitTrace Files for v4.1039
6+
- Improved memory metrics reporting using CGroup data for Linux consumption (#10968)
7+
- Avoid circular dependency when resolving LinuxContainerLegionMetricsPublisher. (#10991)

src/Directory.Version.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<VersionPrefix>4.1039.200</VersionPrefix>
3+
<VersionPrefix>4.1039.300</VersionPrefix>
44
<UpdateBuildNumber>true</UpdateBuildNumber>
55
</PropertyGroup>
66
</Project>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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 System;
5+
using System.IO;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Metrics
9+
{
10+
internal static class CgroupMemoryUsageHelper
11+
{
12+
private const string CgroupPathV1 = "/sys/fs/cgroup/memory/memory.usage_in_bytes";
13+
private const string CgroupPathV2 = "/sys/fs/cgroup/memory.current";
14+
15+
/// <summary>
16+
/// Retrieves the memory usage of the control group in bytes, supporting both cgroup v1 and v2.
17+
/// </summary>
18+
/// <param name="logger">An <see cref="ILogger{TCategoryName}"/> instance for logging.</param>
19+
/// <returns>The memory usage in bytes if available; otherwise, 0.</returns>
20+
internal static long GetMemoryUsageInBytes(ILogger logger)
21+
{
22+
try
23+
{
24+
if (TryReadMemoryUsage(CgroupPathV2, out var usageInBytes) || TryReadMemoryUsage(CgroupPathV1, out usageInBytes))
25+
{
26+
return usageInBytes;
27+
}
28+
29+
logger.LogWarning("Memory usage not available from either control group v1 or v2.");
30+
return 0;
31+
}
32+
catch (Exception ex)
33+
{
34+
logger.LogError(ex, "Error reading control group resource usage.");
35+
return 0;
36+
}
37+
}
38+
39+
private static bool TryReadMemoryUsage(string path, out long memoryUsageInBytes)
40+
{
41+
memoryUsageInBytes = 0;
42+
43+
if (!File.Exists(path))
44+
{
45+
return false;
46+
}
47+
48+
return long.TryParse(File.ReadAllText(path), out memoryUsageInBytes);
49+
}
50+
}
51+
}

src/WebJobs.Script.WebHost/Metrics/LinuxContainerLegionMetricsPublisher.cs

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
using System.Threading;
88
using System.Threading.Tasks;
99
using Microsoft.Azure.Functions.Platform.Metrics.LinuxConsumption;
10+
using Microsoft.Azure.WebJobs.Script.Config;
1011
using Microsoft.Azure.WebJobs.Script.Diagnostics;
1112
using Microsoft.Azure.WebJobs.Script.WebHost.Configuration;
13+
using Microsoft.Extensions.DependencyInjection;
1214
using Microsoft.Extensions.Logging;
1315
using Microsoft.Extensions.Options;
1416

@@ -21,29 +23,34 @@ public sealed class LinuxContainerLegionMetricsPublisher : IMetricsPublisher, ID
2123
private readonly TimeSpan _memorySnapshotInterval = TimeSpan.FromMilliseconds(1000);
2224
private readonly TimeSpan _timerStartDelay = TimeSpan.FromSeconds(2);
2325
private readonly IOptionsMonitor<StandbyOptions> _standbyOptions;
24-
private readonly IDisposable _standbyOptionsOnChangeSubscription;
26+
private readonly IDisposable _standbyOptionsOnChangeListener;
27+
private readonly IDisposable _hostingConfigOptionsOnChangeListener;
2528
private readonly IEnvironment _environment;
2629
private readonly ILogger _logger;
27-
private readonly IScriptHostManager _scriptHostManager;
30+
private readonly IServiceProvider _serviceProvider;
2831
private readonly string _containerName;
32+
private readonly IOptionsMonitor<FunctionsHostingConfigOptions> _hostingConfigOptions;
2933

3034
private IMetricsLogger _metricsLogger;
3135
private TimeSpan _metricPublishInterval;
3236
private Process _process;
3337
private Timer _processMonitorTimer;
3438
private Timer _metricsPublisherTimer;
3539
private bool _initialized = false;
40+
private bool _isCGroupMemoryMetricsEnabled = false;
3641

3742
public LinuxContainerLegionMetricsPublisher(IEnvironment environment, IOptionsMonitor<StandbyOptions> standbyOptions,
3843
IOptions<LinuxConsumptionLegionMetricsPublisherOptions> options, ILogger<LinuxContainerLegionMetricsPublisher> logger,
39-
IFileSystem fileSystem, ILinuxConsumptionMetricsTracker metricsTracker, IScriptHostManager scriptHostManager,
44+
IFileSystem fileSystem, ILinuxConsumptionMetricsTracker metricsTracker, IServiceProvider serviceProvider,
45+
IOptionsMonitor<FunctionsHostingConfigOptions> functionsHostingConfigOptions,
4046
int? metricsPublishIntervalMS = null)
4147
{
4248
_standbyOptions = standbyOptions ?? throw new ArgumentNullException(nameof(standbyOptions));
4349
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
4450
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
4551
_metricsTracker = metricsTracker ?? throw new ArgumentNullException(nameof(metricsTracker));
46-
_scriptHostManager = scriptHostManager ?? throw new ArgumentNullException(nameof(scriptHostManager));
52+
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
53+
_hostingConfigOptions = functionsHostingConfigOptions ?? throw new ArgumentNullException(nameof(functionsHostingConfigOptions));
4754
_containerName = options.Value.ContainerName;
4855

4956
// Set this to 15 minutes worth of files
@@ -59,27 +66,24 @@ public LinuxContainerLegionMetricsPublisher(IEnvironment environment, IOptionsMo
5966

6067
if (_standbyOptions.CurrentValue.InStandbyMode)
6168
{
62-
_standbyOptionsOnChangeSubscription = _standbyOptions.OnChange(o => OnStandbyOptionsChange(o));
69+
_standbyOptionsOnChangeListener = _standbyOptions.OnChange(o => OnStandbyOptionsChange(o));
6370
}
6471
else
6572
{
6673
Start();
6774
}
75+
76+
_hostingConfigOptionsOnChangeListener = _hostingConfigOptions.OnChange(OnHostingConfigOptionsChanged);
6877
}
6978

70-
public IMetricsLogger MetricsLogger
79+
private IMetricsLogger MetricsLogger => _metricsLogger ??= _serviceProvider.GetRequiredService<IMetricsLogger>();
80+
81+
private void OnHostingConfigOptionsChanged(FunctionsHostingConfigOptions newOptions)
7182
{
72-
get
83+
if (newOptions.IsCGroupMemoryMetricsEnabled != _isCGroupMemoryMetricsEnabled)
7384
{
74-
if (_metricsLogger == null)
75-
{
76-
if (!Utility.TryGetHostService<IMetricsLogger>(_scriptHostManager, out _metricsLogger))
77-
{
78-
throw new InvalidOperationException($"Unable to resolve {nameof(IMetricsLogger)} service.");
79-
}
80-
}
81-
82-
return _metricsLogger;
85+
_logger.LogInformation("CGroup memory metrics enabled: {Enabled}", newOptions.IsCGroupMemoryMetricsEnabled);
86+
_isCGroupMemoryMetricsEnabled = newOptions.IsCGroupMemoryMetricsEnabled;
8387
}
8488
}
8589

@@ -185,11 +189,20 @@ private void OnProcessMonitorTimer(object state)
185189
{
186190
try
187191
{
188-
_process.Refresh();
189-
var commitSizeBytes = _process.WorkingSet64;
190-
if (commitSizeBytes != 0)
192+
long memoryUsageInBytes;
193+
if (_hostingConfigOptions.CurrentValue.IsCGroupMemoryMetricsEnabled)
194+
{
195+
memoryUsageInBytes = CgroupMemoryUsageHelper.GetMemoryUsageInBytes(_logger);
196+
}
197+
else
198+
{
199+
_process.Refresh();
200+
memoryUsageInBytes = _process.WorkingSet64;
201+
}
202+
203+
if (memoryUsageInBytes != 0)
191204
{
192-
AddMemoryActivity(DateTime.UtcNow, commitSizeBytes);
205+
AddMemoryActivity(DateTime.UtcNow, memoryUsageInBytes);
193206
}
194207
}
195208
catch (Exception e)
@@ -244,6 +257,8 @@ public void Dispose()
244257
_metricsPublisherTimer = null;
245258

246259
_metricsTracker.OnDiagnosticEvent -= OnMetricsDiagnosticEvent;
260+
_standbyOptionsOnChangeListener?.Dispose();
261+
_hostingConfigOptionsOnChangeListener?.Dispose();
247262
}
248263

249264
internal class Metrics

src/WebJobs.Script.WebHost/Metrics/LinuxContainerMetricsPublisher.cs

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Concurrent;
66
using System.Diagnostics;
7+
using System.IO;
78
using System.Linq;
89
using System.Net.Http;
910
using System.Net.Http.Formatting;
@@ -19,7 +20,7 @@
1920

2021
namespace Microsoft.Azure.WebJobs.Script.WebHost.Metrics
2122
{
22-
public class LinuxContainerMetricsPublisher : IMetricsPublisher
23+
public sealed class LinuxContainerMetricsPublisher : IMetricsPublisher, IDisposable
2324
{
2425
public const string PublishMemoryActivityPath = "/memoryactivity";
2526
public const string PublishFunctionActivityPath = "/functionactivity";
@@ -36,10 +37,11 @@ public class LinuxContainerMetricsPublisher : IMetricsPublisher
3637
private readonly TimeSpan _metricPublishInterval = TimeSpan.FromMilliseconds(30 * 1000);
3738
private readonly TimeSpan _timerStartDelay = TimeSpan.FromSeconds(2);
3839
private readonly IOptionsMonitor<StandbyOptions> _standbyOptions;
39-
private readonly IDisposable _standbyOptionsOnChangeSubscription;
40+
private readonly IDisposable _standbyOptionsOnChangeListener;
41+
private readonly IDisposable _hostingConfigOptionsOnChangeListener;
4042
private readonly string _requestUri;
4143
private readonly IEnvironment _environment;
42-
private readonly IOptions<FunctionsHostingConfigOptions> _hostingConfigOptions;
44+
private readonly IOptionsMonitor<FunctionsHostingConfigOptions> _hostingConfigOptions;
4345

4446
// Buffer for all memory activities for this container.
4547
private BlockingCollection<MemoryActivity> _memoryActivities;
@@ -64,8 +66,9 @@ public class LinuxContainerMetricsPublisher : IMetricsPublisher
6466
private int _errorCount = 0;
6567
private string _stampName;
6668
private bool _initialized = false;
69+
private bool _isCGroupMemoryMetricsEnabled = false;
6770

68-
public LinuxContainerMetricsPublisher(IEnvironment environment, IOptionsMonitor<StandbyOptions> standbyOptions, ILogger<LinuxContainerMetricsPublisher> logger, HostNameProvider hostNameProvider, IOptions<FunctionsHostingConfigOptions> functionsHostingConfigOptions, HttpClient httpClient = null)
71+
public LinuxContainerMetricsPublisher(IEnvironment environment, IOptionsMonitor<StandbyOptions> standbyOptions, ILogger<LinuxContainerMetricsPublisher> logger, HostNameProvider hostNameProvider, IOptionsMonitor<FunctionsHostingConfigOptions> functionsHostingConfigOptions, HttpClient httpClient = null)
6972
{
7073
_standbyOptions = standbyOptions ?? throw new ArgumentNullException(nameof(standbyOptions));
7174
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
@@ -86,12 +89,23 @@ public LinuxContainerMetricsPublisher(IEnvironment environment, IOptionsMonitor<
8689
_httpClient = (httpClient != null) ? httpClient : CreateMetricsPublisherHttpClient();
8790
if (_standbyOptions.CurrentValue.InStandbyMode)
8891
{
89-
_standbyOptionsOnChangeSubscription = _standbyOptions.OnChange(o => OnStandbyOptionsChange());
92+
_standbyOptionsOnChangeListener = _standbyOptions.OnChange(o => OnStandbyOptionsChange());
9093
}
9194
else
9295
{
9396
Start();
9497
}
98+
99+
_hostingConfigOptionsOnChangeListener = _hostingConfigOptions.OnChange(OnHostingConfigOptionsChanged);
100+
}
101+
102+
private void OnHostingConfigOptionsChanged(FunctionsHostingConfigOptions newOptions)
103+
{
104+
if (newOptions.IsCGroupMemoryMetricsEnabled != _isCGroupMemoryMetricsEnabled)
105+
{
106+
_logger.LogInformation("CGroup memory metrics enabled: {Enabled}", newOptions.IsCGroupMemoryMetricsEnabled);
107+
_isCGroupMemoryMetricsEnabled = newOptions.IsCGroupMemoryMetricsEnabled;
108+
}
95109
}
96110

97111
private void OnStandbyOptionsChange()
@@ -234,7 +248,6 @@ internal async Task SendRequest<T>(ConcurrentQueue<T> activitiesToPublish, strin
234248
try
235249
{
236250
var request = BuildRequest(HttpMethod.Post, publishPath, activitiesToPublish.ToArray());
237-
238251
HttpResponseMessage response = await _httpClient.SendAsync(request);
239252
response.EnsureSuccessStatusCode();
240253
}
@@ -265,11 +278,20 @@ private void OnProcessMonitorTimer(object state)
265278
{
266279
try
267280
{
268-
_process.Refresh();
269-
var commitSizeBytes = _process.WorkingSet64;
270-
if (commitSizeBytes != 0)
281+
long memoryUsageInBytes;
282+
if (_hostingConfigOptions.CurrentValue.IsCGroupMemoryMetricsEnabled)
283+
{
284+
memoryUsageInBytes = CgroupMemoryUsageHelper.GetMemoryUsageInBytes(_logger);
285+
}
286+
else
287+
{
288+
_process.Refresh();
289+
memoryUsageInBytes = _process.WorkingSet64;
290+
}
291+
292+
if (memoryUsageInBytes != 0)
271293
{
272-
AddMemoryActivity(DateTime.UtcNow, commitSizeBytes);
294+
AddMemoryActivity(DateTime.UtcNow, memoryUsageInBytes);
273295
}
274296
}
275297
catch (Exception e)
@@ -292,7 +314,7 @@ private HttpRequestMessage BuildRequest<TContent>(HttpMethod method, string path
292314
request.Headers.Add(HostNameHeader, _hostNameProvider.Value);
293315
request.Headers.Add(StampNameHeader, _stampName);
294316

295-
if (_hostingConfigOptions.Value.SwtIssuerEnabled)
317+
if (_hostingConfigOptions.CurrentValue.SwtIssuerEnabled)
296318
{
297319
string swtToken = SimpleWebTokenHelper.CreateToken(DateTime.UtcNow.AddMinutes(5));
298320
request.Headers.Add(ScriptConstants.SiteRestrictedTokenHeaderName, swtToken);
@@ -313,5 +335,16 @@ public void OnFunctionCompleted(string functionName, string invocationId)
313335
{
314336
// nothing to do
315337
}
338+
339+
public void Dispose()
340+
{
341+
_standbyOptionsOnChangeListener?.Dispose();
342+
_hostingConfigOptionsOnChangeListener?.Dispose();
343+
_processMonitorTimer?.Dispose();
344+
_metricsPublisherTimer?.Dispose();
345+
_httpClient?.Dispose();
346+
_memoryActivities?.Dispose();
347+
_functionActivities?.Dispose();
348+
}
316349
}
317350
}

src/WebJobs.Script.WebHost/WebHostServiceCollectionExtensions.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4+
using System;
45
using System.IO.Abstractions;
56
using System.Net.Http;
67
using System.Runtime.InteropServices;
@@ -304,8 +305,9 @@ private static void AddLinuxContainerServices(this IServiceCollection services)
304305
var logger = s.GetService<ILogger<LinuxContainerLegionMetricsPublisher>>();
305306
var metricsTracker = s.GetService<ILinuxConsumptionMetricsTracker>();
306307
var standbyOptions = s.GetService<IOptionsMonitor<StandbyOptions>>();
307-
var scriptHostManager = s.GetService<IScriptHostManager>();
308-
return new LinuxContainerLegionMetricsPublisher(environment, standbyOptions, options, logger, new FileSystem(), metricsTracker, scriptHostManager);
308+
var serviceProvider = s.GetService<IServiceProvider>();
309+
var hostingConfigOptions = s.GetService<IOptionsMonitor<FunctionsHostingConfigOptions>>();
310+
return new LinuxContainerLegionMetricsPublisher(environment, standbyOptions, options, logger, new FileSystem(), metricsTracker, serviceProvider, hostingConfigOptions);
309311
}
310312
else if (environment.IsFlexConsumptionSku())
311313
{
@@ -320,7 +322,7 @@ private static void AddLinuxContainerServices(this IServiceCollection services)
320322
var logger = s.GetService<ILogger<LinuxContainerMetricsPublisher>>();
321323
var standbyOptions = s.GetService<IOptionsMonitor<StandbyOptions>>();
322324
var hostNameProvider = s.GetService<HostNameProvider>();
323-
var hostingConfigOptions = s.GetService<IOptions<FunctionsHostingConfigOptions>>();
325+
var hostingConfigOptions = s.GetService<IOptionsMonitor<FunctionsHostingConfigOptions>>();
324326
return new LinuxContainerMetricsPublisher(environment, standbyOptions, logger, hostNameProvider, hostingConfigOptions);
325327
}
326328

src/WebJobs.Script/Config/FunctionsHostingConfigOptions.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,22 @@ internal bool SwtIssuerEnabled
7878
}
7979
}
8080

81+
/// <summary>
82+
/// Gets or sets a value indicating whether to use cgroup memory metrics for reporting memory usage.
83+
/// </summary>
84+
internal bool IsCGroupMemoryMetricsEnabled
85+
{
86+
get
87+
{
88+
return GetFeatureAsBooleanOrDefault(ScriptConstants.FeatureFlagEnableCGroupMemoryMetrics, false);
89+
}
90+
91+
set
92+
{
93+
_features[ScriptConstants.FeatureFlagEnableCGroupMemoryMetrics] = value ? "1" : "0";
94+
}
95+
}
96+
8197
/// <summary>
8298
/// Gets or sets a string delimited by '|' that contains a list of admin APIs that are allowed to
8399
/// be invoked internally by platform components.

src/WebJobs.Script/ScriptConstants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ public static class ScriptConstants
142142
public const string FeatureFlagDisableOrderedInvocationMessages = "DisableOrderedInvocationMessages";
143143
public const string FeatureFlagEnableAzureMonitorTimeIsoFormat = "EnableAzureMonitorTimeIsoFormat";
144144
public const string FeatureFlagEnableTestDataSuppression = "EnableTestDataSuppression";
145+
public const string FeatureFlagEnableCGroupMemoryMetrics = "EnableCGroupMemoryMetrics";
145146
public const string HostingConfigDisableLinuxAppServiceDetailedExecutionEvents = "DisableLinuxExecutionDetails";
146147
public const string HostingConfigDisableLinuxAppServiceExecutionEventLogBackoff = "DisableLinuxLogBackoff";
147148
public const string FeatureFlagEnableLegacyDurableVersionCheck = "EnableLegacyDurableVersionCheck";

0 commit comments

Comments
 (0)