< Summary

Information
Class: AmbientServices.ThreadPoolPressurePoint
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/Helpers/InternalPressurePoints.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 45
Uncovered lines: 0
Coverable lines: 45
Total lines: 314
Line coverage: 100%
Branch coverage
76%
Covered branches: 20
Total branches: 26
Branch coverage: 76.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%1212100%
get_Name()100%11100%
get_Pressure()57.14%1414100%

File(s)

/home/runner/work/AmbientServices/AmbientServices/AmbientServices/Helpers/InternalPressurePoints.cs

#LineLine coverage
 1using System;
 2using System.Diagnostics;
 3using System.Runtime.Versioning;
 4using System.Threading;
 5
 6namespace AmbientServices;
 7
 8/// <summary>
 9/// An <see cref="IPressurePoint"/> implementation that measures local CPU pressure.
 10/// </summary>
 11/// <remarks>
 12/// <pitch>Feeds this process's CPU utilization into the pressure system, so background work throttles when the CPU is b
 13/// <pledge><see cref="IPressurePoint"/></pledge>
 14/// <plan>Each poll takes a fresh <see cref="CpuSample"/>, swaps it for the previous one via <see cref="Interlocked.Exch
 15/// </remarks>
 16public sealed class CpuPressurePoint : IPressurePoint
 17{
 18    private const double FixedFloatingPointAdjustment = 100_000_000;
 19    private const long MinRawValue = 0;
 20    private const long MaxRawValue = (long)(1.00f * FixedFloatingPointAdjustment);
 21    private const long NeutralRawValue = (long)(0.89f * FixedFloatingPointAdjustment);
 22    private static readonly AmbientService<IAmbientStatistics> AmbientStatistics = Ambient.GetService<IAmbientStatistics
 23    private readonly IAmbientStatistic? _cpuPressure = AmbientStatistics.Local?.GetOrAddStatistic(AmbientStatisticType.R
 24
 25#if NET5_0_OR_GREATER
 26    private readonly float _neutralValue;
 27#endif
 28    private object _previousSample;         // interlocked
 29
 30#if NET5_0_OR_GREATER
 31    /// <summary>
 32    /// Constructs a CPU pressure point.
 33    /// </summary>
 34    /// <param name="neutralValue">A neutral value to use in case we're running in a browser and this information is not
 35    public CpuPressurePoint(float neutralValue = 0.89f)
 36    {
 37        _previousSample = CpuSample.GetSample();
 38        _neutralValue = neutralValue;
 39    }
 40#else
 41    /// <summary>
 42    /// Constructs a CPU pressure point.
 43    /// </summary>
 44    public CpuPressurePoint()
 45    {
 46        _previousSample = CpuSample.GetSample();
 47    }
 48#endif
 49
 50    /// <summary>
 51    /// Gets the name of the pressure point, used for the performance counter instance and status reports.
 52    /// </summary>
 53    public string Name => "Cpu";
 54
 55    /// <summary>
 56    /// Gets the pressure value (between 0.0 and 1.0).
 57    /// </summary>
 58    public float Pressure
 59    {
 60        get
 61        {
 62#if NET5_0_OR_GREATER
 63            if (OperatingSystem.IsBrowser()) return _neutralValue;
 64#endif
 65            CpuSample newSample = CpuSample.GetSample();
 66            CpuSample oldSample = (CpuSample)Interlocked.Exchange(ref _previousSample, newSample);
 67            float newPressure = 0.02f + CpuSample.CpuUtilization(oldSample, newSample);   // CpuUtilization is *process*
 68            _cpuPressure?.SetValue(newPressure);
 69            return newPressure;
 70        }
 71    }
 72}
 73
 74/// <summary>
 75/// A <see cref="IPressurePoint"/> implementation that measures local thread pool pressure.
 76/// </summary>
 77/// <remarks>
 78/// <pitch>Feeds thread starvation signals into the pressure system: worker and completion-port saturation, process thre
 79/// <pledge><see cref="IPressurePoint"/></pledge>
 80/// <plan>Each poll computes several sub-pressures — in-use worker and completion-port threads relative to <see cref="Th
 81/// </remarks>
 82#if NET5_0_OR_GREATER
 83[UnsupportedOSPlatform("browser")]
 84#endif
 85public sealed class ThreadPoolPressurePoint : IPressurePoint
 86{
 87    private const double FixedFloatingPointAdjustment = 100_000_000;
 88    private const long MinRawValue = 0;
 89    private const long MaxRawValue = (long)(1.00f * FixedFloatingPointAdjustment);
 90    private const long NeutralRawValue = (long)(0.89f * FixedFloatingPointAdjustment);
 91
 292    private static readonly AmbientService<IAmbientStatistics> AmbientStatistics = Ambient.GetService<IAmbientStatistics
 293    private readonly IAmbientStatistic? _threadPoolPressure = AmbientStatistics.Local?.GetOrAddStatistic(AmbientStatisti
 294    private readonly IAmbientStatistic? _processThreadPressure = AmbientStatistics.Local?.GetOrAddStatistic(AmbientStati
 95
 96    private readonly int _maxPoolThreads;
 97    private readonly int _maxProcessThreads;
 298    private readonly IAmbientStatistic? _workerPressure = AmbientStatistics.Local?.GetOrAddStatistic(AmbientStatisticTyp
 299    private readonly IAmbientStatistic? _completionPortPressure = AmbientStatistics.Local?.GetOrAddStatistic(AmbientStat
 2100    private readonly IAmbientStatistic? _totalThreadPressure = AmbientStatistics.Local?.GetOrAddStatistic(AmbientStatist
 101#if NETCOREAPP1_0_OR_GREATER
 2102    private readonly IAmbientStatistic? _threadCountChangePressure = AmbientStatistics.Local?.GetOrAddStatistic(AmbientS
 103    private readonly int _maxBufferedThreadPoolActions;
 104    private readonly int _maxThreadsPerSecond;
 105    private int _previousSampleThreadCount;
 106    private int _threadsAddedThisSample;
 107#endif
 108
 109#if NETCOREAPP1_0_OR_GREATER
 110    /// <summary>
 111    /// Constructs a pressure point that measures thread pool pressure.
 112    /// </summary>
 113    /// <param name="maxProcessThreads">The maximum number of threads to allow for this process.</param>
 114    /// <param name="maxPoolThreads">The maximum number of threads to allow for the thread pool.</param>
 115    /// <param name="maxThreadPerSecond">The maximum number of threads being created per second to allow.</param>
 116    /// <param name="maxBufferedThreadPoolActions">The maximum number of buffered thread pool actions to allow.</param>
 2117    public ThreadPoolPressurePoint(int maxProcessThreads = 64 * 1024, int maxPoolThreads = 64 * 1024, int maxThreadPerSe
 118    {
 2119        _maxProcessThreads = maxProcessThreads;
 2120        _maxPoolThreads = maxPoolThreads;
 2121        _maxThreadsPerSecond = maxThreadPerSecond;
 2122        _maxBufferedThreadPoolActions = maxBufferedThreadPoolActions;
 2123    }
 124#else
 125    /// <summary>
 126    /// Constructs a pressure point that measures thread pool pressure.
 127    /// </summary>
 128    /// <param name="maxProcessThreads">The maximum number of threads to allow for this process.</param>
 129    /// <param name="maxPoolThreads">The maximum number of threads to allow for the thread pool.</param>
 130    public ThreadPoolPressurePoint(int maxProcessThreads = 64 * 1024, int maxPoolThreads = 64 * 1024)
 131    {
 132        _maxProcessThreads = maxProcessThreads;
 133        _maxPoolThreads = maxPoolThreads;
 134    }
 135#endif
 136
 137    /// <summary>
 138    /// Gets the name of the pressure point, used for the performance counter instance and status reports.
 139    /// </summary>
 2140    public string Name => "ThreadPool";
 141
 142    /// <summary>
 143    /// Gets the pressure value (between 0.0 and 1.0).
 144    /// </summary>
 145    public float Pressure
 146    {
 147        get
 148        {
 2149            Process currentProcess = Process.GetCurrentProcess();
 2150            float processThreadPressure = 0.0f;
 151#if NET5_0_OR_GREATER
 2152            if (!OperatingSystem.IsBrowser())
 153            {
 154#endif
 2155                int processThreads = currentProcess.Threads.Count;
 2156                processThreadPressure = (1.0f * processThreads) / _maxProcessThreads;
 2157                _processThreadPressure?.SetValue(processThreadPressure);
 158#if NET5_0_OR_GREATER
 159            }
 160#endif
 161#if NETCOREAPP1_0_OR_GREATER
 2162            float pendingWorkPressure = Math.Min(1.0f, (1.0f * ThreadPool.PendingWorkItemCount) / _maxBufferedThreadPool
 163
 2164            int newThreadCount = ThreadPool.ThreadCount;
 2165            int previousThreadCount = Interlocked.Exchange(ref _previousSampleThreadCount, newThreadCount);
 2166            int threadsAdded = Math.Max(0, newThreadCount - previousThreadCount);
 2167            float threadCountChangePressure = Math.Min(1.0f, (threadsAdded * 1.0f) / _maxThreadsPerSecond);
 2168            Interlocked.Exchange(ref _threadsAddedThisSample, threadsAdded);
 2169            _threadCountChangePressure?.SetValue(threadCountChangePressure);
 170#endif
 2171            ThreadPool.GetMaxThreads(out int maxWorkerThreads, out int maxCompletionPortThreads);
 2172            ThreadPool.GetAvailableThreads(out int potentialAdditionalWorkerThreads, out int potentialAdditionalCompleti
 2173            int workerThreads = maxWorkerThreads - potentialAdditionalWorkerThreads;
 2174            int completionPortThreads = maxCompletionPortThreads - potentialAdditionalCompletionPortThreads;
 2175            float workerPressure = (1.0f * workerThreads / maxWorkerThreads);
 2176            float completionPortPressure = (1.0f * completionPortThreads / maxCompletionPortThreads);
 2177            float totalThreadPressure = Math.Min(1.0f, (workerThreads + completionPortThreads) * 1.0f / _maxPoolThreads)
 2178            _workerPressure?.SetValue(workerPressure);
 2179            _completionPortPressure?.SetValue(completionPortPressure);
 2180            _totalThreadPressure?.SetValue(totalThreadPressure);
 181
 2182            float overallThreadPressure = PressureMonitor.Max(
 2183#if NETCOREAPP1_0_OR_GREATER
 2184                    threadCountChangePressure, pendingWorkPressure,
 2185#endif
 2186                    processThreadPressure, workerPressure, completionPortPressure, totalThreadPressure
 2187                    );
 2188            _threadPoolPressure?.SetValue(overallThreadPressure);
 2189            return overallThreadPressure;
 190        }
 191    }
 192}
 193
 194/// <summary>
 195/// A <see cref="IPressurePoint"/> implementation that measures local system memory pressure.
 196/// Memory usage is not directly proportional to memory pressure,
 197/// because significant memory is always in use even when nothing is happening,
 198/// so this pressure point uses a skewed scale to better represent the pressure.
 199/// </summary>
 200/// <remarks>
 201/// <pitch>Feeds memory headroom into the pressure system, on a skewed scale that stays near zero through the memory usa
 202/// <pledge><see cref="IPressurePoint"/></pledge>
 203/// <plan>On .NET Core targets, each poll takes the worse of two linear measures — <c>GC.GetGCMemoryInfo</c> memory load
 204/// <priority>
 205/// <see cref="IPressurePoint"/>
 206/// 1. Silence during healthy operation over early warning: the linear proportion is deliberately skewed so that the mem
 207/// 2. The worse of two measures over the more accurate one: each poll takes the higher of GC memory load and process wo
 208/// </priority>
 209/// </remarks>
 210#if NET5_0_OR_GREATER
 211[UnsupportedOSPlatform("browser")]
 212#endif
 213public sealed class MemoryPressurePoint : IPressurePoint
 214{
 215    private static readonly int[] SkewedProportions = new int[] {                   // a more smooth function would look
 216         0,  0,  0,  0,  0,  0,  0,  0,  0,  0,     // 9% = 0% pressure             // skewed = 1 / (1 + e^(-steepness *
 217         1,  1,  1,  1,  1,  1,  1,  1,  1,  1,     // 19% = 1% pressure            // where steepness adjusts the steep
 218         2,  2,  2,  2,  2,  3,  3,  3,  3,  3,     // 29% = 3% pressure            // and focus is where the curve cros
 219         4,  4,  4,  4,  5,  5,  5,  5,  6,  6,     // 39% = 6% pressure            //
 220         6,  6,  7,  7,  7,  8,  8,  8,  9,  9,     // 49% = 9% pressure            //
 221        10, 10, 11, 11, 12, 12, 13, 13, 14, 14,     // 59% = 14% pressure           //
 222        15, 16, 17, 18, 19, 20, 21, 22, 23, 24,     // 69% = 24% pressure           //
 223        25, 26, 27, 28, 29, 30, 31, 32, 33, 34,     // 79% = 34% pressure           //
 224        36, 38, 40, 42, 44, 46, 48, 50, 52, 64,     // 89% = 64% pressure           //
 225        67, 70, 73, 76, 79, 82, 86, 90, 94, 98,     // 99% = 98% pressure           //
 226    };
 227    private const double FixedFloatingPointAdjustment = 100_000_000;
 228    private const long MinRawValue = 0;
 229    private const long MaxRawValue = (long)(1.00f * FixedFloatingPointAdjustment);
 230    private const long NeutralRawValue = (long)(0.89f * FixedFloatingPointAdjustment);
 231    private static readonly AmbientService<IAmbientStatistics> AmbientStatistics = Ambient.GetService<IAmbientStatistics
 232    private readonly IAmbientStatistic? _memoryPressure = AmbientStatistics.Local?.GetOrAddStatistic(AmbientStatisticTyp
 233
 234#if NETCOREAPP1_0_OR_GREATER
 235    private readonly IAmbientStatistic? _memoryLoadPressure = AmbientStatistics.Local?.GetOrAddStatistic(AmbientStatisti
 236    private readonly IAmbientStatistic? _workingSetPressure = AmbientStatistics.Local?.GetOrAddStatistic(AmbientStatisti
 237    /// <summary>
 238    /// Constructs a pressure point that measures memory pressure.
 239    /// </summary>
 240    public MemoryPressurePoint()
 241    {
 242    }
 243#else
 244    private readonly long _maxBytesAllowed;
 245
 246    /// <summary>
 247    /// Constructs a pressure point that measures memory pressure.
 248    /// </summary>
 249    /// <param name="maxBytesAllowed">The maximum number of bytes allowed to be used by this process.</param>
 250    public MemoryPressurePoint(long maxBytesAllowed = long.MaxValue)
 251    {
 252        _maxBytesAllowed = maxBytesAllowed;
 253    }
 254#endif
 255
 256    /// <summary>
 257    /// Gets the name of the pressure point, used for the performance counter instance and status reports.
 258    /// </summary>
 259    public string Name => "Memory";
 260
 261    /// <summary>
 262    /// Gets the pressure value (between 0.0 and 1.0).
 263    /// </summary>
 264    public float Pressure
 265    {
 266        get
 267        {
 268#if NETCOREAPP1_0_OR_GREATER
 269            GCMemoryInfo info = GC.GetGCMemoryInfo();
 270            long totalPhysicalMemory = info.TotalAvailableMemoryBytes;
 271            long reservedMemory = Math.Min(Math.Max(totalPhysicalMemory / 10, 25_000_000), 4_000_000_000);
 272            long usableMemory = totalPhysicalMemory - reservedMemory;
 273            float loadMemoryPressure = (1.0f * info.MemoryLoadBytes) / usableMemory;
 274            _memoryLoadPressure?.SetValue(loadMemoryPressure);
 275            float workingSetMemoryPressure = 0;
 276#if NET5_0_OR_GREATER
 277            if (!OperatingSystem.IsBrowser())
 278            {
 279#endif
 280                long workingSetMemory = Process.GetCurrentProcess().WorkingSet64;
 281                workingSetMemoryPressure = (1.0f * workingSetMemory) / usableMemory;
 282                _workingSetPressure?.SetValue(workingSetMemoryPressure);
 283#if NET5_0_OR_GREATER
 284            }
 285#endif
 286            float linearPressure = Math.Max(loadMemoryPressure, workingSetMemoryPressure);
 287            float memoryPressure = LinearPressureToMemoryPressure(linearPressure);
 288            _memoryPressure?.SetValue(memoryPressure);
 289            return memoryPressure;
 290#else
 291            long totalBytes = GC.GetTotalMemory(false);
 292            float linearPressure = (totalBytes * 1.0f) / _maxBytesAllowed;
 293            float memoryPressure = LinearPressureToMemoryPressure(linearPressure);
 294            _memoryPressure?.SetValue(memoryPressure);
 295            return memoryPressure;
 296#endif
 297        }
 298    }
 299    internal static float LinearPressureToMemoryPressure(float linearPressure)
 300    {
 301        if (linearPressure <= 0.0f) return 0.0f;
 302        if (linearPressure  > 1.1f) return 1.0f;
 303        if (linearPressure > 0.99f) return 0.99f + 0.01f * (linearPressure - 0.99f) / 0.11f;
 304        int linearPressureOffset = (int)(linearPressure * 100);
 305        float memoryPressure = (SkewedPressure(linearPressureOffset) + (SkewedPressure(linearPressureOffset + 1) - Skewe
 306        return memoryPressure;
 307    }
 308    private static int SkewedPressure(int linearPressureOffset)
 309    {
 310        if (linearPressureOffset <= 0) return 0;
 311        if (linearPressureOffset >= 100) return 100;
 312        return SkewedProportions[linearPressureOffset];
 313    }
 314}