Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report container memory usage for Resource Monitoring Windows Container #5301

Merged
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
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using static Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Interop.ProcessInfo;

namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Interop;

/// <summary>
/// An interface to enable the mocking of process information retrieval.
/// An interface to enable the mocking of memory usage information retrieval.
/// </summary>
internal interface IProcessInfo
{
/// <summary>
/// Retrieve the current application memory information.
/// Retrieve the memory usage of a system.
/// </summary>
/// <returns>An appropriate memory data structure.</returns>
APP_MEMORY_INFORMATION GetCurrentAppMemoryInfo();
/// <returns>Memory usage amount in bytes.</returns>
ulong GetMemoryUsage();
}
Original file line number Diff line number Diff line change
@@ -1,88 +1,41 @@
// 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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;

namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Interop;

/// <summary>
/// Process native methods class.
/// </summary>
/// <remarks>This will not be covered by UTs, as those
/// classes have insufficient and inconsistent privileges,
/// depending on runtime environment.</remarks>
[ExcludeFromCodeCoverage]
internal static class ProcessInfo
internal sealed class ProcessInfo : IProcessInfo
{
private enum PROCESS_INFORMATION_CLASS
public ulong GetMemoryUsage()
{
ProcessAppMemoryInfo = 2
}

/// <summary>
/// The APP_MEMORY_INFORMATION structure.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct APP_MEMORY_INFORMATION
{
public ulong AvailableCommit;
public ulong PrivateCommitUsage;
public ulong PeakPrivateCommitUsage;
public ulong TotalCommitUsage;
}

/// <summary>
/// Retrieve the current application memory information.
/// </summary>
/// <returns>An appropriate memory data structure.</returns>
public static APP_MEMORY_INFORMATION GetCurrentAppMemoryInfo()
{
unsafe
{
APP_MEMORY_INFORMATION info = default;
void* buffer = &info;
using var currentProcess = Process.GetCurrentProcess();
NtGetProcessInformation(
currentProcess.Handle,
PROCESS_INFORMATION_CLASS.ProcessAppMemoryInfo,
buffer,
sizeof(APP_MEMORY_INFORMATION));

return info;
}
}

/// <summary>
/// Get process information.
/// </summary>
/// <param name="handle">The handle of the object to query.</param>
/// <param name="infoClass">Process info class.</param>
/// <param name="buffer">Buffer containing the limit.</param>
/// <param name="size">Buffer size.</param>
private static unsafe void NtGetProcessInformation(IntPtr handle, PROCESS_INFORMATION_CLASS infoClass, void* buffer, int size)
{
if (!UnsafeNativeMethods.GetProcessInformation(
handle,
infoClass,
buffer,
size))
ulong memoryUsage = 0;
var processes = Process.GetProcesses();
foreach (var process in processes)
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
try
{
memoryUsage += (ulong)process.WorkingSet64;
}
#pragma warning disable CA1031 // Do not catch general exception types
catch
#pragma warning restore CA1031 // Do not catch general exception types
{
// Ignore various exceptions including, but not limited:
// AccessDenied (from kernel processes),
// InvalidOperation (process does not exist anymore)
// and silently continue to the next process.
}
finally
{
#pragma warning disable EA0011 // Consider removing unnecessary conditional access operator (?)
process?.Dispose();
#pragma warning restore EA0011 // Consider removing unnecessary conditional access operator (?)
}
}
}

