diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 0ee268f5c56..4c85fb061e3 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -23,6 +23,8 @@ namespace Flow.Launcher.Core.Plugin /// public static class PluginManager { + private static readonly string ClassName = nameof(PluginManager); + private static IEnumerable _contextMenuPlugins; public static List AllPlugins { get; private set; } @@ -194,7 +196,7 @@ public static async Task InitializePluginsAsync() { try { - var milliseconds = await Stopwatch.DebugAsync($"|PluginManager.InitializePlugins|Init method time cost for <{pair.Metadata.Name}>", + var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Init method time cost for <{pair.Metadata.Name}>", () => pair.Plugin.InitAsync(new PluginInitContext(pair.Metadata, API))); pair.Metadata.InitTime += milliseconds; @@ -266,7 +268,7 @@ public static async Task> QueryForPluginAsync(PluginPair pair, Quer try { - var milliseconds = await Stopwatch.DebugAsync($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}", + var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", async () => results = await pair.Plugin.QueryAsync(query, token).ConfigureAwait(false)); token.ThrowIfCancellationRequested(); diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 495a4c1ab5f..1010d9f083b 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -11,12 +11,17 @@ #pragma warning restore IDE0005 using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; -using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; namespace Flow.Launcher.Core.Plugin { public static class PluginsLoader { + private static readonly string ClassName = nameof(PluginsLoader); + + // We should not initialize API in static constructor because it will create another API instance + private static IPublicAPI api = null; + private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); + public static List Plugins(List metadatas, PluginsSettings settings) { var dotnetPlugins = DotNetPlugins(metadatas); @@ -59,8 +64,7 @@ private static IEnumerable DotNetPlugins(List source foreach (var metadata in metadatas) { - var milliseconds = Stopwatch.Debug( - $"|PluginsLoader.DotNetPlugins|Constructor init cost for {metadata.Name}", () => + var milliseconds = API.StopwatchLogDebug(ClassName, $"Constructor init cost for {metadata.Name}", () => { Assembly assembly = null; IAsyncPlugin plugin = null; diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 41a33104b66..1483c58654e 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -7,13 +7,20 @@ using System.Threading.Tasks; using System.Windows.Media; using System.Windows.Media.Imaging; -using Flow.Launcher.Infrastructure.Logger; +using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Infrastructure.Storage; +using Flow.Launcher.Plugin; namespace Flow.Launcher.Infrastructure.Image { public static class ImageLoader { + // We should not initialize API in static constructor because it will create another API instance + private static IPublicAPI api = null; + private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); + + private static readonly string ClassName = nameof(ImageLoader); + private static readonly ImageCache ImageCache = new(); private static SemaphoreSlim storageLock { get; } = new SemaphoreSlim(1, 1); private static BinaryStorage> _storage; @@ -47,15 +54,14 @@ public static async Task InitializeAsync() _ = Task.Run(async () => { - await Stopwatch.NormalAsync("|ImageLoader.Initialize|Preload images cost", async () => + await API.StopwatchLogInfoAsync(ClassName, "Preload images cost", async () => { foreach (var (path, isFullImage) in usage) { await LoadAsync(path, isFullImage); } }); - Log.Info( - $"|ImageLoader.Initialize|Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}"); + API.LogInfo(ClassName, $"Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}"); }); } @@ -71,7 +77,7 @@ await _storage.SaveAsync(ImageCache.EnumerateEntries() } catch (System.Exception e) { - Log.Exception($"|ImageLoader.SaveAsync|Failed to save image cache to file", e); + API.LogException(ClassName, "Failed to save image cache to file", e); } finally { @@ -166,8 +172,8 @@ private static async ValueTask LoadInternalAsync(string path, bool } catch (System.Exception e2) { - Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path} on first try", e); - Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path} on second try", e2); + API.LogException(ClassName, $"|ImageLoader.Load|Failed to get thumbnail for {path} on first try", e); + API.LogException(ClassName, $"|ImageLoader.Load|Failed to get thumbnail for {path} on second try", e2); ImageSource image = ImageCache[Constant.MissingImgIcon, false]; ImageCache[path, false] = image; diff --git a/Flow.Launcher.Infrastructure/Stopwatch.cs b/Flow.Launcher.Infrastructure/Stopwatch.cs index dd6edaff93b..784d323fe52 100644 --- a/Flow.Launcher.Infrastructure/Stopwatch.cs +++ b/Flow.Launcher.Infrastructure/Stopwatch.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using Flow.Launcher.Infrastructure.Logger; @@ -7,8 +6,6 @@ namespace Flow.Launcher.Infrastructure { public static class Stopwatch { - private static readonly Dictionary Count = new Dictionary(); - private static readonly object Locker = new object(); /// /// This stopwatch will appear only in Debug mode /// @@ -62,36 +59,5 @@ public static async Task NormalAsync(string message, Func action) Log.Info(info); return milliseconds; } - - - - public static void StartCount(string name, Action action) - { - var stopWatch = new System.Diagnostics.Stopwatch(); - stopWatch.Start(); - action(); - stopWatch.Stop(); - var milliseconds = stopWatch.ElapsedMilliseconds; - lock (Locker) - { - if (Count.ContainsKey(name)) - { - Count[name] += milliseconds; - } - else - { - Count[name] = 0; - } - } - } - - public static void EndCount() - { - foreach (var key in Count.Keys) - { - string info = $"{key} already cost {Count[key]}ms"; - Log.Debug(info); - } - } } } diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 273676bfbd6..55f09dc3b4d 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -473,5 +473,31 @@ public interface IPublicAPI /// /// public Task UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false); + + /// + /// Log debug message of the time taken to execute a method + /// Message will only be logged in Debug mode + /// + /// The time taken to execute the method in milliseconds + public long StopwatchLogDebug(string className, string message, Action action, [CallerMemberName] string methodName = ""); + + /// + /// Log debug message of the time taken to execute a method asynchronously + /// Message will only be logged in Debug mode + /// + /// The time taken to execute the method in milliseconds + public Task StopwatchLogDebugAsync(string className, string message, Func action, [CallerMemberName] string methodName = ""); + + /// + /// Log info message of the time taken to execute a method + /// + /// The time taken to execute the method in milliseconds + public long StopwatchLogInfo(string className, string message, Action action, [CallerMemberName] string methodName = ""); + + /// + /// Log info message of the time taken to execute a method asynchronously + /// + /// The time taken to execute the method in milliseconds + public Task StopwatchLogInfoAsync(string className, string message, Func action, [CallerMemberName] string methodName = ""); } } diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 81938612c34..90fefe0a642 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -21,7 +21,6 @@ using Flow.Launcher.ViewModel; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; namespace Flow.Launcher { @@ -35,6 +34,8 @@ public partial class App : IDisposable, ISingleInstanceApp #region Private Fields + private static readonly string ClassName = nameof(App); + private static bool _disposed; private MainWindow _mainWindow; private readonly MainViewModel _mainVM; @@ -136,7 +137,7 @@ public static void Main() private async void OnStartup(object sender, StartupEventArgs e) { - await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () => + await API.StopwatchLogInfoAsync(ClassName, "Startup cost", async () => { // Because new message box api uses MessageBoxEx window, // if it is created and closed before main window is created, it will cause the application to exit. @@ -313,7 +314,7 @@ protected virtual void Dispose(bool disposing) _disposed = true; } - Stopwatch.Normal("|App.Dispose|Dispose cost", () => + API.StopwatchLogInfo(ClassName, "Dispose cost", () => { Log.Info("|App.Dispose|Begin Flow Launcher dispose ----------------------------------------------------"); diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index b67ef6ab6e5..95ef6c9f3bd 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -31,6 +31,7 @@ using Flow.Launcher.ViewModel; using JetBrains.Annotations; using Squirrel; +using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; namespace Flow.Launcher { @@ -431,6 +432,18 @@ public void InstallPlugin(UserPlugin plugin, string zipFilePath) => public Task UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false) => PluginManager.UninstallPluginAsync(pluginMetadata, removePluginSettings); + public long StopwatchLogDebug(string className, string message, Action action, [CallerMemberName] string methodName = "") => + Stopwatch.Debug($"|{className}.{methodName}|{message}", action); + + public Task StopwatchLogDebugAsync(string className, string message, Func action, [CallerMemberName] string methodName = "") => + Stopwatch.DebugAsync($"|{className}.{methodName}|{message}", action); + + public long StopwatchLogInfo(string className, string message, Action action, [CallerMemberName] string methodName = "") => + Stopwatch.Normal($"|{className}.{methodName}|{message}", action); + + public Task StopwatchLogInfoAsync(string className, string message, Func action, [CallerMemberName] string methodName = "") => + Stopwatch.NormalAsync($"|{className}.{methodName}|{message}", action); + #endregion #region Private Methods diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index a50868b69dd..f03040d68b1 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using System.Windows.Controls; -using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.Program.Programs; using Flow.Launcher.Plugin.Program.Views; @@ -14,7 +13,6 @@ using Flow.Launcher.Plugin.SharedCommands; using Microsoft.Extensions.Caching.Memory; using Path = System.IO.Path; -using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; namespace Flow.Launcher.Plugin.Program { @@ -32,6 +30,8 @@ public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, I internal static PluginInitContext Context { get; private set; } + private static readonly string ClassName = nameof(Main); + private static readonly List emptyResults = new(); private static readonly MemoryCacheOptions cacheOptions = new() { SizeLimit = 1560 }; @@ -109,7 +109,7 @@ public async Task> QueryAsync(Query query, CancellationToken token) } catch (OperationCanceledException) { - Log.Debug("|Flow.Launcher.Plugin.Program.Main|Query operation cancelled"); + Context.API.LogDebug(ClassName, "Query operation cancelled"); return emptyResults; } finally @@ -188,7 +188,7 @@ public async Task InitAsync(PluginInitContext context) var _win32sCount = 0; var _uwpsCount = 0; - await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", async () => + await Context.API.StopwatchLogInfoAsync(ClassName, "Preload programs cost", async () => { var pluginCacheDirectory = Context.CurrentPluginMetadata.PluginCacheDirectoryPath; FilesFolders.ValidateDirectory(pluginCacheDirectory); @@ -253,8 +253,8 @@ static void MoveFile(string sourcePath, string destinationPath) _uwpsCount = _uwps.Count; _uwpsLock.Release(); }); - Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32sCount}>"); - Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwpsCount}>"); + Context.API.LogInfo(ClassName, $"Number of preload win32 programs <{_win32sCount}>"); + Context.API.LogInfo(ClassName, $"Number of preload uwps <{_uwpsCount}>"); var cacheEmpty = _win32sCount == 0 || _uwpsCount == 0; @@ -295,7 +295,7 @@ public static async Task IndexWin32ProgramsAsync() } catch (Exception e) { - Log.Exception("|Flow.Launcher.Plugin.Program.Main|Failed to index Win32 programs", e); + Context.API.LogException(ClassName, "Failed to index Win32 programs", e); } finally { @@ -320,7 +320,7 @@ public static async Task IndexUwpProgramsAsync() } catch (Exception e) { - Log.Exception("|Flow.Launcher.Plugin.Program.Main|Failed to index Uwp programs", e); + Context.API.LogException(ClassName, "Failed to index Uwp programs", e); } finally { @@ -332,12 +332,12 @@ public static async Task IndexProgramsAsync() { var win32Task = Task.Run(async () => { - await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32ProgramsAsync); + await Context.API.StopwatchLogInfoAsync(ClassName, "Win32Program index cost", IndexWin32ProgramsAsync); }); var uwpTask = Task.Run(async () => { - await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|UWPProgram index cost", IndexUwpProgramsAsync); + await Context.API.StopwatchLogInfoAsync(ClassName, "UWPProgram index cost", IndexUwpProgramsAsync); }); await Task.WhenAll(win32Task, uwpTask).ConfigureAwait(false);