Skip to content
Draft
Show file tree
Hide file tree
Changes from 10 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Globalization;

namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes;

public class KubernetesMetadata
{
/// <summary>
/// Gets or sets the resource memory limit the container is allowed to use.
/// </summary>
public ulong LimitsMemory { get; set; }

/// <summary>
/// Gets or sets the resource CPU limit the container is allowed to use.
/// </summary>
public ulong LimitsCpu { get; set; }

/// <summary>
/// Gets or sets the resource memory request the container is allowed to use.
/// </summary>
public ulong RequestsMemory { get; set; }

/// <summary>
/// Gets or sets the resource CPU request the container is allowed to use.
/// </summary>
public ulong RequestsCpu { get; set; }

private string _environmentVariablePrefix;

public KubernetesMetadata(string environmentVariablePrefix)
{
_environmentVariablePrefix = environmentVariablePrefix;
}

public void Build()
{
LimitsMemory = Convert.ToUInt64(Environment.GetEnvironmentVariable($"{_environmentVariablePrefix}LIMITS_MEMORY"), CultureInfo.InvariantCulture);
LimitsCpu = Convert.ToUInt64(Environment.GetEnvironmentVariable($"{_environmentVariablePrefix}LIMITS_CPU"), CultureInfo.InvariantCulture);
RequestsMemory = Convert.ToUInt64(Environment.GetEnvironmentVariable($"{_environmentVariablePrefix}REQUESTS_MEMORY"), CultureInfo.InvariantCulture);
RequestsCpu = Convert.ToUInt64(Environment.GetEnvironmentVariable($"{_environmentVariablePrefix}REQUESTS_CPU"), CultureInfo.InvariantCulture);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes;

internal class KubernetesResourceQuotasProvider : IResourceQuotasProvider
{
private KubernetesMetadata _cosmicMetadata;

public KubernetesResourceQuotasProvider(KubernetesMetadata cosmicMetadata) // we could inject ClusterMetadataHere in the future instead of that thingy
{
_cosmicMetadata = cosmicMetadata;
}

public ResourceQuotas GetResourceQuotas()
{
// extract relevant cluster metadata
return new ResourceQuotas
{
RequestsCpu = ConvertMillicoreToUnit(_cosmicMetadata.RequestsCpu),
LimitsCpu = ConvertMillicoreToUnit(_cosmicMetadata.LimitsCpu),
RequestsMemory = _cosmicMetadata.RequestsMemory,
LimitsMemory = _cosmicMetadata.LimitsMemory,
};
}

private static double ConvertMillicoreToUnit(ulong millicores)
{
return millicores / 1000.0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
///
/// </summary>
public static class KubernetesResourceQuotasServiceCollectionsExtensions
{
/// <summary>
///
/// </summary>
/// <param name="services"></param>
/// <param name="configureOptions"></param>
/// <returns></returns>
public static IServiceCollection AddKubernetesResourceQuotas(
this IServiceCollection services,
Action<KubernetesMetadata>? configureOptions = null)
{
// Simple - just register before AddResourceMonitoring() is called
services.TryAddSingleton<IResourceQuotasProvider, KubernetesResourceQuotasProvider>();

if (configureOptions != null)
{
_ = services.Configure(configureOptions);
}

services.AddResourceMonitoring();

return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes</RootNamespace>
<Description>Measures processor and memory usage.</Description>
<Workstream>ResourceMonitoring</Workstream>
<NoWarn Condition="'$(TargetFramework)' == 'net462'">$(NoWarn);CS0436</NoWarn>
</PropertyGroup>

<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
<UseLoggingGenerator>true</UseLoggingGenerator>
<InjectSharedDataValidation>true</InjectSharedDataValidation>
<InjectSharedRentedSpan>true</InjectSharedRentedSpan>
<InjectExperimentalAttributeOnLegacy>true</InjectExperimentalAttributeOnLegacy>
<InjectObsoleteAttributeOnLegacy>true</InjectObsoleteAttributeOnLegacy>
<InjectPlatformAttributesOnLegacy>true</InjectPlatformAttributesOnLegacy>
<InjectSharedBufferWriterPool>true</InjectSharedBufferWriterPool>
<InjectSharedDiagnosticIds>true</InjectSharedDiagnosticIds>
<InjectStringSplitExtensions>true</InjectStringSplitExtensions>
<InjectSharedInstruments>true</InjectSharedInstruments>
</PropertyGroup>

<PropertyGroup>
<Stage>normal</Stage>
<MinCodeCoverage>99</MinCodeCoverage>
<MinMutationScore>90</MinMutationScore>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net462'">
<Compile Remove="Linux\**\*.cs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Microsoft.Extensions.ClusterMetadata.Kubernetes\Microsoft.Extensions.ClusterMetadata.Kubernetes.csproj" />
<ProjectReference Include="..\Microsoft.Extensions.Telemetry.Abstractions\Microsoft.Extensions.Telemetry.Abstractions.csproj" />
<ProjectReference Include="..\Microsoft.Extensions.Diagnostics.ResourceMonitoring\Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj" />
<ProjectReference Include="..\Microsoft.Extensions.DependencyInjection.AutoActivation\Microsoft.Extensions.DependencyInjection.AutoActivation.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Bcl.HashCode" Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))" />
<PackageReference Include="Microsoft.Bcl.TimeProvider" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Microsoft.Extensions.Diagnostics" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
<PackageReference Include="System.Collections.Immutable" Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" />
<PackageReference Include="System.Threading.Tasks.Extensions" Condition="'$(TargetFramework)' == 'net462'" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleToDynamicProxyGenAssembly2 Include="*" />
<InternalsVisibleToTest Include="$(AssemblyName).Tests" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring;

public interface IResourceQuotasProvider
{
public ResourceQuotas GetResourceQuotas();
}

Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ internal sealed class LinuxUtilizationProvider : ISnapshotProvider
private readonly object _memoryLocker = new();
private readonly ILogger<LinuxUtilizationProvider> _logger;
private readonly ILinuxUtilizationParser _parser;
private readonly ulong _memoryLimit;
private double _memoryLimit;
private double _cpuLimit;
private ulong _memoryRequest;
private double _cpuRequest;
private readonly long _cpuPeriodsInterval;
private readonly TimeSpan _cpuRefreshInterval;
private readonly TimeSpan _memoryRefreshInterval;
Expand All @@ -43,8 +46,15 @@ internal sealed class LinuxUtilizationProvider : ISnapshotProvider
private long _previousCgroupCpuPeriodCounter;
public SystemResources Resources { get; }

public LinuxUtilizationProvider(IOptions<ResourceMonitoringOptions> options, ILinuxUtilizationParser parser,
IMeterFactory meterFactory, ILogger<LinuxUtilizationProvider>? logger = null, TimeProvider? timeProvider = null)
private IResourceQuotasProvider _resourceQuotasProvider;

public LinuxUtilizationProvider(
IOptions<ResourceMonitoringOptions> options,
ILinuxUtilizationParser parser,
IMeterFactory meterFactory,
IResourceQuotasProvider resourceQuotasProvider,
ILogger<LinuxUtilizationProvider>? logger = null,
TimeProvider? timeProvider = null)
{
_parser = parser;
_logger = logger ?? NullLogger<LinuxUtilizationProvider>.Instance;
Expand All @@ -54,15 +64,19 @@ public LinuxUtilizationProvider(IOptions<ResourceMonitoringOptions> options, ILi
_memoryRefreshInterval = options.Value.MemoryConsumptionRefreshInterval;
_refreshAfterCpu = now;
_refreshAfterMemory = now;
_memoryLimit = _parser.GetAvailableMemoryInBytes();
_previousHostCpuTime = _parser.GetHostCpuUsageInNanoseconds();
_previousCgroupCpuTime = _parser.GetCgroupCpuUsageInNanoseconds();

_resourceQuotasProvider = resourceQuotasProvider;
var quotas = _resourceQuotasProvider.GetResourceQuotas();
_memoryLimit = quotas.LimitsMemory;
_cpuLimit = quotas.LimitsCpu;
_cpuRequest = quotas.RequestsCpu;
_memoryRequest = quotas.RequestsMemory;

float hostCpus = _parser.GetHostCpuCount();
float cpuLimit = _parser.GetCgroupLimitedCpus();
float cpuRequest = _parser.GetCgroupRequestCpu();
float scaleRelativeToCpuLimit = hostCpus / cpuLimit;
float scaleRelativeToCpuRequest = hostCpus / cpuRequest;
double scaleRelativeToCpuLimit = hostCpus / _cpuLimit;
double scaleRelativeToCpuRequest = hostCpus / _cpuRequest;
_scaleRelativeToCpuRequestForTrackerApi = hostCpus; // the division by cpuRequest is performed later on in the ResourceUtilization class

#pragma warning disable CA2000 // Dispose objects before losing scope
Expand All @@ -74,21 +88,24 @@ public LinuxUtilizationProvider(IOptions<ResourceMonitoringOptions> options, ILi

if (options.Value.UseLinuxCalculationV2)
{
cpuLimit = _parser.GetCgroupLimitV2();
cpuRequest = _parser.GetCgroupRequestCpuV2();
if (!options.Value.EmitRequestsMetrics)
{
_cpuLimit = _parser.GetCgroupLimitV2();
_cpuRequest = _parser.GetCgroupRequestCpuV2();
}

// Get Cpu periods interval from cgroup
_cpuPeriodsInterval = _parser.GetCgroupPeriodsIntervalInMicroSecondsV2();
(_previousCgroupCpuTime, _previousCgroupCpuPeriodCounter) = _parser.GetCgroupCpuUsageInNanosecondsAndCpuPeriodsV2();

_ = meter.CreateObservableGauge(
name: ResourceUtilizationInstruments.ContainerCpuLimitUtilization,
observeValues: () => GetMeasurementWithRetry(() => CpuUtilizationLimit(cpuLimit)),
observeValues: () => GetMeasurementWithRetry(() => CpuUtilizationLimit(_cpuLimit)),
unit: "1");

_ = meter.CreateObservableGauge(
name: ResourceUtilizationInstruments.ContainerCpuRequestUtilization,
observeValues: () => GetMeasurementWithRetry(() => CpuUtilizationRequest(cpuRequest)),
observeValues: () => GetMeasurementWithRetry(() => CpuUtilizationRequest(_cpuRequest)),
unit: "1");

_ = meter.CreateObservableGauge(
Expand Down Expand Up @@ -119,6 +136,14 @@ public LinuxUtilizationProvider(IOptions<ResourceMonitoringOptions> options, ILi
observeValues: () => GetMeasurementWithRetry(MemoryPercentage),
unit: "1");

if (options.Value.EmitRequestsMetrics)
{
_ = meter.CreateObservableGauge(
name: ResourceUtilizationInstruments.ContainerMemoryRequestUtilization,
observeValue: () => GetMeasurementWithRetry(MemoryPercantageForRequest), // todo implement
unit: "1");
}

_ = meter.CreateObservableUpDownCounter(
name: ResourceUtilizationInstruments.ContainerMemoryUsage,
observeValues: () => GetMeasurementWithRetry(() => (long)MemoryUsage()),
Expand All @@ -130,12 +155,9 @@ public LinuxUtilizationProvider(IOptions<ResourceMonitoringOptions> options, ILi
observeValues: () => GetMeasurementWithRetry(MemoryPercentage),
unit: "1");

// cpuRequest is a CPU request (aka guaranteed number of CPU units) for pod, for host its 1 core
// cpuLimit is a CPU limit (aka max CPU units available) for a pod or for a host.
// _memoryLimit - Resource Memory Limit (in k8s terms)
// _memoryLimit - To keep the contract, this parameter will get the Host available memory
Resources = new SystemResources(cpuRequest, cpuLimit, _memoryLimit, _memoryLimit);
_logger.SystemResourcesInfo(cpuLimit, cpuRequest, _memoryLimit, _memoryLimit);
ulong memoryLimitRounded = (ulong)Math.Round(_memoryLimit);
Resources = new SystemResources(_cpuRequest, _cpuLimit, _memoryRequest, memoryLimitRounded);
_logger.SystemResourcesInfo(_cpuLimit, _cpuRequest, memoryLimitRounded, _memoryRequest);
}

public double CpuUtilizationV2()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring;

internal class LinuxResourceQuotasProvider : IResourceQuotasProvider

{
public ResourceQuotas GetResourceQuotas()
{
// bring logic from LinuxUtilizationProvider for limits and requests
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Microsoft.Extensions.ClusterMetadata.Kubernetes\Microsoft.Extensions.ClusterMetadata.Kubernetes.csproj" />
<ProjectReference Include="..\Microsoft.Extensions.Telemetry.Abstractions\Microsoft.Extensions.Telemetry.Abstractions.csproj" />
<ProjectReference Include="..\Microsoft.Extensions.DependencyInjection.AutoActivation\Microsoft.Extensions.DependencyInjection.AutoActivation.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,6 @@ public partial class ResourceMonitoringOptions
/// <remarks>Previously <c>EnableDiskIoMetrics</c>.</remarks>
[Experimental(diagnosticId: DiagnosticIds.Experiments.ResourceMonitoring, UrlFormat = DiagnosticIds.UrlFormat)]
public bool EnableSystemDiskIoMetrics { get; set; }

public bool EmitRequestsMetrics { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Runtime.Versioning;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Diagnostics.ResourceMonitoring;

#if !NETFRAMEWORK
using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux;
using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Disk;
Expand Down Expand Up @@ -114,6 +115,7 @@ private static void PickWindowsSnapshotProvider(this ResourceMonitorBuilder buil
if (JobObjectInfo.SafeJobHandle.IsProcessInJob())
{
builder.Services.TryAddSingleton<ISnapshotProvider, WindowsContainerSnapshotProvider>();
builder.Services.TryAddSingleton<IResourceQuotasProvider, WindowsContainerResourceQuotasProvider>();
}
else
{
Expand All @@ -127,6 +129,7 @@ private static ResourceMonitorBuilder AddLinuxProvider(this ResourceMonitorBuild
_ = Throw.IfNull(builder);

builder.Services.TryAddActivatedSingleton<ISnapshotProvider, LinuxUtilizationProvider>();
builder.Services.TryAddSingleton<IResourceQuotasProvider, LinuxResourceQuotasProvider>();

builder.Services.TryAddSingleton(TimeProvider.System);
builder.Services.TryAddSingleton<IFileSystem, OSFileSystem>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring;

public class ResourceQuotas
{
/// <summary>
/// Gets or sets the resource memory limit the container is allowed to use.
/// </summary>
public ulong LimitsMemory { get; set; }

/// <summary>
/// Gets or sets the resource CPU limit the container is allowed to use.
/// </summary>
public ulong LimitsCpu { get; set; }

/// <summary>
/// Gets or sets the resource memory request the container is allowed to use.
/// </summary>
public ulong RequestsMemory { get; set; }

/// <summary>
/// Gets or sets the resource CPU request the container is allowed to use.
/// </summary>
public ulong RequestsCpu { get; set; }
}
Loading
Loading