@@ -19,6 +19,7 @@ public class GcMemoryLoadCalculatorTests
1919 private const long Total8GiB = 8L * 1024 * 1024 * 1024 ;
2020 private const long Total79GiB = 79L * 1024 * 1024 * 1024 ;
2121 private const long Total96GiB = 96L * 1024 * 1024 * 1024 ;
22+ private static readonly Func < int ? > GetTotalProcessorCount = ( ) => 4 ;
2223
2324 [ Fact ]
2425 public void Calculate_NoHardLimit_RecoversTrueLoad ( )
@@ -27,7 +28,12 @@ public void Calculate_NoHardLimit_RecoversTrueLoad()
2728 var totalAvailableMemoryBytes = Total8GiB ;
2829 var memoryLoadBytes = Encode ( Total8GiB , 42 ) ;
2930
30- var result = GcMemoryLoadCalculator . TryCalculate ( memoryLoadBytes , highMemoryLoadThresholdBytes , totalAvailableMemoryBytes , configuredHighPercent : null , processorCount : 4 ) ;
31+ var result = GcMemoryLoadCalculator . TryCalculate (
32+ memoryLoadBytes ,
33+ highMemoryLoadThresholdBytes ,
34+ totalAvailableMemoryBytes ,
35+ configuredHighPercent : null ,
36+ GetTotalProcessorCount ) ;
3137
3238 result . Should ( ) . Be ( 42 ) ;
3339 }
@@ -46,7 +52,7 @@ public void Calculate_DefaultContainerHardLimit_RecoversTrueLoad(int loadPercent
4652 var totalAvailableMemoryBytes = Encode ( Total8GiB , 75 ) ;
4753 var memoryLoadBytes = Encode ( Total8GiB , loadPercent ) ;
4854
49- var result = GcMemoryLoadCalculator . TryCalculate ( memoryLoadBytes , highMemoryLoadThresholdBytes , totalAvailableMemoryBytes , configuredHighPercent : null , processorCount : 4 ) ;
55+ var result = GcMemoryLoadCalculator . TryCalculate ( memoryLoadBytes , highMemoryLoadThresholdBytes , totalAvailableMemoryBytes , configuredHighPercent : null , GetTotalProcessorCount ) ;
5056
5157 result . Should ( ) . Be ( loadPercent ) ;
5258 }
@@ -62,7 +68,7 @@ public void Calculate_ConfiguredViaHexEnvVar_ClampsToNinetyNine()
6268 var totalAvailableMemoryBytes = Encode ( Total8GiB , 50 ) ;
6369 var memoryLoadBytes = Encode ( Total8GiB , 42 ) ;
6470
65- var result = GcMemoryLoadCalculator . TryCalculate ( memoryLoadBytes , highMemoryLoadThresholdBytes , totalAvailableMemoryBytes , configuredHighPercent , processorCount : 4 ) ;
71+ var result = GcMemoryLoadCalculator . TryCalculate ( memoryLoadBytes , highMemoryLoadThresholdBytes , totalAvailableMemoryBytes , configuredHighPercent , GetTotalProcessorCount ) ;
6672
6773 result . Should ( ) . Be ( 42 ) ;
6874 }
@@ -80,7 +86,7 @@ public void Calculate_ConfiguredViaRuntimeConfigKnob_ParsesDecimal()
8086 var totalAvailableMemoryBytes = Encode ( Total8GiB , 50 ) ;
8187 var memoryLoadBytes = Encode ( Total8GiB , 42 ) ;
8288
83- var result = GcMemoryLoadCalculator . TryCalculate ( memoryLoadBytes , highMemoryLoadThresholdBytes , totalAvailableMemoryBytes , configuredHighPercent , processorCount : 4 ) ;
89+ var result = GcMemoryLoadCalculator . TryCalculate ( memoryLoadBytes , highMemoryLoadThresholdBytes , totalAvailableMemoryBytes , configuredHighPercent , GetTotalProcessorCount ) ;
8490
8591 result . Should ( ) . Be ( 42 ) ;
8692 }
@@ -193,42 +199,59 @@ public void ParseAppContextHighMemPercent_ResolvesExpectedValue(object? appConte
193199 result . Should ( ) . Be ( expected ) ;
194200 }
195201
196- [ Fact ]
197- public void ResolveHighMemoryLoadThresholdPercent_HostAtOrAboveEightyGiB_UsesProcessorAdjustedDefault ( )
202+ [ Theory ]
203+ [ InlineData ( 96 , 97 , 48 , 97 ) ] // min(10, 3 + (int)(47 / 48)) == 3 -> th == 97
204+ [ InlineData ( 82 , 90 , 4 , 90 ) ] // min(10, 3 + (int)(47 / 4)) == min(10, 14) == 10 -> th == 90
205+ [ InlineData ( 96 , 97 , null , null ) ] // needs the host-wide processor count; if it couldn't be reliably determined, bail out
206+ public void ResolveHighMemoryLoadThresholdPercent_HostAtOrAboveEightyGiB_DependsOnProcessorCount ( int totalGiB , int thresholdPercent , int ? processorCount , int ? expected )
198207 {
199- var highMemoryLoadThresholdBytes = Encode ( Total96GiB , 97 ) ;
208+ var totalBytes = totalGiB * 1024L * 1024 * 1024 ;
209+ var highMemoryLoadThresholdBytes = Encode ( totalBytes , thresholdPercent ) ;
200210
201- // min(10, 3 + (int)(47 / 48)) == 3 -> th == 97
202- var result = GcMemoryLoadCalculator . ResolveHighMemoryLoadThresholdPercent ( highMemoryLoadThresholdBytes , configuredHighPercent : null , processorCount : 48 ) ;
211+ var result = GcMemoryLoadCalculator . ResolveHighMemoryLoadThresholdPercent ( highMemoryLoadThresholdBytes , configuredHighPercent : null , ( ) => processorCount ) ;
203212
204- result . Should ( ) . Be ( 97 ) ;
213+ result . Should ( ) . Be ( expected ) ;
214+ }
215+
216+ [ Theory ]
217+ [ InlineData ( 4 , 90 ) ]
218+ [ InlineData ( null , 90 ) ] // the flat-90 branch never needs the processor count, so an unknown count shouldn't stop it resolving
219+ public void ResolveHighMemoryLoadThresholdPercent_HostJustBelowEightyGiB_UsesFixedDefaultRegardlessOfProcessorCount ( int ? processorCount , int ? expected )
220+ {
221+ var highMemoryLoadThresholdBytes = Encode ( Total79GiB , 90 ) ;
222+
223+ var result = GcMemoryLoadCalculator . ResolveHighMemoryLoadThresholdPercent ( highMemoryLoadThresholdBytes , configuredHighPercent : null , ( ) => processorCount ) ;
224+
225+ result . Should ( ) . Be ( expected ) ;
205226 }
206227
207228 [ Fact ]
208- public void ResolveHighMemoryLoadThresholdPercent_HostAtOrAboveEightyGiB_LowProcessorCount_CapsAtNinety ( )
229+ public void ResolveHighMemoryLoadThresholdPercent_ConfiguredOverride_UnknownProcessorCountStillResolves ( )
209230 {
210- var highMemoryLoadThresholdBytes = Encode ( Total82GiB , 90 ) ;
231+ // A configured override never needs the processor count either.
232+ var highMemoryLoadThresholdBytes = Encode ( Total96GiB , 70 ) ;
211233
212- // min(10, 3 + (int)(47 / 4)) == min(10, 14) == 10 -> th == 90
213- var result = GcMemoryLoadCalculator . ResolveHighMemoryLoadThresholdPercent ( highMemoryLoadThresholdBytes , configuredHighPercent : null , processorCount : 4 ) ;
234+ var result = GcMemoryLoadCalculator . ResolveHighMemoryLoadThresholdPercent ( highMemoryLoadThresholdBytes , configuredHighPercent : 70 , ( ) => null ) ;
214235
215- result . Should ( ) . Be ( 90 ) ;
236+ result . Should ( ) . Be ( 70 ) ;
216237 }
217238
218239 [ Fact ]
219- public void ResolveHighMemoryLoadThresholdPercent_HostJustBelowEightyGiB_UsesFixedDefault ( )
240+ public void Calculate_HostAtOrAboveEightyGiB_UnknownProcessorCount_ReturnsNull ( )
220241 {
221- var highMemoryLoadThresholdBytes = Encode ( Total79GiB , 90 ) ;
242+ var highMemoryLoadThresholdBytes = Encode ( Total96GiB , 97 ) ;
243+ var totalAvailableMemoryBytes = Total96GiB ;
244+ var memoryLoadBytes = Encode ( Total96GiB , 42 ) ;
222245
223- var result = GcMemoryLoadCalculator . ResolveHighMemoryLoadThresholdPercent ( highMemoryLoadThresholdBytes , configuredHighPercent : null , processorCount : 4 ) ;
246+ var result = GcMemoryLoadCalculator . TryCalculate ( memoryLoadBytes , highMemoryLoadThresholdBytes , totalAvailableMemoryBytes , configuredHighPercent : null , ( ) => null ) ;
224247
225- result . Should ( ) . Be ( 90 ) ;
248+ result . Should ( ) . BeNull ( ) ;
226249 }
227250
228251 [ Fact ]
229252 public void Calculate_HighMemoryLoadThresholdBytesIsZero_ReturnsNull ( )
230253 {
231- var result = GcMemoryLoadCalculator . TryCalculate ( memoryLoadBytes : 500 , highMemoryLoadThresholdBytes : 0 , totalAvailableMemoryBytes : Total8GiB , configuredHighPercent : null , processorCount : 4 ) ;
254+ var result = GcMemoryLoadCalculator . TryCalculate ( memoryLoadBytes : 500 , highMemoryLoadThresholdBytes : 0 , totalAvailableMemoryBytes : Total8GiB , configuredHighPercent : null , GetTotalProcessorCount ) ;
232255
233256 result . Should ( ) . BeNull ( ) ;
234257 }
@@ -238,7 +261,7 @@ public void Calculate_PathologicalHardLimitExceedsImpliedPhysicalMemory_ReturnsN
238261 {
239262 // A tiny highMemoryLoadThresholdBytes next to a huge totalAvailableMemoryBytes can never be consistent -
240263 // TotalAvailableMemoryBytes (heap_hard_limit) can never exceed total_physical_mem.
241- var result = GcMemoryLoadCalculator . TryCalculate ( memoryLoadBytes : 500 , highMemoryLoadThresholdBytes : 1000 , totalAvailableMemoryBytes : Total8GiB , configuredHighPercent : null , processorCount : 4 ) ;
264+ var result = GcMemoryLoadCalculator . TryCalculate ( memoryLoadBytes : 500 , highMemoryLoadThresholdBytes : 1000 , totalAvailableMemoryBytes : Total8GiB , configuredHighPercent : null , GetTotalProcessorCount ) ;
242265
243266 result . Should ( ) . BeNull ( ) ;
244267 }
@@ -259,7 +282,7 @@ public void Calculate_ExplicitHardLimitWhoseRatioLooksLikeACleanPercentage_Recov
259282 var totalAvailableMemoryBytes = Encode ( Total8GiB , 93.75 ) ;
260283 var memoryLoadBytes = Encode ( Total8GiB , loadPercent ) ;
261284
262- var result = GcMemoryLoadCalculator . TryCalculate ( memoryLoadBytes , highMemoryLoadThresholdBytes , totalAvailableMemoryBytes , configuredHighPercent : null , processorCount : 4 ) ;
285+ var result = GcMemoryLoadCalculator . TryCalculate ( memoryLoadBytes , highMemoryLoadThresholdBytes , totalAvailableMemoryBytes , configuredHighPercent : null , GetTotalProcessorCount ) ;
263286
264287 result . Should ( ) . Be ( loadPercent ) ;
265288 }
@@ -273,7 +296,7 @@ public void Calculate_ConfiguredOverrideWinsUnderAHardLimit()
273296 var totalAvailableMemoryBytes = Encode ( Total8GiB , 75 ) ;
274297 var memoryLoadBytes = Encode ( Total8GiB , 42 ) ;
275298
276- var result = GcMemoryLoadCalculator . TryCalculate ( memoryLoadBytes , highMemoryLoadThresholdBytes , totalAvailableMemoryBytes , configuredHighPercent : 70 , processorCount : 4 ) ;
299+ var result = GcMemoryLoadCalculator . TryCalculate ( memoryLoadBytes , highMemoryLoadThresholdBytes , totalAvailableMemoryBytes , configuredHighPercent : 70 , GetTotalProcessorCount ) ;
277300
278301 result . Should ( ) . Be ( 42 ) ;
279302 }
@@ -321,7 +344,7 @@ public void DriftCanary_MeasuredDefaultHighMemoryLoadThresholdMatchesProductionP
321344 var measured = TryMeasureHighMemoryLoadThresholdPercent ( info . HighMemoryLoadThresholdBytes , info . TotalAvailableMemoryBytes ) ;
322345 Skip . If ( measured is null , "Could not measure high_memory_load_th on this host (a hard limit may be in play despite no knob being detected, or the C#/C++ rounding didn't round-trip)." ) ;
323346
324- var predicted = GcMemoryLoadCalculator . ResolveHighMemoryLoadThresholdPercent ( info . HighMemoryLoadThresholdBytes , configuredHighPercent : null , Environment . ProcessorCount ) ;
347+ var predicted = GcMemoryLoadCalculator . ResolveHighMemoryLoadThresholdPercent ( info . HighMemoryLoadThresholdBytes , configuredHighPercent : null , ( ) => TotalProcessorCount . Value ) ;
325348
326349 measured . Should ( ) . Be ( predicted ) ;
327350 // The ratio-inference removed from production, kept only as a measurement tool for the drift canary above:
0 commit comments