11namespace ServiceControl . Audit . Persistence . RavenDB . CustomChecks ;
22
33using System ;
4- using System . Collections . Generic ;
54using System . Threading ;
65using System . Threading . Tasks ;
76using NServiceBus . CustomChecks ;
87using NServiceBus . Logging ;
98
109class CheckDirtyMemory ( MemoryInformationRetriever memoryInformationRetriever ) : CustomCheck ( "RavenDB dirty memory trends" , "ServiceControl.Audit Health" , TimeSpan . FromMinutes ( 5 ) )
1110{
12- readonly List < int > lastDirtyMemoryReads = [ ] ;
1311 public override async Task < CheckResult > PerformCheck ( CancellationToken cancellationToken = default )
1412 {
1513 var ( isHighDirty , dirtyMemoryKb ) = await memoryInformationRetriever . GetMemoryInformation ( cancellationToken ) ;
@@ -23,83 +21,8 @@ public override async Task<CheckResult> PerformCheck(CancellationToken cancellat
2321 return CheckResult . Failed ( message ) ;
2422 }
2523
26- lastDirtyMemoryReads . Add ( dirtyMemoryKb ) ;
27- if ( lastDirtyMemoryReads . Count > 20 )
28- {
29- //cap the list at 20 which means we're keeping about 1 hour and 40 minutes of data
30- lastDirtyMemoryReads . RemoveAt ( 0 ) ;
31- }
32-
33- switch ( lastDirtyMemoryReads . Count )
34- {
35- case < 3 :
36- Log . Debug ( "Not enough RavenDB dirty memory data in the series to calculate a trend." ) ;
37- break ;
38- // TODO do we need a threshold below which the check never fails?
39- // Three means we'll be observing for 15 minutes before calculating the trend
40- case >= 3 when AnalyzeTrendUsingRegression ( lastDirtyMemoryReads ) == TrendDirection . Increasing :
41- {
42- var message = $ "RavenDB dirty memory is increasing. Last available value is { dirtyMemoryKb } kb. " +
43- $ "Check the ServiceControl troubleshooting guide for guidance on how to mitigate the issue. " +
44- $ "Visit the https://docs.particular.net/servicecontrol/troubleshooting page for more information.";
45- Log . Warn ( message ) ;
46- return CheckResult . Failed ( message ) ;
47- }
48-
49- default :
50- // NOP
51- break ;
52- }
53-
5424 return CheckResult . Pass ;
5525 }
5626
57- static TrendDirection AnalyzeTrendUsingRegression ( List < int > values )
58- {
59- if ( values is not { Count : > 1 } )
60- {
61- throw new ArgumentException ( "Need at least two values to determine a trend" ) ;
62- }
63-
64- // Calculate slope using linear regression
65- double numberOfPoints = values . Count ;
66- double sumOfIndices = 0 ;
67- double sumOfValues = 0 ;
68- double sumOfIndicesMultipliedByValues = 0 ;
69- double sumOfIndicesSquared = 0 ;
70-
71- for ( int i = 0 ; i < values . Count ; i ++ )
72- {
73- double index = i ;
74- double value = values [ i ] ;
75-
76- sumOfIndices += index ;
77- sumOfValues += value ;
78- sumOfIndicesMultipliedByValues += index * value ;
79- sumOfIndicesSquared += index * index ;
80- }
81-
82- // Slope formula: (n*Σxy - Σx*Σy) / (n*Σx² - (Σx)²)
83- double slopeNumerator = ( numberOfPoints * sumOfIndicesMultipliedByValues ) - ( sumOfIndices * sumOfValues ) ;
84- double slopeDenominator = ( numberOfPoints * sumOfIndicesSquared ) - ( sumOfIndices * sumOfIndices ) ;
85- double slope = slopeNumerator / slopeDenominator ;
86-
87- // Determine trend based on slope
88- const double slopeThreshold = 0.001 ; // Small threshold to handle floating-point precision
89- if ( Math . Abs ( slope ) < slopeThreshold )
90- {
91- return TrendDirection . Flat ;
92- }
93-
94- return slope > 0 ? TrendDirection . Increasing : TrendDirection . Decreasing ;
95- }
96-
97- enum TrendDirection
98- {
99- Increasing ,
100- Decreasing ,
101- Flat
102- }
103-
10427 static readonly ILog Log = LogManager . GetLogger < CheckDirtyMemory > ( ) ;
10528}
0 commit comments