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>
This commit is contained in:
@@ -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<T>(string phase, Func<T> action)
|
||||
{
|
||||
if (_startupTrace == null)
|
||||
return action();
|
||||
|
||||
return _startupTrace.Measure(phase, action);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -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<string> 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<T>(string phase, Func<T> 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<Task> 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;
|
||||
|
||||
@@ -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<Grid> _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}"));
|
||||
}
|
||||
}
|
||||
|
||||
+8
-44
@@ -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
|
||||
/// <summary>
|
||||
/// File-writing startup tracer. Records phase timings and flushes them to a CSV-style log.
|
||||
/// Created via <see cref="StartupTracer.Create"/> when startup timing is enabled.
|
||||
/// </summary>
|
||||
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<Entry> _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);
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Optional startup instrumentation consumed by startup operations. A no-op implementation
|
||||
/// is used when tracing is disabled, so callers never need null checks.
|
||||
/// </summary>
|
||||
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<string>? getDetail);
|
||||
|
||||
T Measure<T>(string phase, Func<T> action);
|
||||
|
||||
T Measure<T>(string phase, Func<T> action, Func<T, string>? getDetail);
|
||||
|
||||
Task MeasureAsync(string phase, Func<Task> action);
|
||||
|
||||
Task MeasureAsync(string phase, Func<Task> action, Func<string>? getDetail);
|
||||
|
||||
Task<T> MeasureAsync<T>(string phase, Func<Task<T>> action);
|
||||
|
||||
Task<T> MeasureAsync<T>(string phase, Func<Task<T>> action, Func<T, string>? getDetail);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Zero-overhead tracer used when startup tracing is disabled. Measure overloads simply
|
||||
/// invoke the supplied delegate; everything else is a no-op.
|
||||
/// </summary>
|
||||
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<string>? getDetail)
|
||||
{
|
||||
action();
|
||||
}
|
||||
|
||||
public T Measure<T>(string phase, Func<T> action)
|
||||
{
|
||||
return action();
|
||||
}
|
||||
|
||||
public T Measure<T>(string phase, Func<T> action, Func<T, string>? getDetail)
|
||||
{
|
||||
return action();
|
||||
}
|
||||
|
||||
public Task MeasureAsync(string phase, Func<Task> action)
|
||||
{
|
||||
return action();
|
||||
}
|
||||
|
||||
public Task MeasureAsync(string phase, Func<Task> action, Func<string>? getDetail)
|
||||
{
|
||||
return action();
|
||||
}
|
||||
|
||||
public Task<T> MeasureAsync<T>(string phase, Func<Task<T>> action)
|
||||
{
|
||||
return action();
|
||||
}
|
||||
|
||||
public Task<T> MeasureAsync<T>(string phase, Func<Task<T>> action, Func<T, string>? getDetail)
|
||||
{
|
||||
return action();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Factory that selects the appropriate <see cref="IStartupTracer"/> based on environment
|
||||
/// configuration. Returns a <see cref="FileStartupTracer"/> when timing is enabled, otherwise
|
||||
/// the shared <see cref="NoOpStartupTracer"/>.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<string> 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<T>(string phase, Func<T> 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<Task> action)
|
||||
{
|
||||
if (_startupTrace is not { IsComplete: false })
|
||||
if (_startupTrace.IsComplete)
|
||||
{
|
||||
await action();
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user