private static class UnsafeNativeMethods
{
[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[return: MarshalAs(UnmanagedType.Bool)]
public static unsafe extern bool GetProcessInformation(
IntPtr processHandle,
PROCESS_INFORMATION_CLASS processInformationClass,
void* processInformation,
int processInformationSize);
return memoryUsage;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public WindowsContainerSnapshotProvider(
ILogger<WindowsContainerSnapshotProvider> logger,
IMeterFactory meterFactory,
IOptions<ResourceMonitoringOptions> options)
: this(new MemoryInfo(), new SystemInfo(), new ProcessInfoWrapper(), logger, meterFactory,
: this(new MemoryInfo(), new SystemInfo(), new ProcessInfo(), logger, meterFactory,
static () => new JobHandleWrapper(), TimeProvider.System, options.Value)
{
}
Expand Down Expand Up @@ -169,12 +169,7 @@ private ulong GetMemoryLimits(IJobHandle jobHandle)
/// Gets memory usage within the system.
/// </summary>
/// <returns>Memory usage within the system in bytes.</returns>
private ulong GetMemoryUsage()
{
var memoryInfo = _processInfo.GetCurrentAppMemoryInfo();

return memoryInfo.TotalCommitUsage;
}
private ulong GetMemoryUsage() => _processInfo.GetMemoryUsage();

private double MemoryPercentage()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public sealed class WindowsContainerSnapshotProviderTests
private SYSTEM_INFO _sysInfo;
private JOBOBJECT_BASIC_ACCOUNTING_INFORMATION _accountingInfo;
private JOBOBJECT_CPU_RATE_CONTROL_INFORMATION _cpuLimit;
private ProcessInfo.APP_MEMORY_INFORMATION _appMemoryInfo;
private ulong _appMemoryUsage;
private JOBOBJECT_EXTENDED_LIMIT_INFORMATION _limitInfo;

public WindowsContainerSnapshotProviderTests()
Expand Down Expand Up @@ -60,9 +60,9 @@ public WindowsContainerSnapshotProviderTests()
_jobHandleMock.Setup(j => j.GetExtendedLimitInfo())
.Returns(() => _limitInfo);

_appMemoryInfo.TotalCommitUsage = 1000UL;
_processInfoMock.Setup(p => p.GetCurrentAppMemoryInfo())
.Returns(() => _appMemoryInfo);
_appMemoryUsage = 1000UL;
_processInfoMock.Setup(p => p.GetMemoryUsage())
.Returns(() => _appMemoryUsage);
}

[Theory]
Expand Down Expand Up @@ -120,7 +120,7 @@ public void GetSnapshot_ProducesCorrectSnapshot()
Assert.Equal(_accountingInfo.TotalUserTime, data.UserTimeSinceStart.Ticks);
Assert.Equal(_limitInfo.JobMemoryLimit.ToUInt64(), source.Resources.GuaranteedMemoryInBytes);
Assert.Equal(_limitInfo.JobMemoryLimit.ToUInt64(), source.Resources.MaximumMemoryInBytes);
Assert.Equal(_appMemoryInfo.TotalCommitUsage, data.MemoryUsageInBytes);
Assert.Equal(_appMemoryUsage, data.MemoryUsageInBytes);
Assert.True(data.MemoryUsageInBytes > 0);
}

Expand All @@ -147,7 +147,7 @@ public void GetSnapshot_ProducesCorrectSnapshotForDifferentCpuRate()
Assert.Equal(0.7, source.Resources.MaximumCpuUnits);
Assert.Equal(_limitInfo.JobMemoryLimit.ToUInt64(), source.Resources.GuaranteedMemoryInBytes);
Assert.Equal(_limitInfo.JobMemoryLimit.ToUInt64(), source.Resources.MaximumMemoryInBytes);
Assert.Equal(_appMemoryInfo.TotalCommitUsage, data.MemoryUsageInBytes);
Assert.Equal(_appMemoryUsage, data.MemoryUsageInBytes);
Assert.True(data.MemoryUsageInBytes > 0);
}

Expand All @@ -160,7 +160,7 @@ public void GetSnapshot_With_JobMemoryLimit_Set_To_Zero_ProducesCorrectSnapshot(

_limitInfo.JobMemoryLimit = new UIntPtr(0);

_appMemoryInfo.TotalCommitUsage = 3000UL;
_appMemoryUsage = 3000UL;

var source = new WindowsContainerSnapshotProvider(
_memoryInfoMock.Object,
Expand All @@ -179,7 +179,7 @@ public void GetSnapshot_With_JobMemoryLimit_Set_To_Zero_ProducesCorrectSnapshot(
Assert.Equal(1.0, source.Resources.MaximumCpuUnits);
Assert.Equal(_memStatus.TotalPhys, source.Resources.GuaranteedMemoryInBytes);
Assert.Equal(_memStatus.TotalPhys, source.Resources.MaximumMemoryInBytes);
Assert.Equal(_appMemoryInfo.TotalCommitUsage, data.MemoryUsageInBytes);
Assert.Equal(_appMemoryUsage, data.MemoryUsageInBytes);
Assert.True(data.MemoryUsageInBytes > 0);
}

Expand Down Expand Up @@ -248,13 +248,12 @@ public void SnapshotProvider_EmitsCpuMetrics()
[Fact]
public void SnapshotProvider_EmitsMemoryMetrics()
{
_appMemoryInfo.TotalCommitUsage = 200UL;
_appMemoryUsage = 200UL;

ProcessInfo.APP_MEMORY_INFORMATION updatedAppMemoryInfo = default;
updatedAppMemoryInfo.TotalCommitUsage = 600UL;
_processInfoMock.SetupSequence(p => p.GetCurrentAppMemoryInfo())
.Returns(() => _appMemoryInfo)
.Returns(updatedAppMemoryInfo)
ulong updatedAppMemoryUsage = 600UL;
_processInfoMock.SetupSequence(p => p.GetMemoryUsage())
.Returns(() => _appMemoryUsage)
.Returns(updatedAppMemoryUsage)
.Throws(new InvalidOperationException("We shouldn't hit here..."));

var fakeClock = new FakeTimeProvider();
Expand Down