Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions release_notes.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
### Release notes

<!-- Please add your release notes in the following format:
- My change description (#PR)
-->
- Improved memory metrics reporting using CGroup data for Linux consumption (#10968)
- Fixing GrpcWorkerChannel concurrency bug (#10998)
- Avoid circular dependency when resolving LinuxContainerLegionMetricsPublisher. (#10991)
### Release notes

<!-- Please add your release notes in the following format:
- My change description (#PR)
-->
- Improved memory metrics reporting using CGroup data for Linux consumption (#10968)
- Memory allocation optimizations in `RpcWorkerConfigFactory.AddProviders` (#10959)
- Fixing GrpcWorkerChannel concurrency bug (#10998)
- Avoid circular dependency when resolving LinuxContainerLegionMetricsPublisher. (#10991)
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,15 @@ internal EnvironmentCondition(ILogger logger, IEnvironment environment, WorkerPr
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_environment = environment ?? throw new ArgumentNullException(nameof(environment));

descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionName, out _name);
descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionExpression, out _expression);
if (descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionName, out var conditionNameElement))
{
_name = conditionNameElement.GetString();
}

if (descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionExpression, out var conditionExpressionElement))
Comment thread
kshyju marked this conversation as resolved.
{
_expression = conditionExpressionElement.GetString();
}

Validate();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,15 @@ public HostPropertyCondition(ILogger logger, ISystemRuntimeInformation systemRun
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_systemRuntimeInformation = systemRuntimeInformation ?? throw new ArgumentNullException(nameof(systemRuntimeInformation));

descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionName, out _name);
descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionExpression, out _expression);
if (descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionName, out var conditionNameElement))
{
_name = conditionNameElement.GetString();
}

if (descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionExpression, out var conditionExpressionElement))
Comment thread
kshyju marked this conversation as resolved.
{
_expression = conditionExpressionElement.GetString();
}

Validate();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,18 @@
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Microsoft.Azure.WebJobs.Script.Workers.Profiles
{
public sealed class WorkerProfileConditionDescriptor
{
[JsonExtensionData]
#pragma warning disable CS0649 // The value is assigned by the serializer
private IDictionary<string, JToken> _extensionData;
#pragma warning restore CS0649

private IDictionary<string, string> _properties;

[JsonProperty(Required = Required.Always, PropertyName = WorkerConstants.WorkerDescriptionProfileConditionType)]
[JsonRequired]
[JsonPropertyName(WorkerConstants.WorkerDescriptionProfileConditionType)]
public string Type { get; set; }

public IDictionary<string, string> Properties
{
get
{
if (_properties == null)
{
_properties = _extensionData?.ToDictionary(kv => kv.Key, kv => kv.Value.ToString()) ?? new Dictionary<string, string>();
}

return _properties;
}
}
[JsonExtensionData]
public Dictionary<string, JsonElement> Properties { get; set; } = new Dictionary<string, JsonElement>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.RegularExpressions;
using Microsoft.Azure.WebJobs.Script.Diagnostics;
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;

namespace Microsoft.Azure.WebJobs.Script.Workers.Rpc
{
Expand All @@ -24,6 +24,10 @@ internal class RpcWorkerConfigFactory
private readonly IMetricsLogger _metricsLogger;
private readonly string _workerRuntime;
private readonly IEnvironment _environment;
private readonly JsonSerializerOptions _jsonSerializerOptions = new()
{
PropertyNameCaseInsensitive = true
};

private Dictionary<string, RpcWorkerConfig> _workerDescriptionDictionary = new Dictionary<string, RpcWorkerConfig>();

Expand Down Expand Up @@ -116,6 +120,18 @@ internal void AddProvider(string workerDir)
{
try
{
// After specialization, load worker config only for the specified runtime unless it's a multi-language app.
if (!string.IsNullOrWhiteSpace(_workerRuntime) && !_environment.IsPlaceholderModeEnabled() && !_environment.IsMultiLanguageRuntimeEnvironment())
{
string workerRuntime = Path.GetFileName(workerDir);
// Only skip worker directories that don't match the current runtime.
// Do not skip non-worker directories like the function app payload directory
if (!workerRuntime.Equals(_workerRuntime, StringComparison.OrdinalIgnoreCase) && workerDir.StartsWith(WorkersDirPath))
{
return;
}
}

string workerConfigPath = Path.Combine(workerDir, RpcWorkerConstants.WorkerConfigFileName);

if (!File.Exists(workerConfigPath))
Expand All @@ -126,15 +142,13 @@ internal void AddProvider(string workerDir)

_logger.LogDebug("Found worker config: {workerConfigPath}", workerConfigPath);

// Parse worker config file
string json = File.ReadAllText(workerConfigPath);
JObject workerConfig = JObject.Parse(json);
RpcWorkerDescription workerDescription = workerConfig.Property(WorkerConstants.WorkerDescription).Value.ToObject<RpcWorkerDescription>();
var workerConfig = GetWorkerConfigJsonElement(workerConfigPath);
var workerDescriptionElement = workerConfig.GetProperty(WorkerConstants.WorkerDescription);
var workerDescription = workerDescriptionElement.Deserialize<RpcWorkerDescription>(_jsonSerializerOptions);
workerDescription.WorkerDirectory = workerDir;

//Read the profiles from worker description and load the profile for which the conditions match
JToken profiles = workerConfig.GetValue(WorkerConstants.WorkerDescriptionProfiles);
if (profiles != null)
// Read the profiles from worker description and load the profile for which the conditions match
if (workerConfig.TryGetProperty(WorkerConstants.WorkerDescriptionProfiles, out var profiles))
{
List<WorkerDescriptionProfile> workerDescriptionProfiles = ReadWorkerDescriptionProfiles(profiles);
if (workerDescriptionProfiles.Count > 0)
Expand All @@ -146,7 +160,7 @@ internal void AddProvider(string workerDir)

// Check if any app settings are provided for that language
var languageSection = _config.GetSection($"{RpcWorkerConstants.LanguageWorkersSectionName}:{workerDescription.Language}");
workerDescription.Arguments = workerDescription.Arguments ?? new List<string>();
workerDescription.Arguments ??= new List<string>();
GetWorkerDescriptionFromAppSettings(workerDescription, languageSection);
AddArgumentsFromAppSettings(workerDescription, languageSection);

Expand Down Expand Up @@ -197,9 +211,24 @@ internal void AddProvider(string workerDir)
}
}

private List<WorkerDescriptionProfile> ReadWorkerDescriptionProfiles(JToken profilesJToken)
private static JsonElement GetWorkerConfigJsonElement(string workerConfigPath)
{
var profiles = profilesJToken.ToObject<IList<WorkerProfileDescriptor>>();
ReadOnlySpan<byte> jsonSpan = File.ReadAllBytes(workerConfigPath);

if (jsonSpan.StartsWith<byte>([0xEF, 0xBB, 0xBF]))
{
jsonSpan = jsonSpan[3..]; // Skip UTF-8 Byte Order Mark (BOM) if present at the beginning of the file.
}
Comment thread
kshyju marked this conversation as resolved.

var reader = new Utf8JsonReader(jsonSpan, isFinalBlock: true, state: default);
using var doc = JsonDocument.ParseValue(ref reader);

return doc.RootElement.Clone();
}

private List<WorkerDescriptionProfile> ReadWorkerDescriptionProfiles(JsonElement profilesElement)
{
var profiles = profilesElement.Deserialize<IList<WorkerProfileDescriptor>>(_jsonSerializerOptions);

if (profiles == null || profiles.Count <= 0)
{
Expand Down Expand Up @@ -237,11 +266,16 @@ private List<WorkerDescriptionProfile> ReadWorkerDescriptionProfiles(JToken prof
return descriptionProfiles;
}

internal WorkerProcessCountOptions GetWorkerProcessCount(JObject workerConfig)
internal WorkerProcessCountOptions GetWorkerProcessCount(JsonElement workerConfig)
{
WorkerProcessCountOptions workerProcessCount = workerConfig.Property(WorkerConstants.ProcessCount)?.Value.ToObject<WorkerProcessCountOptions>();
WorkerProcessCountOptions workerProcessCount = null;

if (workerConfig.TryGetProperty(WorkerConstants.ProcessCount, out var processCountElement))
{
workerProcessCount = processCountElement.Deserialize<WorkerProcessCountOptions>();
}

workerProcessCount = workerProcessCount ?? new WorkerProcessCountOptions();
workerProcessCount ??= new WorkerProcessCountOptions();

if (workerProcessCount.SetProcessCountToNumberOfCpuCores)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Microsoft.Azure.WebJobs.Script.Workers;
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
using Xunit;
Expand All @@ -28,8 +28,8 @@ public void EnvironmentConditionTest_ThrowsValidationException(string name, stri
var descriptor = new WorkerProfileConditionDescriptor();
descriptor.Type = WorkerConstants.WorkerDescriptionProfileEnvironmentCondition;

descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = name;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = expression;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = JsonSerializer.SerializeToElement(name);
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = JsonSerializer.SerializeToElement(expression);

Assert.Throws<ValidationException>(() => new EnvironmentCondition(testLogger, _testEnvironment, descriptor));
}
Expand All @@ -45,8 +45,8 @@ public void EnvironmentConditionTest_EvaluateTrue(string name, string testExpres

var descriptor = new WorkerProfileConditionDescriptor();
descriptor.Type = WorkerConstants.WorkerDescriptionProfileEnvironmentCondition;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = name;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = testExpression;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = JsonSerializer.SerializeToElement(name);
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = JsonSerializer.SerializeToElement(testExpression);

var environmentCondition = new EnvironmentCondition(testLogger, _testEnvironment, descriptor);

Expand All @@ -64,8 +64,8 @@ public void EnvironmentConditionTest_EvaluateFalse(string name, string testExpre

var descriptor = new WorkerProfileConditionDescriptor();
descriptor.Type = WorkerConstants.WorkerDescriptionProfileEnvironmentCondition;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = name;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = testExpression;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = JsonSerializer.SerializeToElement(name);
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = JsonSerializer.SerializeToElement(testExpression);

var environmentCondition = new EnvironmentCondition(testLogger, _testEnvironment, descriptor);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.ComponentModel.DataAnnotations;
using System.Linq.Expressions;
using System.Text.Json;
using Microsoft.Azure.WebJobs.Script.Workers;
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
using Xunit;
Expand Down Expand Up @@ -29,8 +31,8 @@ public void HostPropertyConditionTest_ThrowsValidationException(string name, str
var descriptor = new WorkerProfileConditionDescriptor();
descriptor.Type = WorkerConstants.WorkerDescriptionProfileHostPropertyCondition;

descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = name;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = expression;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = JsonSerializer.SerializeToElement(name);
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = JsonSerializer.SerializeToElement(expression);

Assert.Throws<ValidationException>(() => new HostPropertyCondition(testLogger, _testSystemRuntimeInfo, descriptor));
}
Expand All @@ -45,8 +47,8 @@ public void HostPropertyConditionTest_EvaluateTrue(string name, string testExpre

var descriptor = new WorkerProfileConditionDescriptor();
descriptor.Type = WorkerConstants.WorkerDescriptionProfileHostPropertyCondition;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = name;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = testExpression;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = JsonSerializer.SerializeToElement(name);
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = JsonSerializer.SerializeToElement(testExpression);

var hostPropertyCondition = new HostPropertyCondition(testLogger, _testSystemRuntimeInfo, descriptor);

Expand All @@ -63,8 +65,8 @@ public void HostPropertyConditionTest_EvaluateFalse(string name, string testExpr

var descriptor = new WorkerProfileConditionDescriptor();
descriptor.Type = WorkerConstants.WorkerDescriptionProfileHostPropertyCondition;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = name;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = testExpression;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = JsonSerializer.SerializeToElement(name);
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = JsonSerializer.SerializeToElement(testExpression);

var hostPropertyCondition = new HostPropertyCondition(testLogger, _testSystemRuntimeInfo, descriptor);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Text.Json;
using Microsoft.Azure.WebJobs.Script.Workers;
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -29,8 +30,8 @@ public static EnvironmentCondition GetTestEnvironmentCondition(ILogger logger, T
{
var descriptor = new WorkerProfileConditionDescriptor();
descriptor.Type = WorkerConstants.WorkerDescriptionProfileEnvironmentCondition;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = name;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = expression;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = JsonSerializer.SerializeToElement(name);
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = JsonSerializer.SerializeToElement(expression);

return new EnvironmentCondition(logger, testEnvironment, descriptor);
}
Expand All @@ -39,8 +40,8 @@ public static HostPropertyCondition GetTestHostPropertyCondition(ILogger logger,
{
var descriptor = new WorkerProfileConditionDescriptor();
descriptor.Type = WorkerConstants.WorkerDescriptionProfileHostPropertyCondition;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = name;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = expression;
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = JsonSerializer.SerializeToElement(name);
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = JsonSerializer.SerializeToElement(expression);

return new HostPropertyCondition(logger, testSystemRuntimeInfo, descriptor);
}
Expand Down
Loading