From db18a396c24ae014d9f7dcfe3e913afd0bf92512 Mon Sep 17 00:00:00 2001 From: laurentiu021 Date: Mon, 18 May 2026 13:03:44 +0300 Subject: [PATCH] fix: optimize ConsoleViewModel trimming and LogsViewModel batch dispatch --- CHANGELOG.md | 10 ++++++ .../SysManager/ViewModels/ConsoleViewModel.cs | 20 +++++++++--- .../SysManager/ViewModels/LogsViewModel.cs | 31 +++++++++++++++++-- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f421094e..28b364c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [0.48.33] - 2026-05-18 + +### Changed +- **ConsoleViewModel** — buffer trimming now uses clear-and-rebuild when + removing more than 25% of lines, reducing worst-case from O(n×excess) + to O(n) (CQ-LOW: ConsoleViewModel O(n²)). +- **LogsViewModel** — event log entries are now dispatched to the UI thread + in batches of 50 instead of one-at-a-time, reducing dispatcher overhead + by ~98% when loading large event logs (CQ-LOW: LogsViewModel batch dispatch). + ## [0.48.32] - 2026-05-18 ### Fixed diff --git a/SysManager/SysManager/ViewModels/ConsoleViewModel.cs b/SysManager/SysManager/ViewModels/ConsoleViewModel.cs index bb0b0fe9..f8c621b3 100644 --- a/SysManager/SysManager/ViewModels/ConsoleViewModel.cs +++ b/SysManager/SysManager/ViewModels/ConsoleViewModel.cs @@ -38,11 +38,23 @@ public void Append(PowerShellLine line) Lines.Add(line); if (Lines.Count > MaxLines) { - // PERF-005: Remove excess items from front (index 0). Each removal - // is O(n) but unavoidable with ObservableCollection. + // PERF: When excess is large relative to buffer, clear-and-rebuild + // is O(n) vs O(n*excess) for repeated RemoveAt(0). var excess = Lines.Count - MaxLines; - for (int i = 0; i < excess; i++) - Lines.RemoveAt(0); + if (excess > Lines.Count / 4) + { + var keep = new PowerShellLine[MaxLines]; + for (int i = 0; i < MaxLines; i++) + keep[i] = Lines[excess + i]; + Lines.Clear(); + foreach (var item in keep) + Lines.Add(item); + } + else + { + for (int i = 0; i < excess; i++) + Lines.RemoveAt(0); + } } } } diff --git a/SysManager/SysManager/ViewModels/LogsViewModel.cs b/SysManager/SysManager/ViewModels/LogsViewModel.cs index 22d62e94..05875120 100644 --- a/SysManager/SysManager/ViewModels/LogsViewModel.cs +++ b/SysManager/SysManager/ViewModels/LogsViewModel.cs @@ -132,14 +132,41 @@ private async Task RefreshAsync() try { + const int batchSize = 50; + var batch = new List(batchSize); + await foreach (var entry in _eventLogs.ReadAsync(opt, _cts.Token)) { + batch.Add(entry); + if (batch.Count >= batchSize) + { + var items = batch.ToArray(); + batch.Clear(); + Post(() => + { + foreach (var item in items) + { + Entries.Add(item); + UpdateCounts(item, 1); + } + }); + } + } + + // Flush remaining items + if (batch.Count > 0) + { + var remaining = batch.ToArray(); Post(() => { - Entries.Add(entry); - UpdateCounts(entry, 1); + foreach (var item in remaining) + { + Entries.Add(item); + UpdateCounts(item, 1); + } }); } + StatusMessage = $"Loaded {Entries.Count} events from {SelectedLog}"; UpdateVisibleCount(); }