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