From 9c4f8125a9bdb2d834af4cd19937c5f421362f49 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Mon, 10 Mar 2025 17:54:31 +0100 Subject: [PATCH 01/24] Add an initial draft of a dirty memory custom check --- .../CustomChecks/CheckDirtyMemory.cs | 48 +++++++++++++++++++ .../MemoryInformationRetriever.cs | 32 +++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs create mode 100644 src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs new file mode 100644 index 0000000000..39b980b277 --- /dev/null +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -0,0 +1,48 @@ +namespace ServiceControl.Audit.Persistence.RavenDB.CustomChecks; + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using NServiceBus.CustomChecks; + +class CheckDirtyMemory(IRavenDocumentStoreProvider documentStoreProvider) : CustomCheck("ServiceControl.Audit database", "Dirty memory trends", TimeSpan.FromMinutes(5)) +{ + readonly List lastDirtyMemoryReads = []; + public override async Task PerformCheck(CancellationToken cancellationToken = default) + { + var retriever = await GetMemoryRetriever(cancellationToken); + var memoryInfo = await retriever.GetMemoryInformation(cancellationToken); + + if (memoryInfo.IsHighDirty) + { + //log warning + return CheckResult.Failed("There is a high level of dirty memory. Check the ServiceControl " + + "troubleshooting guide for guidance on how to mitigate the issue."); + } + + lastDirtyMemoryReads.Add(memoryInfo.DirtyMemory); + if (lastDirtyMemoryReads.Count > 20) + { + //cap the list at 20 + lastDirtyMemoryReads.RemoveAt(lastDirtyMemoryReads.Count - 1); + } + + // evaluate the trends + // if the amount of dirty memory is constantly growing log a warning and fail the check + + return CheckResult.Pass; + } + + MemoryInformationRetriever _retriever; + async Task GetMemoryRetriever(CancellationToken cancellationToken = default) + { + if (_retriever == null) + { + var documentStore = await documentStoreProvider.GetDocumentStore(cancellationToken); + var serverUrl = documentStore.Urls[0]; //TODO is there a better way to get the RavenDB server URL? + _retriever = new MemoryInformationRetriever(serverUrl); + } + return _retriever; + } +} \ No newline at end of file diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs b/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs new file mode 100644 index 0000000000..4ff9f236c4 --- /dev/null +++ b/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs @@ -0,0 +1,32 @@ +namespace ServiceControl.Audit.Persistence.RavenDB; + +using System; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +class MemoryInformationRetriever(string serverUrl) +{ + readonly HttpClient client = new() { BaseAddress = new Uri(serverUrl) }; + + record ResponseDto + { + public MemoryInformation MemoryInformation { get; set; } + } + + record MemoryInformation + { + public bool IsHighDirty { get; set; } + public string DirtyMemory { get; set; } + } + + public async Task<(bool IsHighDirty, int DirtyMemory)> GetMemoryInformation(CancellationToken cancellationToken = default) + { + var httpResponse = await client.GetAsync("/admin/debug/memory/stats", cancellationToken); + var responseDto = JsonSerializer.Deserialize(await httpResponse.Content.ReadAsStringAsync(cancellationToken)); + + return (responseDto.MemoryInformation.IsHighDirty, int.Parse(responseDto.MemoryInformation.DirtyMemory.Split(' ').First())); + } +} \ No newline at end of file From 8b1cbfed00d47e2c87027067cc4bab6a3efd336d Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Mon, 10 Mar 2025 18:33:45 +0100 Subject: [PATCH 02/24] Add some memory analysis --- .../CustomChecks/CheckDirtyMemory.cs | 55 ++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index 39b980b277..2074069d83 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -28,8 +28,10 @@ public override async Task PerformCheck(CancellationToken cancellat lastDirtyMemoryReads.RemoveAt(lastDirtyMemoryReads.Count - 1); } - // evaluate the trends - // if the amount of dirty memory is constantly growing log a warning and fail the check + if (lastDirtyMemoryReads.Count > 3 && AnalyzeTrendUsingRegression(lastDirtyMemoryReads) == TrendDirection.Increasing) // Three means we'll be observing for 15 minutes before calculating the trend + { + // log a warning and fail the check + } return CheckResult.Pass; } @@ -45,4 +47,53 @@ async Task GetMemoryRetriever(CancellationToken canc } return _retriever; } + + static TrendDirection AnalyzeTrendUsingRegression(List values) + { + if (values == null || values.Count <= 1) + { + throw new ArgumentException("Need at least two values to determine a trend"); + } + + // Calculate slope using linear regression + double n = values.Count; + double sumX = 0; + double sumY = 0; + double sumXY = 0; + double sumXX = 0; + + for (int i = 0; i < values.Count; i++) + { + double x = i; + double y = values[i]; + + sumX += x; + sumY += y; + sumXY += x * y; + sumXX += x * x; + } + + double slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX); + + // Determine trend based on slope + if (Math.Abs(slope) < 0.001) // Small threshold to handle floating-point precision + { + return TrendDirection.Flat; + } + + if (slope > 0) + { + return TrendDirection.Increasing; + } + + return TrendDirection.Decreasing; + } + + enum TrendDirection + { + Increasing, + Decreasing, + Flat, + Mixed + } } \ No newline at end of file From bb93f38e87a64ae69842d5f7123a533c862f7161 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Mon, 10 Mar 2025 19:30:58 +0100 Subject: [PATCH 03/24] Use the database configuration to get the server URL --- .../CustomChecks/CheckDirtyMemory.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index 2074069d83..b43965e721 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -6,12 +6,12 @@ namespace ServiceControl.Audit.Persistence.RavenDB.CustomChecks; using System.Threading.Tasks; using NServiceBus.CustomChecks; -class CheckDirtyMemory(IRavenDocumentStoreProvider documentStoreProvider) : CustomCheck("ServiceControl.Audit database", "Dirty memory trends", TimeSpan.FromMinutes(5)) +class CheckDirtyMemory(DatabaseConfiguration databaseConfiguration) : CustomCheck("ServiceControl.Audit database", "Dirty memory trends", TimeSpan.FromMinutes(5)) { readonly List lastDirtyMemoryReads = []; public override async Task PerformCheck(CancellationToken cancellationToken = default) { - var retriever = await GetMemoryRetriever(cancellationToken); + var retriever = await GetMemoryRetriever(); var memoryInfo = await retriever.GetMemoryInformation(cancellationToken); if (memoryInfo.IsHighDirty) @@ -37,15 +37,9 @@ public override async Task PerformCheck(CancellationToken cancellat } MemoryInformationRetriever _retriever; - async Task GetMemoryRetriever(CancellationToken cancellationToken = default) + async Task GetMemoryRetriever() { - if (_retriever == null) - { - var documentStore = await documentStoreProvider.GetDocumentStore(cancellationToken); - var serverUrl = documentStore.Urls[0]; //TODO is there a better way to get the RavenDB server URL? - _retriever = new MemoryInformationRetriever(serverUrl); - } - return _retriever; + return _retriever ??= new MemoryInformationRetriever(databaseConfiguration.ServerConfiguration.ServerUrl); } static TrendDirection AnalyzeTrendUsingRegression(List values) From 6793f77f8fe006358fc4bb46373474582bd64eb9 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Mon, 10 Mar 2025 21:19:46 +0100 Subject: [PATCH 04/24] Add more logging --- .../CustomChecks/CheckDirtyMemory.cs | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index b43965e721..630a98061c 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -5,6 +5,7 @@ namespace ServiceControl.Audit.Persistence.RavenDB.CustomChecks; using System.Threading; using System.Threading.Tasks; using NServiceBus.CustomChecks; +using NServiceBus.Logging; class CheckDirtyMemory(DatabaseConfiguration databaseConfiguration) : CustomCheck("ServiceControl.Audit database", "Dirty memory trends", TimeSpan.FromMinutes(5)) { @@ -16,31 +17,38 @@ public override async Task PerformCheck(CancellationToken cancellat if (memoryInfo.IsHighDirty) { - //log warning - return CheckResult.Failed("There is a high level of dirty memory. Check the ServiceControl " + - "troubleshooting guide for guidance on how to mitigate the issue."); + var message = $"There is a high level of dirty memory ({memoryInfo.DirtyMemory}kb). Check the ServiceControl " + + "troubleshooting guide for guidance on how to mitigate the issue."; + Log.Warn(message); + return CheckResult.Failed(message); } lastDirtyMemoryReads.Add(memoryInfo.DirtyMemory); if (lastDirtyMemoryReads.Count > 20) { - //cap the list at 20 - lastDirtyMemoryReads.RemoveAt(lastDirtyMemoryReads.Count - 1); + //cap the list at 20 which means we're keeping about 1 hour and 40 minutes of data + lastDirtyMemoryReads.RemoveAt(0); } - if (lastDirtyMemoryReads.Count > 3 && AnalyzeTrendUsingRegression(lastDirtyMemoryReads) == TrendDirection.Increasing) // Three means we'll be observing for 15 minutes before calculating the trend + if (lastDirtyMemoryReads.Count < 3) { - // log a warning and fail the check + Log.Debug("Not enough dirty memory data in the series to calculate a trend."); + } + + // Three means we'll be observing for 15 minutes before calculating the trend + if (lastDirtyMemoryReads.Count >= 3 && AnalyzeTrendUsingRegression(lastDirtyMemoryReads) == TrendDirection.Increasing) + { + var message = $"Dirty memory is increasing. Last available value is {memoryInfo.DirtyMemory}kb. " + + $"Check the ServiceControl troubleshooting guide for guidance on how to mitigate the issue."; + Log.Warn(message); + return CheckResult.Failed(message); } return CheckResult.Pass; } MemoryInformationRetriever _retriever; - async Task GetMemoryRetriever() - { - return _retriever ??= new MemoryInformationRetriever(databaseConfiguration.ServerConfiguration.ServerUrl); - } + async Task GetMemoryRetriever() => _retriever ??= new MemoryInformationRetriever(databaseConfiguration.ServerConfiguration.ServerUrl); static TrendDirection AnalyzeTrendUsingRegression(List values) { @@ -87,7 +95,8 @@ enum TrendDirection { Increasing, Decreasing, - Flat, - Mixed + Flat } + + static readonly ILog Log = LogManager.GetLogger(); } \ No newline at end of file From 5afa4c08b6cb8e0bc2fd56c79472654f6435b805 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Mon, 10 Mar 2025 21:20:43 +0100 Subject: [PATCH 05/24] Add a to-do --- .../CustomChecks/CheckDirtyMemory.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index 630a98061c..c1994b39b0 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -35,6 +35,7 @@ public override async Task PerformCheck(CancellationToken cancellat Log.Debug("Not enough dirty memory data in the series to calculate a trend."); } + // 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 if (lastDirtyMemoryReads.Count >= 3 && AnalyzeTrendUsingRegression(lastDirtyMemoryReads) == TrendDirection.Increasing) { From 838d9677af238bbcebd297f0c8dccd8eb06d4a99 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 11 Mar 2025 10:15:29 +0100 Subject: [PATCH 06/24] minor tweaks --- .../CustomChecks/CheckDirtyMemory.cs | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index c1994b39b0..ab233e01ca 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -13,36 +13,37 @@ class CheckDirtyMemory(DatabaseConfiguration databaseConfiguration) : CustomChec public override async Task PerformCheck(CancellationToken cancellationToken = default) { var retriever = await GetMemoryRetriever(); - var memoryInfo = await retriever.GetMemoryInformation(cancellationToken); + var (isHighDirty, dirtyMemory) = await retriever.GetMemoryInformation(cancellationToken); - if (memoryInfo.IsHighDirty) + if (isHighDirty) { - var message = $"There is a high level of dirty memory ({memoryInfo.DirtyMemory}kb). Check the ServiceControl " + + var message = $"There is a high level of dirty memory ({dirtyMemory}kb). Check the ServiceControl " + "troubleshooting guide for guidance on how to mitigate the issue."; Log.Warn(message); return CheckResult.Failed(message); } - lastDirtyMemoryReads.Add(memoryInfo.DirtyMemory); + lastDirtyMemoryReads.Add(dirtyMemory); 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); } - if (lastDirtyMemoryReads.Count < 3) + switch (lastDirtyMemoryReads.Count) { - Log.Debug("Not enough dirty memory data in the series to calculate a trend."); - } - - // 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 - if (lastDirtyMemoryReads.Count >= 3 && AnalyzeTrendUsingRegression(lastDirtyMemoryReads) == TrendDirection.Increasing) - { - var message = $"Dirty memory is increasing. Last available value is {memoryInfo.DirtyMemory}kb. " + - $"Check the ServiceControl troubleshooting guide for guidance on how to mitigate the issue."; - Log.Warn(message); - return CheckResult.Failed(message); + case < 3: + Log.Debug("Not enough 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 = $"Dirty memory is increasing. Last available value is {dirtyMemory}kb. " + + $"Check the ServiceControl troubleshooting guide for guidance on how to mitigate the issue."; + Log.Warn(message); + return CheckResult.Failed(message); + } } return CheckResult.Pass; @@ -53,7 +54,7 @@ public override async Task PerformCheck(CancellationToken cancellat static TrendDirection AnalyzeTrendUsingRegression(List values) { - if (values == null || values.Count <= 1) + if (values is not { Count: > 1 }) { throw new ArgumentException("Need at least two values to determine a trend"); } @@ -84,12 +85,7 @@ static TrendDirection AnalyzeTrendUsingRegression(List values) return TrendDirection.Flat; } - if (slope > 0) - { - return TrendDirection.Increasing; - } - - return TrendDirection.Decreasing; + return slope > 0 ? TrendDirection.Increasing : TrendDirection.Decreasing; } enum TrendDirection From 2364de787b25c6d8aacf6746edfa861d17614a8c Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 11 Mar 2025 10:19:48 +0100 Subject: [PATCH 07/24] Because editorconfig --- .../CustomChecks/CheckDirtyMemory.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index ab233e01ca..9b0de38741 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -12,7 +12,7 @@ class CheckDirtyMemory(DatabaseConfiguration databaseConfiguration) : CustomChec readonly List lastDirtyMemoryReads = []; public override async Task PerformCheck(CancellationToken cancellationToken = default) { - var retriever = await GetMemoryRetriever(); + var retriever = GetMemoryRetriever(); var (isHighDirty, dirtyMemory) = await retriever.GetMemoryInformation(cancellationToken); if (isHighDirty) @@ -44,13 +44,17 @@ public override async Task PerformCheck(CancellationToken cancellat Log.Warn(message); return CheckResult.Failed(message); } + + default: + // NOP + break; } return CheckResult.Pass; } MemoryInformationRetriever _retriever; - async Task GetMemoryRetriever() => _retriever ??= new MemoryInformationRetriever(databaseConfiguration.ServerConfiguration.ServerUrl); + MemoryInformationRetriever GetMemoryRetriever() => _retriever ??= new MemoryInformationRetriever(databaseConfiguration.ServerConfiguration.ServerUrl); static TrendDirection AnalyzeTrendUsingRegression(List values) { @@ -77,7 +81,7 @@ static TrendDirection AnalyzeTrendUsingRegression(List values) sumXX += x * x; } - double slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX); + double slope = ((n * sumXY) - (sumX * sumY)) / ((n * sumXX) - (sumX * sumX)); // Determine trend based on slope if (Math.Abs(slope) < 0.001) // Small threshold to handle floating-point precision From 652aaf4c51cd85258b4eee860d5ad0bb393a7b35 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 11 Mar 2025 15:28:17 +0100 Subject: [PATCH 08/24] Use better variable names --- .../CustomChecks/CheckDirtyMemory.cs | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index 9b0de38741..b509a72396 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -64,27 +64,31 @@ static TrendDirection AnalyzeTrendUsingRegression(List values) } // Calculate slope using linear regression - double n = values.Count; - double sumX = 0; - double sumY = 0; - double sumXY = 0; - double sumXX = 0; + 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 x = i; - double y = values[i]; + double index = i; + double value = values[i]; - sumX += x; - sumY += y; - sumXY += x * y; - sumXX += x * x; + sumOfIndices += index; + sumOfValues += value; + sumOfIndicesMultipliedByValues += index * value; + sumOfIndicesSquared += index * index; } - double slope = ((n * sumXY) - (sumX * sumY)) / ((n * sumXX) - (sumX * sumX)); + // 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 - if (Math.Abs(slope) < 0.001) // Small threshold to handle floating-point precision + const double slopeThreshold = 0.001; // Small threshold to handle floating-point precision + if (Math.Abs(slope) < slopeThreshold) { return TrendDirection.Flat; } From f08d19bfcc54097fbbd7fccf712ceab45f613c7d Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 11 Mar 2025 15:32:17 +0100 Subject: [PATCH 09/24] Refactor the memory information retriever to check the content schema --- .../MemoryInformationRetriever.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs b/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs index 4ff9f236c4..613d02ad10 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs @@ -22,11 +22,16 @@ record MemoryInformation public string DirtyMemory { get; set; } } - public async Task<(bool IsHighDirty, int DirtyMemory)> GetMemoryInformation(CancellationToken cancellationToken = default) + public async Task<(bool IsHighDirty, int DirtyMemoryKb)> GetMemoryInformation(CancellationToken cancellationToken = default) { var httpResponse = await client.GetAsync("/admin/debug/memory/stats", cancellationToken); var responseDto = JsonSerializer.Deserialize(await httpResponse.Content.ReadAsStringAsync(cancellationToken)); - return (responseDto.MemoryInformation.IsHighDirty, int.Parse(responseDto.MemoryInformation.DirtyMemory.Split(' ').First())); + var values = responseDto.MemoryInformation.DirtyMemory.Split(' '); + if (!string.Equals(values[1],"KBytes", StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException($"Unexpected response. Was expecting memory details in KBytes, instead received: {responseDto.MemoryInformation.DirtyMemory}"); + } + return (responseDto.MemoryInformation.IsHighDirty, int.Parse(values[0])); } } \ No newline at end of file From 44db42ebeade6e0b0bbba0299b0cd5366a27440f Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 11 Mar 2025 15:36:49 +0100 Subject: [PATCH 10/24] Register the MemoryInformationRetriever in DI --- .../CustomChecks/CheckDirtyMemory.cs | 14 +++++--------- .../MemoryInformationRetriever.cs | 5 ++--- .../RavenPersistence.cs | 1 + 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index b509a72396..67261f773a 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -7,23 +7,22 @@ namespace ServiceControl.Audit.Persistence.RavenDB.CustomChecks; using NServiceBus.CustomChecks; using NServiceBus.Logging; -class CheckDirtyMemory(DatabaseConfiguration databaseConfiguration) : CustomCheck("ServiceControl.Audit database", "Dirty memory trends", TimeSpan.FromMinutes(5)) +class CheckDirtyMemory(MemoryInformationRetriever memoryInformationRetriever) : CustomCheck("ServiceControl.Audit database", "Dirty memory trends", TimeSpan.FromMinutes(5)) { readonly List lastDirtyMemoryReads = []; public override async Task PerformCheck(CancellationToken cancellationToken = default) { - var retriever = GetMemoryRetriever(); - var (isHighDirty, dirtyMemory) = await retriever.GetMemoryInformation(cancellationToken); + var (isHighDirty, dirtyMemoryKb) = await memoryInformationRetriever.GetMemoryInformation(cancellationToken); if (isHighDirty) { - var message = $"There is a high level of dirty memory ({dirtyMemory}kb). Check the ServiceControl " + + var message = $"There is a high level of dirty memory ({dirtyMemoryKb}kb). Check the ServiceControl " + "troubleshooting guide for guidance on how to mitigate the issue."; Log.Warn(message); return CheckResult.Failed(message); } - lastDirtyMemoryReads.Add(dirtyMemory); + 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 @@ -39,7 +38,7 @@ public override async Task PerformCheck(CancellationToken cancellat // Three means we'll be observing for 15 minutes before calculating the trend case >= 3 when AnalyzeTrendUsingRegression(lastDirtyMemoryReads) == TrendDirection.Increasing: { - var message = $"Dirty memory is increasing. Last available value is {dirtyMemory}kb. " + + var message = $"Dirty memory is increasing. Last available value is {dirtyMemoryKb}kb. " + $"Check the ServiceControl troubleshooting guide for guidance on how to mitigate the issue."; Log.Warn(message); return CheckResult.Failed(message); @@ -53,9 +52,6 @@ public override async Task PerformCheck(CancellationToken cancellat return CheckResult.Pass; } - MemoryInformationRetriever _retriever; - MemoryInformationRetriever GetMemoryRetriever() => _retriever ??= new MemoryInformationRetriever(databaseConfiguration.ServerConfiguration.ServerUrl); - static TrendDirection AnalyzeTrendUsingRegression(List values) { if (values is not { Count: > 1 }) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs b/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs index 613d02ad10..ea22ec5263 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs @@ -1,15 +1,14 @@ namespace ServiceControl.Audit.Persistence.RavenDB; using System; -using System.Linq; using System.Net.Http; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -class MemoryInformationRetriever(string serverUrl) +class MemoryInformationRetriever(DatabaseConfiguration databaseConfiguration) { - readonly HttpClient client = new() { BaseAddress = new Uri(serverUrl) }; + readonly HttpClient client = new() { BaseAddress = new Uri(databaseConfiguration.ServerConfiguration.ServerUrl) }; record ResponseDto { diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistence.cs b/src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistence.cs index 75081a0547..0cd872cbe8 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistence.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/RavenPersistence.cs @@ -21,6 +21,7 @@ public void AddPersistence(IServiceCollection services) static void ConfigureLifecycle(IServiceCollection services, DatabaseConfiguration databaseConfiguration) { services.AddSingleton(databaseConfiguration); + services.AddSingleton(); services.AddSingleton(); services.AddHostedService(); From 8fa559371c21b1184be368cab76d1af49a29f205 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 11 Mar 2025 15:40:01 +0100 Subject: [PATCH 11/24] Fix formatting --- .../MemoryInformationRetriever.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs b/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs index ea22ec5263..3502c9244c 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs @@ -27,7 +27,7 @@ record MemoryInformation var responseDto = JsonSerializer.Deserialize(await httpResponse.Content.ReadAsStringAsync(cancellationToken)); var values = responseDto.MemoryInformation.DirtyMemory.Split(' '); - if (!string.Equals(values[1],"KBytes", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(values[1], "KBytes", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException($"Unexpected response. Was expecting memory details in KBytes, instead received: {responseDto.MemoryInformation.DirtyMemory}"); } From 37e1d6f493997359eacdd1d6341efedfdf24721f Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 11 Mar 2025 18:58:36 +0100 Subject: [PATCH 12/24] Update the HTTP GET URL to trim the response size --- .../MemoryInformationRetriever.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs b/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs index 3502c9244c..5eaefacd45 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs @@ -23,7 +23,7 @@ record MemoryInformation public async Task<(bool IsHighDirty, int DirtyMemoryKb)> GetMemoryInformation(CancellationToken cancellationToken = default) { - var httpResponse = await client.GetAsync("/admin/debug/memory/stats", cancellationToken); + var httpResponse = await client.GetAsync("/admin/debug/memory/stats?includeThreads=false&includeMappings=false", cancellationToken); var responseDto = JsonSerializer.Deserialize(await httpResponse.Content.ReadAsStringAsync(cancellationToken)); var values = responseDto.MemoryInformation.DirtyMemory.Split(' '); From 4396f4118c7d90b59cae2f7aa2d4337e8b0bbc91 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 11 Mar 2025 19:14:50 +0100 Subject: [PATCH 13/24] Make the custom check work in both embedded and external mode --- .../MemoryInformationRetriever.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs b/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs index 5eaefacd45..3bab2dc7b5 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs @@ -8,7 +8,10 @@ namespace ServiceControl.Audit.Persistence.RavenDB; class MemoryInformationRetriever(DatabaseConfiguration databaseConfiguration) { - readonly HttpClient client = new() { BaseAddress = new Uri(databaseConfiguration.ServerConfiguration.ServerUrl) }; + // TODO what does a connection string look like? Is it only a URI or could it contain other stuff? + // The ?? operator is needed because ServerUrl is populated when running embedded and connection string when running in external mode. + // However the tricky part is that when tests are run they behave like if it was external mode + readonly HttpClient client = new() { BaseAddress = new Uri(databaseConfiguration.ServerConfiguration.ServerUrl ?? databaseConfiguration.ServerConfiguration.ConnectionString) }; record ResponseDto { From fd681ea045b8aa084001f6a380b210f45820b4d3 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 11 Mar 2025 19:15:06 +0100 Subject: [PATCH 14/24] Custom checks approved list --- .../CustomCheckTests.VerifyCustomChecks.approved.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ServiceControl.Audit.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt b/src/ServiceControl.Audit.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt index 2dd77f440a..1ee566332d 100644 --- a/src/ServiceControl.Audit.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt +++ b/src/ServiceControl.Audit.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt @@ -1,3 +1,4 @@ +Dirty memory trends: ServiceControl.Audit database ServiceControl.Audit Health: Audit Database Index Lag ServiceControl.Audit Health: Audit Message Ingestion Process Storage space: ServiceControl.Audit database \ No newline at end of file From b1cb4cc77f6b2aa01b58b2560d08e84e1ccdf6a0 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 11 Mar 2025 21:10:22 +0100 Subject: [PATCH 15/24] Update log statements to mention RavenDB --- .../CustomChecks/CheckDirtyMemory.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index 67261f773a..e100872589 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -16,7 +16,7 @@ public override async Task PerformCheck(CancellationToken cancellat if (isHighDirty) { - var message = $"There is a high level of dirty memory ({dirtyMemoryKb}kb). Check the ServiceControl " + + 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."; Log.Warn(message); return CheckResult.Failed(message); @@ -32,13 +32,13 @@ public override async Task PerformCheck(CancellationToken cancellat switch (lastDirtyMemoryReads.Count) { case < 3: - Log.Debug("Not enough dirty memory data in the series to calculate a trend."); + 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 = $"Dirty memory is increasing. Last available value is {dirtyMemoryKb}kb. " + + 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."; Log.Warn(message); return CheckResult.Failed(message); From 0d5325f219cd7e8a250d91da42e8d4a3b5e7de7c Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Wed, 12 Mar 2025 09:38:37 +0100 Subject: [PATCH 16/24] Properly invert custom check id and category --- .../CustomChecks/CheckDirtyMemory.cs | 2 +- .../CustomCheckTests.VerifyCustomChecks.approved.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index e100872589..faa30435ab 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -7,7 +7,7 @@ namespace ServiceControl.Audit.Persistence.RavenDB.CustomChecks; using NServiceBus.CustomChecks; using NServiceBus.Logging; -class CheckDirtyMemory(MemoryInformationRetriever memoryInformationRetriever) : CustomCheck("ServiceControl.Audit database", "Dirty memory trends", TimeSpan.FromMinutes(5)) +class CheckDirtyMemory(MemoryInformationRetriever memoryInformationRetriever) : CustomCheck("RavenDB dirty memory trends", "ServiceControl.Audit Health", TimeSpan.FromMinutes(5)) { readonly List lastDirtyMemoryReads = []; public override async Task PerformCheck(CancellationToken cancellationToken = default) diff --git a/src/ServiceControl.Audit.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt b/src/ServiceControl.Audit.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt index 1ee566332d..0e8182dded 100644 --- a/src/ServiceControl.Audit.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt +++ b/src/ServiceControl.Audit.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt @@ -1,4 +1,4 @@ -Dirty memory trends: ServiceControl.Audit database ServiceControl.Audit Health: Audit Database Index Lag ServiceControl.Audit Health: Audit Message Ingestion Process +ServiceControl.Audit Health: RavenDB dirty memory trends Storage space: ServiceControl.Audit database \ No newline at end of file From 86d1612e6a6d63195f0144e79f667b789cf3bfdd Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Wed, 12 Mar 2025 09:39:43 +0100 Subject: [PATCH 17/24] Add the CheckDirtyMemory custom check to the primary instance --- .../RavenPersistence.cs | 2 + .../CustomChecks/CheckDirtyMemory.cs | 103 ++++++++++++++++++ .../MemoryInformationRetriever.cs | 37 +++++++ ...IApprovals.CustomCheckDetails.approved.txt | 1 + ...CheckTests.VerifyCustomChecks.approved.txt | 1 + 5 files changed, 144 insertions(+) create mode 100644 src/ServiceControl.Persistence.RavenDb/CustomChecks/CheckDirtyMemory.cs create mode 100644 src/ServiceControl.Persistence.RavenDb/MemoryInformationRetriever.cs diff --git a/src/ServiceControl.Persistence.RavenDB/RavenPersistence.cs b/src/ServiceControl.Persistence.RavenDB/RavenPersistence.cs index 7b1e07ecbb..93a8fe63b7 100644 --- a/src/ServiceControl.Persistence.RavenDB/RavenPersistence.cs +++ b/src/ServiceControl.Persistence.RavenDB/RavenPersistence.cs @@ -49,7 +49,9 @@ public void AddPersistence(IServiceCollection services) services.AddCustomCheck(); services.AddCustomCheck(); services.AddCustomCheck(); + services.AddCustomCheck(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/ServiceControl.Persistence.RavenDb/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Persistence.RavenDb/CustomChecks/CheckDirtyMemory.cs new file mode 100644 index 0000000000..41ca21b319 --- /dev/null +++ b/src/ServiceControl.Persistence.RavenDb/CustomChecks/CheckDirtyMemory.cs @@ -0,0 +1,103 @@ +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 lastDirtyMemoryReads = []; + public override async Task 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."; + 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."; + Log.Warn(message); + return CheckResult.Failed(message); + } + + default: + // NOP + break; + } + + return CheckResult.Pass; + } + + static TrendDirection AnalyzeTrendUsingRegression(List 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(); +} \ No newline at end of file diff --git a/src/ServiceControl.Persistence.RavenDb/MemoryInformationRetriever.cs b/src/ServiceControl.Persistence.RavenDb/MemoryInformationRetriever.cs new file mode 100644 index 0000000000..6203e18eb7 --- /dev/null +++ b/src/ServiceControl.Persistence.RavenDb/MemoryInformationRetriever.cs @@ -0,0 +1,37 @@ +namespace ServiceControl.Persistence.RavenDB; + +using System; +using System.Net.Http; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +class MemoryInformationRetriever(RavenPersisterSettings persisterSettings) +{ + // TODO what does a connection string look like? Is it only a URI or could it contain other stuff? + readonly HttpClient client = new() { BaseAddress = new Uri(persisterSettings.ConnectionString) }; + + record ResponseDto + { + public MemoryInformation MemoryInformation { get; set; } + } + + record MemoryInformation + { + public bool IsHighDirty { get; set; } + public string DirtyMemory { get; set; } + } + + public async Task<(bool IsHighDirty, int DirtyMemoryKb)> GetMemoryInformation(CancellationToken cancellationToken = default) + { + var httpResponse = await client.GetAsync("/admin/debug/memory/stats?includeThreads=false&includeMappings=false", cancellationToken); + var responseDto = JsonSerializer.Deserialize(await httpResponse.Content.ReadAsStringAsync(cancellationToken)); + + var values = responseDto.MemoryInformation.DirtyMemory.Split(' '); + if (!string.Equals(values[1], "KBytes", StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException($"Unexpected response. Was expecting memory details in KBytes, instead received: {responseDto.MemoryInformation.DirtyMemory}"); + } + return (responseDto.MemoryInformation.IsHighDirty, int.Parse(values[0])); + } +} \ No newline at end of file diff --git a/src/ServiceControl.Persistence.Tests.RavenDB/ApprovalFiles/APIApprovals.CustomCheckDetails.approved.txt b/src/ServiceControl.Persistence.Tests.RavenDB/ApprovalFiles/APIApprovals.CustomCheckDetails.approved.txt index 8c907ba40d..1e5ca563f0 100644 --- a/src/ServiceControl.Persistence.Tests.RavenDB/ApprovalFiles/APIApprovals.CustomCheckDetails.approved.txt +++ b/src/ServiceControl.Persistence.Tests.RavenDB/ApprovalFiles/APIApprovals.CustomCheckDetails.approved.txt @@ -1,4 +1,5 @@ ServiceControl Health: Error Database Index Errors ServiceControl Health: Error Database Index Lag ServiceControl Health: Message Ingestion Process +ServiceControl Health: RavenDB dirty memory trends Storage space: ServiceControl database \ No newline at end of file diff --git a/src/ServiceControl.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt b/src/ServiceControl.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt index 8c907ba40d..1e5ca563f0 100644 --- a/src/ServiceControl.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt +++ b/src/ServiceControl.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt @@ -1,4 +1,5 @@ ServiceControl Health: Error Database Index Errors ServiceControl Health: Error Database Index Lag ServiceControl Health: Message Ingestion Process +ServiceControl Health: RavenDB dirty memory trends Storage space: ServiceControl database \ No newline at end of file From 151e29d9534093dd74ccedeb4acb6556c3b30be1 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Wed, 12 Mar 2025 11:31:27 +0100 Subject: [PATCH 18/24] Link to the troubleshooting guidance page --- .../CustomChecks/CheckDirtyMemory.cs | 6 ++++-- .../CustomChecks/CheckDirtyMemory.cs | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index faa30435ab..86a713c6b0 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -17,7 +17,8 @@ public override async Task PerformCheck(CancellationToken cancellat 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."; + "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); } @@ -39,7 +40,8 @@ public override async Task PerformCheck(CancellationToken cancellat 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."; + $"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); } diff --git a/src/ServiceControl.Persistence.RavenDb/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Persistence.RavenDb/CustomChecks/CheckDirtyMemory.cs index 41ca21b319..bb78011af1 100644 --- a/src/ServiceControl.Persistence.RavenDb/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Persistence.RavenDb/CustomChecks/CheckDirtyMemory.cs @@ -17,7 +17,8 @@ public override async Task PerformCheck(CancellationToken cancellat 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."; + "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); } @@ -39,7 +40,8 @@ public override async Task PerformCheck(CancellationToken cancellat 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."; + $"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); } From f83d6f7b81b8b600045e4b7b7cbc1c0f2e7dec69 Mon Sep 17 00:00:00 2001 From: Brandon Ording Date: Wed, 12 Mar 2025 16:38:35 -0400 Subject: [PATCH 19/24] Fix casing --- .../CustomChecks/CheckDirtyMemory.cs | 0 .../MemoryInformationRetriever.cs | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/{ServiceControl.Persistence.RavenDb => ServiceControl.Persistence.RavenDB}/CustomChecks/CheckDirtyMemory.cs (100%) rename src/{ServiceControl.Persistence.RavenDb => ServiceControl.Persistence.RavenDB}/MemoryInformationRetriever.cs (100%) diff --git a/src/ServiceControl.Persistence.RavenDb/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs similarity index 100% rename from src/ServiceControl.Persistence.RavenDb/CustomChecks/CheckDirtyMemory.cs rename to src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs diff --git a/src/ServiceControl.Persistence.RavenDb/MemoryInformationRetriever.cs b/src/ServiceControl.Persistence.RavenDB/MemoryInformationRetriever.cs similarity index 100% rename from src/ServiceControl.Persistence.RavenDb/MemoryInformationRetriever.cs rename to src/ServiceControl.Persistence.RavenDB/MemoryInformationRetriever.cs From daddc0c546b941079a1da27f75b8c9761b5b21a0 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 13 Mar 2025 15:42:34 +0100 Subject: [PATCH 20/24] Remove the trends evaluation from the dirty memory custom check It'll be added in a separate PR --- .../CustomChecks/CheckDirtyMemory.cs | 77 ------------------- .../CustomChecks/CheckDirtyMemory.cs | 77 ------------------- 2 files changed, 154 deletions(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index 86a713c6b0..2cd2a96701 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -1,7 +1,6 @@ namespace ServiceControl.Audit.Persistence.RavenDB.CustomChecks; using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using NServiceBus.CustomChecks; @@ -9,7 +8,6 @@ namespace ServiceControl.Audit.Persistence.RavenDB.CustomChecks; class CheckDirtyMemory(MemoryInformationRetriever memoryInformationRetriever) : CustomCheck("RavenDB dirty memory trends", "ServiceControl.Audit Health", TimeSpan.FromMinutes(5)) { - readonly List lastDirtyMemoryReads = []; public override async Task PerformCheck(CancellationToken cancellationToken = default) { var (isHighDirty, dirtyMemoryKb) = await memoryInformationRetriever.GetMemoryInformation(cancellationToken); @@ -23,83 +21,8 @@ public override async Task PerformCheck(CancellationToken cancellat 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 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(); } \ No newline at end of file diff --git a/src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index bb78011af1..8d8dae0f9f 100644 --- a/src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -1,7 +1,6 @@ namespace ServiceControl.Persistence.RavenDB.CustomChecks; using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using NServiceBus.CustomChecks; @@ -9,7 +8,6 @@ namespace ServiceControl.Persistence.RavenDB.CustomChecks; class CheckDirtyMemory(MemoryInformationRetriever memoryInformationRetriever) : CustomCheck("RavenDB dirty memory trends", "ServiceControl Health", TimeSpan.FromMinutes(5)) { - readonly List lastDirtyMemoryReads = []; public override async Task PerformCheck(CancellationToken cancellationToken = default) { var (isHighDirty, dirtyMemoryKb) = await memoryInformationRetriever.GetMemoryInformation(cancellationToken); @@ -23,83 +21,8 @@ public override async Task PerformCheck(CancellationToken cancellat 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 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(); } \ No newline at end of file From a5bdd85c30e47d2152ac27bd41fbcf0bbd6f067d Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 13 Mar 2025 15:58:24 +0100 Subject: [PATCH 21/24] Deep link to guidance --- .../CustomChecks/CheckDirtyMemory.cs | 2 +- .../CustomChecks/CheckDirtyMemory.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index 2cd2a96701..daca8474dd 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -16,7 +16,7 @@ public override async Task PerformCheck(CancellationToken cancellat { 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."; + "Visit the https://docs.particular.net/servicecontrol/troubleshooting#ravendb-dirty-memory page for more information."; Log.Warn(message); return CheckResult.Failed(message); } diff --git a/src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index 8d8dae0f9f..8318efacc8 100644 --- a/src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -16,7 +16,7 @@ public override async Task PerformCheck(CancellationToken cancellat { 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."; + "Visit the https://docs.particular.net/servicecontrol/troubleshooting#ravendb-dirty-memory page for more information."; Log.Warn(message); return CheckResult.Failed(message); } From d13d41257109a7e373231c9d17df94d43b4b043a Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 14 Mar 2025 09:49:38 +0100 Subject: [PATCH 22/24] Reword custom check and log warning message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andreas Öhlund --- .../CustomChecks/CheckDirtyMemory.cs | 4 +--- .../CustomChecks/CheckDirtyMemory.cs | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index daca8474dd..228c7e13d4 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -14,9 +14,7 @@ public override async Task PerformCheck(CancellationToken cancellat 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#ravendb-dirty-memory page for more information."; + var message = $"There is a high level of RavenDB dirty memory ({dirtyMemoryKb}kb). See https://docs.particular.net/servicecontrol/troubleshooting#ravendb-dirty-memory for guidance on how to mitigate the issue."; Log.Warn(message); return CheckResult.Failed(message); } diff --git a/src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index 8318efacc8..b073e934f5 100644 --- a/src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -14,9 +14,7 @@ public override async Task PerformCheck(CancellationToken cancellat 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#ravendb-dirty-memory page for more information."; + var message = $"There is a high level of RavenDB dirty memory ({dirtyMemoryKb}kb). See https://docs.particular.net/servicecontrol/troubleshooting#ravendb-dirty-memory for guidance on how to mitigate the issue."; Log.Warn(message); return CheckResult.Failed(message); } From 631e7f94df766a8a8fc28f4d81015b0861043101 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 14 Mar 2025 10:03:13 +0100 Subject: [PATCH 23/24] Rename the dirty memory custom check ID --- .../CustomChecks/CheckDirtyMemory.cs | 2 +- .../CustomCheckTests.VerifyCustomChecks.approved.txt | 2 +- .../CustomChecks/CheckDirtyMemory.cs | 2 +- .../ApprovalFiles/APIApprovals.CustomCheckDetails.approved.txt | 2 +- .../CustomCheckTests.VerifyCustomChecks.approved.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index 228c7e13d4..cedca079c4 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -6,7 +6,7 @@ namespace ServiceControl.Audit.Persistence.RavenDB.CustomChecks; using NServiceBus.CustomChecks; using NServiceBus.Logging; -class CheckDirtyMemory(MemoryInformationRetriever memoryInformationRetriever) : CustomCheck("RavenDB dirty memory trends", "ServiceControl.Audit Health", TimeSpan.FromMinutes(5)) +class CheckDirtyMemory(MemoryInformationRetriever memoryInformationRetriever) : CustomCheck("RavenDB dirty memory", "ServiceControl.Audit Health", TimeSpan.FromMinutes(5)) { public override async Task PerformCheck(CancellationToken cancellationToken = default) { diff --git a/src/ServiceControl.Audit.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt b/src/ServiceControl.Audit.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt index 0e8182dded..f510666c86 100644 --- a/src/ServiceControl.Audit.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt +++ b/src/ServiceControl.Audit.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt @@ -1,4 +1,4 @@ ServiceControl.Audit Health: Audit Database Index Lag ServiceControl.Audit Health: Audit Message Ingestion Process -ServiceControl.Audit Health: RavenDB dirty memory trends +ServiceControl.Audit Health: RavenDB dirty memory Storage space: ServiceControl.Audit database \ No newline at end of file diff --git a/src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs b/src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs index b073e934f5..fb0ec20c96 100644 --- a/src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs +++ b/src/ServiceControl.Persistence.RavenDB/CustomChecks/CheckDirtyMemory.cs @@ -6,7 +6,7 @@ namespace ServiceControl.Persistence.RavenDB.CustomChecks; using NServiceBus.CustomChecks; using NServiceBus.Logging; -class CheckDirtyMemory(MemoryInformationRetriever memoryInformationRetriever) : CustomCheck("RavenDB dirty memory trends", "ServiceControl Health", TimeSpan.FromMinutes(5)) +class CheckDirtyMemory(MemoryInformationRetriever memoryInformationRetriever) : CustomCheck("RavenDB dirty memory", "ServiceControl Health", TimeSpan.FromMinutes(5)) { public override async Task PerformCheck(CancellationToken cancellationToken = default) { diff --git a/src/ServiceControl.Persistence.Tests.RavenDB/ApprovalFiles/APIApprovals.CustomCheckDetails.approved.txt b/src/ServiceControl.Persistence.Tests.RavenDB/ApprovalFiles/APIApprovals.CustomCheckDetails.approved.txt index 1e5ca563f0..ecb6bed984 100644 --- a/src/ServiceControl.Persistence.Tests.RavenDB/ApprovalFiles/APIApprovals.CustomCheckDetails.approved.txt +++ b/src/ServiceControl.Persistence.Tests.RavenDB/ApprovalFiles/APIApprovals.CustomCheckDetails.approved.txt @@ -1,5 +1,5 @@ ServiceControl Health: Error Database Index Errors ServiceControl Health: Error Database Index Lag ServiceControl Health: Message Ingestion Process -ServiceControl Health: RavenDB dirty memory trends +ServiceControl Health: RavenDB dirty memory Storage space: ServiceControl database \ No newline at end of file diff --git a/src/ServiceControl.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt b/src/ServiceControl.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt index 1e5ca563f0..ecb6bed984 100644 --- a/src/ServiceControl.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt +++ b/src/ServiceControl.Persistence.Tests.RavenDB/ApprovalFiles/CustomCheckTests.VerifyCustomChecks.approved.txt @@ -1,5 +1,5 @@ ServiceControl Health: Error Database Index Errors ServiceControl Health: Error Database Index Lag ServiceControl Health: Message Ingestion Process -ServiceControl Health: RavenDB dirty memory trends +ServiceControl Health: RavenDB dirty memory Storage space: ServiceControl database \ No newline at end of file From 9783a38ce0ab48588487be50bb1759dc7c9aa7cf Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 14 Mar 2025 10:07:02 +0100 Subject: [PATCH 24/24] reword comments --- .../MemoryInformationRetriever.cs | 8 +++++--- .../MemoryInformationRetriever.cs | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs b/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs index 3bab2dc7b5..eea7d87865 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDB/MemoryInformationRetriever.cs @@ -8,9 +8,11 @@ namespace ServiceControl.Audit.Persistence.RavenDB; class MemoryInformationRetriever(DatabaseConfiguration databaseConfiguration) { - // TODO what does a connection string look like? Is it only a URI or could it contain other stuff? - // The ?? operator is needed because ServerUrl is populated when running embedded and connection string when running in external mode. - // However the tricky part is that when tests are run they behave like if it was external mode + // What does a connection string look like? Is it only a URI or could it contain other stuff? + // The ?? operator is needed because ServerUrl is populated when running embedded and connection + // string when running in external mode. However, the tricky part is that when tests are run they + // behave like if it was external mode. If the connection string contain always only the server + // URL, this code is safe, otherwise it need to be adjusted to extract the server URL. readonly HttpClient client = new() { BaseAddress = new Uri(databaseConfiguration.ServerConfiguration.ServerUrl ?? databaseConfiguration.ServerConfiguration.ConnectionString) }; record ResponseDto diff --git a/src/ServiceControl.Persistence.RavenDB/MemoryInformationRetriever.cs b/src/ServiceControl.Persistence.RavenDB/MemoryInformationRetriever.cs index 6203e18eb7..485c6b6640 100644 --- a/src/ServiceControl.Persistence.RavenDB/MemoryInformationRetriever.cs +++ b/src/ServiceControl.Persistence.RavenDB/MemoryInformationRetriever.cs @@ -8,7 +8,10 @@ namespace ServiceControl.Persistence.RavenDB; class MemoryInformationRetriever(RavenPersisterSettings persisterSettings) { - // TODO what does a connection string look like? Is it only a URI or could it contain other stuff? + // What does a connection string look like? Is it only a URI or could it contain other stuff? + // The primary instance has only the concept of a connection string (vs the Audit instance having + // both a ServiceUrl and a ConnectionString). If the connection string contain always only the + // server URL, this code is safe, otherwise it need to be adjusted to extract the server URL. readonly HttpClient client = new() { BaseAddress = new Uri(persisterSettings.ConnectionString) }; record ResponseDto