From 955b4256866445adbceeb44292afb9dd7793d00e Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 15 May 2026 11:40:34 -0400 Subject: [PATCH] Log process memory at the end of each collection cycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lite has had no internal record of its own memory usage. Bug reporters (see #933) had to read it off Task Manager, and we had no historical trace for diagnosing growth patterns. After every collection cycle — which is also after archival and retention run, so it captures the quiescent state — log: Process memory: WS=XXX MB, Private=XXX MB, GC heap=XXX MB WS is Working Set (what Task Manager shows). Private is private bytes (unique to this process, the more honest "actual RAM cost" number). GC heap is .NET-managed memory only — together with WS-Private this splits managed vs native vs shared. One INFO log line per minute. Three property reads — negligible overhead. Errors swallowed at DEBUG level (don't ever break the collection loop because we couldn't read memory stats). Co-Authored-By: Claude Opus 4.7 (1M context) --- Lite/Services/CollectionBackgroundService.cs | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Lite/Services/CollectionBackgroundService.cs b/Lite/Services/CollectionBackgroundService.cs index 6db67e31..82c8b032 100644 --- a/Lite/Services/CollectionBackgroundService.cs +++ b/Lite/Services/CollectionBackgroundService.cs @@ -7,6 +7,7 @@ */ using System; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; @@ -114,6 +115,13 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) /* Periodic retention cleanup */ RunRetentionIfDue(); + + /* Log process memory at the end of each cycle. Lets bug reporters + self-report memory without Task Manager, gives us a continuous + memory trace for diagnosis, and surfaces regressions in the log + that would otherwise need external sampling to detect. Three + property reads — negligible overhead at 1-minute cadence. */ + LogProcessMemory(); } try @@ -182,4 +190,22 @@ private void RunRetentionIfDue() } } + private void LogProcessMemory() + { + try + { + using var process = Process.GetCurrentProcess(); + var wsMb = process.WorkingSet64 / 1024 / 1024; + var privMb = process.PrivateMemorySize64 / 1024 / 1024; + var gcMb = GC.GetTotalMemory(forceFullCollection: false) / 1024 / 1024; + _logger?.LogInformation( + "Process memory: WS={WorkingSetMb} MB, Private={PrivateMb} MB, GC heap={GcMb} MB", + wsMb, privMb, gcMb); + } + catch (Exception ex) + { + _logger?.LogDebug(ex, "Failed to read process memory stats"); + } + } + }