From 9b576ea633671a102e8082acaa439636df5ad20f Mon Sep 17 00:00:00 2001 From: Jason Ross Date: Tue, 2 Jun 2026 14:57:48 -0500 Subject: [PATCH] Extract startup tracing behind IStartupTracer abstraction Add Microsoft.Extensions.DependencyInjection package and introduce an IStartupTracer abstraction with NoOpStartupTracer (zero-overhead default) and FileStartupTracer (renamed from WinUiStartupTrace). A StartupTracer factory selects the implementation from environment configuration. Consumers (App, MainWindow, MainWindowViewModel, SensorColumnMeasurer) now depend on the non-nullable interface, removing null-conditional trace calls and decoupling startup instrumentation from construction logic. No behavior change: tracing still writes the same log when enabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../App.xaml.cs | 31 +++----- .../LibreHardwareMonitor.Windows.WinUI.csproj | 1 + .../MainWindow.cs | 51 ++++++------ .../Services/SensorColumnMeasurer.cs | 7 +- .../FileStartupTracer.cs} | 52 ++---------- .../Services/Tracing/IStartupTracer.cs | 39 +++++++++ .../Services/Tracing/NoOpStartupTracer.cs | 79 +++++++++++++++++++ .../Services/Tracing/StartupTracer.cs | 56 +++++++++++++ .../ViewModels/MainWindowViewModel.cs | 25 +++--- 9 files changed, 237 insertions(+), 104 deletions(-) rename LibreHardwareMonitor.Windows.WinUI/Services/{WinUiStartupTrace.cs => Tracing/FileStartupTracer.cs} (83%) create mode 100644 LibreHardwareMonitor.Windows.WinUI/Services/Tracing/IStartupTracer.cs create mode 100644 LibreHardwareMonitor.Windows.WinUI/Services/Tracing/NoOpStartupTracer.cs create mode 100644 LibreHardwareMonitor.Windows.WinUI/Services/Tracing/StartupTracer.cs diff --git a/LibreHardwareMonitor.Windows.WinUI/App.xaml.cs b/LibreHardwareMonitor.Windows.WinUI/App.xaml.cs index f768fba..77bb031 100644 --- a/LibreHardwareMonitor.Windows.WinUI/App.xaml.cs +++ b/LibreHardwareMonitor.Windows.WinUI/App.xaml.cs @@ -5,7 +5,7 @@ using System; using System.IO; using System.Threading.Tasks; -using LibreHardwareMonitor.Windows.WinUI.Services; +using LibreHardwareMonitor.Windows.WinUI.Services.Tracing; using Microsoft.UI.Xaml; using IOPath = System.IO.Path; @@ -14,13 +14,13 @@ namespace LibreHardwareMonitor.Windows.WinUI; public partial class App : Application { private bool _launchStarted; - private readonly WinUiStartupTrace? _startupTrace; + private readonly IStartupTracer _startupTrace; private Window? _window; public App() { - _startupTrace = WinUiStartupTrace.Create(); - _startupTrace?.Mark("App.Constructor.Begin"); + _startupTrace = StartupTracer.Create(); + _startupTrace.Mark("App.Constructor.Begin"); MeasureStartup("App.WireExceptionHandlers", () => { UnhandledException += App_UnhandledException; @@ -28,8 +28,8 @@ public partial class App : Application TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; }); MeasureStartup("App.InitializeComponent", InitializeComponent); - _startupTrace?.Mark("App.Constructor.Complete"); - _startupTrace?.Flush(); + _startupTrace.Mark("App.Constructor.Complete"); + _startupTrace.Flush(); } protected override void OnLaunched(LaunchActivatedEventArgs args) @@ -45,18 +45,18 @@ public partial class App : Application _launchStarted = true; try { - _startupTrace?.Mark("App.LaunchMainWindow.Begin"); + _startupTrace.Mark("App.LaunchMainWindow.Begin"); MainWindow mainWindow = MeasureStartup("App.CreateMainWindow", () => new MainWindow(_startupTrace)); _window = mainWindow; MeasureStartup("App.ActivateWindow", mainWindow.Activate); MeasureStartup("App.StartMonitoringAfterActivation", mainWindow.StartMonitoringAfterActivation); - _startupTrace?.Mark("App.LaunchMainWindow.Complete"); - _startupTrace?.Flush(); + _startupTrace.Mark("App.LaunchMainWindow.Complete"); + _startupTrace.Flush(); } catch (Exception ex) { - _startupTrace?.Mark("App.LaunchMainWindow.Exception", $"{ex.GetType().FullName}: {ex.Message}"); - _startupTrace?.Flush(); + _startupTrace.Mark("App.LaunchMainWindow.Exception", $"{ex.GetType().FullName}: {ex.Message}"); + _startupTrace.Flush(); WriteExceptionLog("Window launch failed", ex); throw; } @@ -64,20 +64,11 @@ public partial class App : Application private void MeasureStartup(string phase, Action action) { - if (_startupTrace == null) - { - action(); - return; - } - _startupTrace.Measure(phase, action); } private T MeasureStartup(string phase, Func action) { - if (_startupTrace == null) - return action(); - return _startupTrace.Measure(phase, action); } diff --git a/LibreHardwareMonitor.Windows.WinUI/LibreHardwareMonitor.Windows.WinUI.csproj b/LibreHardwareMonitor.Windows.WinUI/LibreHardwareMonitor.Windows.WinUI.csproj index 06c33f8..e779a1e 100644 --- a/LibreHardwareMonitor.Windows.WinUI/LibreHardwareMonitor.Windows.WinUI.csproj +++ b/LibreHardwareMonitor.Windows.WinUI/LibreHardwareMonitor.Windows.WinUI.csproj @@ -21,6 +21,7 @@ + diff --git a/LibreHardwareMonitor.Windows.WinUI/MainWindow.cs b/LibreHardwareMonitor.Windows.WinUI/MainWindow.cs index 22eef24..5d31ed0 100644 --- a/LibreHardwareMonitor.Windows.WinUI/MainWindow.cs +++ b/LibreHardwareMonitor.Windows.WinUI/MainWindow.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using LibreHardwareMonitor.Hardware; using LibreHardwareMonitor.Windows.WinUI.Controls; using LibreHardwareMonitor.Windows.WinUI.Services; +using LibreHardwareMonitor.Windows.WinUI.Services.Tracing; using LibreHardwareMonitor.Windows.WinUI.Utilities; using LibreHardwareMonitor.Windows.WinUI.ViewModels; using Microsoft.UI; @@ -33,7 +34,7 @@ public sealed partial class MainWindow : Window private readonly WindowChromeManager _chromeManager; private readonly WindowPlacementService _placementService; private readonly DispatcherQueueTimer _timer; - private readonly WinUiStartupTrace? _startupTrace; + private readonly IStartupTracer _startupTrace; private readonly TrayIconService _trayIconService; private readonly DialogService _dialogService; private readonly SensorColumnMeasurer _columnMeasurer; @@ -50,14 +51,14 @@ public sealed partial class MainWindow : Window private bool _sensorTreeRebuildQueued; private bool _sensorColumnWidthUpdateQueued; - public MainWindow() : this(null) + public MainWindow() : this(NoOpStartupTracer.Instance) { } - internal MainWindow(WinUiStartupTrace? startupTrace) + internal MainWindow(IStartupTracer startupTrace) { _startupTrace = startupTrace; - _startupTrace?.Mark("MainWindow.Constructor.Begin"); + _startupTrace.Mark("MainWindow.Constructor.Begin"); AppSettings settings = MeasureStartup("MainWindow.LoadSettings", AppSettings.LoadDefault); ViewModel = MeasureStartup("MainWindow.CreateViewModel", () => new MainWindowViewModel(settings, _startupTrace)); @@ -159,14 +160,14 @@ public sealed partial class MainWindow : Window Closed += MainWindow_Closed; }); - _startupTrace?.Mark("MainWindow.Constructor.Complete"); + _startupTrace.Mark("MainWindow.Constructor.Complete"); } public MainWindowViewModel ViewModel { get; } public void StartMonitoringAfterActivation() { - _startupTrace?.Mark("MainWindow.StartMonitoringAfterActivation.Queued"); + _startupTrace.Mark("MainWindow.StartMonitoringAfterActivation.Queued"); if (!DispatcherQueue.TryEnqueue(async () => { if (_isMonitoringStarted) @@ -175,27 +176,27 @@ public sealed partial class MainWindow : Window _isMonitoringStarted = true; try { - _startupTrace?.Mark("MainWindow.StartMonitoringAfterActivation.Begin"); + _startupTrace.Mark("MainWindow.StartMonitoringAfterActivation.Begin"); ApplyInitialWindowState(); await MeasureStartupAsync("MainWindowViewModel.StartAsync", ViewModel.StartAsync); SyncTraySensors(); SyncGadgetSensors(); UpdateGadgetVisibility(); MeasureStartup("MainWindow.StartTimer", _timer.Start); - _startupTrace?.Mark("MainWindow.StartMonitoringAfterActivation.Complete"); + _startupTrace.Mark("MainWindow.StartMonitoringAfterActivation.Complete"); RequestStartupTraceComplete(); - _startupTrace?.Flush(); + _startupTrace.Flush(); } catch (Exception ex) { - _startupTrace?.Mark("MainWindow.StartMonitoringAfterActivation.Exception", $"{ex.GetType().FullName}: {ex.Message}"); - _startupTrace?.Flush(); + _startupTrace.Mark("MainWindow.StartMonitoringAfterActivation.Exception", $"{ex.GetType().FullName}: {ex.Message}"); + _startupTrace.Flush(); RecordRuntimeError("Hardware initialization failed", ex); } })) { - _startupTrace?.Mark("MainWindow.StartMonitoringAfterActivation.EnqueueFailed"); - _startupTrace?.Flush(); + _startupTrace.Mark("MainWindow.StartMonitoringAfterActivation.EnqueueFailed"); + _startupTrace.Flush(); } } @@ -207,8 +208,8 @@ public sealed partial class MainWindow : Window private void RootGrid_Loaded(object sender, RoutedEventArgs e) { RootGrid.Loaded -= RootGrid_Loaded; - _startupTrace?.Mark("MainWindow.RootLoaded", GetRootSizeDetail()); - _startupTrace?.Flush(); + _startupTrace.Mark("MainWindow.RootLoaded", GetRootSizeDetail()); + _startupTrace.Flush(); } private void RootGrid_LayoutUpdated(object? sender, object e) @@ -218,13 +219,13 @@ public sealed partial class MainWindow : Window _firstLayoutRecorded = true; RootGrid.LayoutUpdated -= RootGrid_LayoutUpdated; - _startupTrace?.Mark("MainWindow.FirstLayoutUpdated", GetRootSizeDetail()); - _startupTrace?.Flush(); + _startupTrace.Mark("MainWindow.FirstLayoutUpdated", GetRootSizeDetail()); + _startupTrace.Flush(); } private void MeasureStartup(string phase, Action action) { - if (_startupTrace is not { IsComplete: false }) + if (_startupTrace.IsComplete) { action(); return; @@ -235,7 +236,7 @@ public sealed partial class MainWindow : Window private void MeasureStartup(string phase, Action action, Func getDetail) { - if (_startupTrace is not { IsComplete: false }) + if (_startupTrace.IsComplete) { action(); return; @@ -246,7 +247,7 @@ public sealed partial class MainWindow : Window private T MeasureStartup(string phase, Func action) { - if (_startupTrace is not { IsComplete: false }) + if (_startupTrace.IsComplete) return action(); return _startupTrace.Measure(phase, action); @@ -254,7 +255,7 @@ public sealed partial class MainWindow : Window private async Task MeasureStartupAsync(string phase, Func action) { - if (_startupTrace is not { IsComplete: false }) + if (_startupTrace.IsComplete) { await action(); return; @@ -427,8 +428,8 @@ public sealed partial class MainWindow : Window private void RecordRuntimeError(string message, Exception exception) { - _startupTrace?.Mark("MainWindow.RuntimeError", $"{message}: {exception.GetType().FullName}: {exception.Message}"); - _startupTrace?.Flush(); + _startupTrace.Mark("MainWindow.RuntimeError", $"{message}: {exception.GetType().FullName}: {exception.Message}"); + _startupTrace.Flush(); // Use a dedicated runtime log (not the startup log that App.xaml.cs appends to) and write it only once, so a // recurring per-tick failure neither clobbers the startup diagnostics nor grows the file without bound. @@ -466,7 +467,7 @@ public sealed partial class MainWindow : Window _secondaryWindows.CloseAll(); _placementService.Save(); ViewModel.Dispose(); - _startupTrace?.Dispose(); + _startupTrace.Dispose(); } private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args) @@ -566,7 +567,7 @@ public sealed partial class MainWindow : Window private void CompleteStartupTraceIfReady() { - if (_startupCompleteRecorded || _startupTrace is not { IsComplete: false } || _columnMeasurer.RowCount == 0) + if (_startupCompleteRecorded || _startupTrace.IsComplete || _columnMeasurer.RowCount == 0) return; _startupCompleteRecorded = true; diff --git a/LibreHardwareMonitor.Windows.WinUI/Services/SensorColumnMeasurer.cs b/LibreHardwareMonitor.Windows.WinUI/Services/SensorColumnMeasurer.cs index c742cac..782004f 100644 --- a/LibreHardwareMonitor.Windows.WinUI/Services/SensorColumnMeasurer.cs +++ b/LibreHardwareMonitor.Windows.WinUI/Services/SensorColumnMeasurer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using LibreHardwareMonitor.Windows.WinUI.Services.Tracing; using LibreHardwareMonitor.Windows.WinUI.ViewModels; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; @@ -24,7 +25,7 @@ internal sealed class SensorColumnMeasurer private static readonly TimeSpan SettleDelay = TimeSpan.FromSeconds(5); private readonly MainWindowViewModel _viewModel; - private readonly WinUiStartupTrace? _startupTrace; + private readonly IStartupTracer _startupTrace; private readonly Dictionary<(string Text, bool Bold), double> _cache = new(); private readonly List _rowGrids = []; private readonly DispatcherQueueTimer _settleTimer; @@ -34,7 +35,7 @@ internal sealed class SensorColumnMeasurer private double _stableDeviceColumnWidth = DefaultDeviceColumnWidth; private bool _settled; - public SensorColumnMeasurer(DispatcherQueue dispatcherQueue, MainWindowViewModel viewModel, WinUiStartupTrace? startupTrace) + public SensorColumnMeasurer(DispatcherQueue dispatcherQueue, MainWindowViewModel viewModel, IStartupTracer startupTrace) { _viewModel = viewModel ?? throw new ArgumentNullException(nameof(viewModel)); _startupTrace = startupTrace; @@ -215,6 +216,6 @@ internal sealed class SensorColumnMeasurer return; _viewModel.Settings.SetValue(DeviceColumnWidthSetting, measuredWidth); - _startupTrace?.Mark("MainWindow.RecordDeviceColumnWidth", FormattableString.Invariant($"width={measuredWidth:F0}, rows={_rowGrids.Count}")); + _startupTrace.Mark("MainWindow.RecordDeviceColumnWidth", FormattableString.Invariant($"width={measuredWidth:F0}, rows={_rowGrids.Count}")); } } diff --git a/LibreHardwareMonitor.Windows.WinUI/Services/WinUiStartupTrace.cs b/LibreHardwareMonitor.Windows.WinUI/Services/Tracing/FileStartupTracer.cs similarity index 83% rename from LibreHardwareMonitor.Windows.WinUI/Services/WinUiStartupTrace.cs rename to LibreHardwareMonitor.Windows.WinUI/Services/Tracing/FileStartupTracer.cs index 63144f0..2846f7f 100644 --- a/LibreHardwareMonitor.Windows.WinUI/Services/WinUiStartupTrace.cs +++ b/LibreHardwareMonitor.Windows.WinUI/Services/Tracing/FileStartupTracer.cs @@ -11,13 +11,14 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace LibreHardwareMonitor.Windows.WinUI.Services; +namespace LibreHardwareMonitor.Windows.WinUI.Services.Tracing; -internal sealed class WinUiStartupTrace : IDisposable +/// +/// File-writing startup tracer. Records phase timings and flushes them to a CSV-style log. +/// Created via when startup timing is enabled. +/// +internal sealed class FileStartupTracer : IStartupTracer { - private const string EnabledEnvironmentVariable = "LHM_WINUI_STARTUP_TIMING"; - private const string PathEnvironmentVariable = "LHM_WINUI_STARTUP_TIMING_PATH"; - private readonly List _entries = new(); private readonly string _fileName; private readonly object _lock = new(); @@ -25,9 +26,9 @@ internal sealed class WinUiStartupTrace : IDisposable private bool _completed; private bool _disposed; - private WinUiStartupTrace() + internal FileStartupTracer(string fileName) { - _fileName = GetLogFileName(); + _fileName = fileName; _totalStopwatch = Stopwatch.StartNew(); Mark("WinUIStartupTrace.Begin"); } @@ -41,11 +42,6 @@ internal sealed class WinUiStartupTrace : IDisposable } } - public static WinUiStartupTrace? Create() - { - return IsEnabled() ? new WinUiStartupTrace() : null; - } - public void Complete(string phase, string detail = "") { bool shouldWrite; @@ -231,38 +227,6 @@ internal sealed class WinUiStartupTrace : IDisposable } } - private static string GetLogFileName() - { - string? configuredPath = Environment.GetEnvironmentVariable(PathEnvironmentVariable); - - string fileName = $"LibreHardwareMonitor.WinUIStartupTiming-{DateTime.Now:yyyyMMdd-HHmmss-fff}.log"; - if (string.IsNullOrWhiteSpace(configuredPath)) - return Path.Combine(AppContext.BaseDirectory, fileName); - - if (configuredPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) - || configuredPath.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal) - || Directory.Exists(configuredPath)) - { - return Path.Combine(configuredPath, fileName); - } - - return configuredPath; - } - - private static bool IsEnabled() - { - string environmentValue = Environment.GetEnvironmentVariable(EnabledEnvironmentVariable) ?? ""; - return IsTruthy(environmentValue); - } - - private static bool IsTruthy(string value) - { - return value.Equals("1", StringComparison.OrdinalIgnoreCase) - || value.Equals("true", StringComparison.OrdinalIgnoreCase) - || value.Equals("yes", StringComparison.OrdinalIgnoreCase) - || value.Equals("on", StringComparison.OrdinalIgnoreCase); - } - private void AddEntry(string phase, TimeSpan elapsed, string status, string detail) { AddEntry(phase, _totalStopwatch.Elapsed, elapsed, status, detail); diff --git a/LibreHardwareMonitor.Windows.WinUI/Services/Tracing/IStartupTracer.cs b/LibreHardwareMonitor.Windows.WinUI/Services/Tracing/IStartupTracer.cs new file mode 100644 index 0000000..2938642 --- /dev/null +++ b/LibreHardwareMonitor.Windows.WinUI/Services/Tracing/IStartupTracer.cs @@ -0,0 +1,39 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// Copyright (C) LibreHardwareMonitor and Contributors. + +using System; +using System.Threading.Tasks; + +namespace LibreHardwareMonitor.Windows.WinUI.Services.Tracing; + +/// +/// Optional startup instrumentation consumed by startup operations. A no-op implementation +/// is used when tracing is disabled, so callers never need null checks. +/// +internal interface IStartupTracer : IDisposable +{ + bool IsComplete { get; } + + void Mark(string phase, string detail = ""); + + void Complete(string phase, string detail = ""); + + void Flush(); + + void Measure(string phase, Action action); + + void Measure(string phase, Action action, Func? getDetail); + + T Measure(string phase, Func action); + + T Measure(string phase, Func action, Func? getDetail); + + Task MeasureAsync(string phase, Func action); + + Task MeasureAsync(string phase, Func action, Func? getDetail); + + Task MeasureAsync(string phase, Func> action); + + Task MeasureAsync(string phase, Func> action, Func? getDetail); +} diff --git a/LibreHardwareMonitor.Windows.WinUI/Services/Tracing/NoOpStartupTracer.cs b/LibreHardwareMonitor.Windows.WinUI/Services/Tracing/NoOpStartupTracer.cs new file mode 100644 index 0000000..83b90f3 --- /dev/null +++ b/LibreHardwareMonitor.Windows.WinUI/Services/Tracing/NoOpStartupTracer.cs @@ -0,0 +1,79 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// Copyright (C) LibreHardwareMonitor and Contributors. + +using System; +using System.Threading.Tasks; + +namespace LibreHardwareMonitor.Windows.WinUI.Services.Tracing; + +/// +/// Zero-overhead tracer used when startup tracing is disabled. Measure overloads simply +/// invoke the supplied delegate; everything else is a no-op. +/// +internal sealed class NoOpStartupTracer : IStartupTracer +{ + public static readonly NoOpStartupTracer Instance = new(); + + private NoOpStartupTracer() + { + } + + public bool IsComplete => false; + + public void Mark(string phase, string detail = "") + { + } + + public void Complete(string phase, string detail = "") + { + } + + public void Flush() + { + } + + public void Measure(string phase, Action action) + { + action(); + } + + public void Measure(string phase, Action action, Func? getDetail) + { + action(); + } + + public T Measure(string phase, Func action) + { + return action(); + } + + public T Measure(string phase, Func action, Func? getDetail) + { + return action(); + } + + public Task MeasureAsync(string phase, Func action) + { + return action(); + } + + public Task MeasureAsync(string phase, Func action, Func? getDetail) + { + return action(); + } + + public Task MeasureAsync(string phase, Func> action) + { + return action(); + } + + public Task MeasureAsync(string phase, Func> action, Func? getDetail) + { + return action(); + } + + public void Dispose() + { + } +} diff --git a/LibreHardwareMonitor.Windows.WinUI/Services/Tracing/StartupTracer.cs b/LibreHardwareMonitor.Windows.WinUI/Services/Tracing/StartupTracer.cs new file mode 100644 index 0000000..01435a1 --- /dev/null +++ b/LibreHardwareMonitor.Windows.WinUI/Services/Tracing/StartupTracer.cs @@ -0,0 +1,56 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// Copyright (C) LibreHardwareMonitor and Contributors. + +using System; +using System.IO; + +namespace LibreHardwareMonitor.Windows.WinUI.Services.Tracing; + +/// +/// Factory that selects the appropriate based on environment +/// configuration. Returns a when timing is enabled, otherwise +/// the shared . +/// +internal static class StartupTracer +{ + private const string EnabledEnvironmentVariable = "LHM_WINUI_STARTUP_TIMING"; + private const string PathEnvironmentVariable = "LHM_WINUI_STARTUP_TIMING_PATH"; + + public static IStartupTracer Create() + { + return IsEnabled() ? new FileStartupTracer(GetLogFileName()) : NoOpStartupTracer.Instance; + } + + private static string GetLogFileName() + { + string? configuredPath = Environment.GetEnvironmentVariable(PathEnvironmentVariable); + + string fileName = $"LibreHardwareMonitor.WinUIStartupTiming-{DateTime.Now:yyyyMMdd-HHmmss-fff}.log"; + if (string.IsNullOrWhiteSpace(configuredPath)) + return Path.Combine(AppContext.BaseDirectory, fileName); + + if (configuredPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) + || configuredPath.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal) + || Directory.Exists(configuredPath)) + { + return Path.Combine(configuredPath, fileName); + } + + return configuredPath; + } + + private static bool IsEnabled() + { + string environmentValue = Environment.GetEnvironmentVariable(EnabledEnvironmentVariable) ?? ""; + return IsTruthy(environmentValue); + } + + private static bool IsTruthy(string value) + { + return value.Equals("1", StringComparison.OrdinalIgnoreCase) + || value.Equals("true", StringComparison.OrdinalIgnoreCase) + || value.Equals("yes", StringComparison.OrdinalIgnoreCase) + || value.Equals("on", StringComparison.OrdinalIgnoreCase); + } +} diff --git a/LibreHardwareMonitor.Windows.WinUI/ViewModels/MainWindowViewModel.cs b/LibreHardwareMonitor.Windows.WinUI/ViewModels/MainWindowViewModel.cs index b0f91a7..1ac672a 100644 --- a/LibreHardwareMonitor.Windows.WinUI/ViewModels/MainWindowViewModel.cs +++ b/LibreHardwareMonitor.Windows.WinUI/ViewModels/MainWindowViewModel.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using LibreHardwareMonitor.Hardware; using LibreHardwareMonitor.Hardware.Storage; using LibreHardwareMonitor.Windows.WinUI.Services; +using LibreHardwareMonitor.Windows.WinUI.Services.Tracing; using LibreHardwareMonitor.Windows.WinUI.Utilities; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; @@ -88,7 +89,7 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable private readonly SensorSelectionService _sensorSelection; private readonly RemoteWebServer _remoteWebServer; private readonly StartupService _startupService = new(); - private readonly WinUiStartupTrace? _startupTrace; + private readonly IStartupTracer _startupTrace; private AppThemeMode _themeMode; private int _loggingIntervalIndex; private PlotLocation _plotLocation; @@ -115,15 +116,15 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable private TemperatureUnit _temperatureUnit; private int _updateIntervalIndex; - public MainWindowViewModel(AppSettings settings) : this(settings, null) + public MainWindowViewModel(AppSettings settings) : this(settings, NoOpStartupTracer.Instance) { } - internal MainWindowViewModel(AppSettings settings, WinUiStartupTrace? startupTrace) + internal MainWindowViewModel(AppSettings settings, IStartupTracer startupTrace) { Settings = settings; _startupTrace = startupTrace; - _startupTrace?.Mark("MainWindowViewModel.Constructor.Begin"); + _startupTrace.Mark("MainWindowViewModel.Constructor.Begin"); _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); _hardwareMonitor = new HardwareMonitorService(settings); _logger = new Logger(_hardwareMonitor.Computer); @@ -162,7 +163,7 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable StorageDevice.ThrottleInterval = _throttleAtaUpdate ? TimeSpan.FromSeconds(30) : TimeSpan.Zero; _logger.LoggingInterval = LoggingIntervals[_loggingIntervalIndex]; _logger.FileRotationMethod = (LoggerFileRotation)Math.Clamp(settings.GetValue("logger.fileRotation", 0), 0, 1); - _startupTrace?.Mark("MainWindowViewModel.Constructor.Complete"); + _startupTrace.Mark("MainWindowViewModel.Constructor.Complete"); } public event EventHandler? PlotInvalidated; @@ -864,7 +865,7 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable try { IsHardwareLoading = true; - _startupTrace?.Mark("MainWindowViewModel.StartAsync.Begin"); + _startupTrace.Mark("MainWindowViewModel.StartAsync.Begin"); StatusText = "Initializing hardware sensors..."; await MeasureStartupAsync("MainWindowViewModel.HardwareMonitor.OpenAsync", () => _hardwareMonitor.OpenAsync(raiseTreeRebuilt: false)); MeasureStartup("MainWindowViewModel.UpdateRoot", UpdateRoot, GetRootDetail); @@ -873,12 +874,12 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable MeasureStartup("MainWindowViewModel.StartWebServerFromSettings", StartWebServerFromSettings); MeasureStartup("MainWindowViewModel.UpdateStatus", UpdateStatus, GetRootDetail); _isStarted = true; - _startupTrace?.Mark("MainWindowViewModel.StartAsync.Complete", GetRootDetail()); + _startupTrace.Mark("MainWindowViewModel.StartAsync.Complete", GetRootDetail()); } finally { IsHardwareLoading = false; - _startupTrace?.Mark("MainWindowViewModel.HardwareLoadingComplete", GetRootDetail()); + _startupTrace.Mark("MainWindowViewModel.HardwareLoadingComplete", GetRootDetail()); _isStarting = false; } } @@ -951,7 +952,7 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable private void MeasureStartup(string phase, Action action) { - if (_startupTrace is not { IsComplete: false }) + if (_startupTrace.IsComplete) { action(); return; @@ -962,7 +963,7 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable private void MeasureStartup(string phase, Action action, Func getDetail) { - if (_startupTrace is not { IsComplete: false }) + if (_startupTrace.IsComplete) { action(); return; @@ -973,7 +974,7 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable private T MeasureStartup(string phase, Func action) { - if (_startupTrace is not { IsComplete: false }) + if (_startupTrace.IsComplete) return action(); return _startupTrace.Measure(phase, action); @@ -981,7 +982,7 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable private async Task MeasureStartupAsync(string phase, Func action) { - if (_startupTrace is not { IsComplete: false }) + if (_startupTrace.IsComplete) { await action(); return;