Skip to content

New API Function from Stopwatch & Improve Program Plugin #3429

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Flow.Launcher.Core/Plugin/PluginManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ namespace Flow.Launcher.Core.Plugin
/// </summary>
public static class PluginManager
{
private static readonly string ClassName = nameof(PluginManager);

private static IEnumerable<PluginPair> _contextMenuPlugins;

public static List<PluginPair> AllPlugins { get; private set; }
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -266,7 +268,7 @@ public static async Task<List<Result>> 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();
Expand Down
10 changes: 7 additions & 3 deletions Flow.Launcher.Core/Plugin/PluginsLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IPublicAPI>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an issue for now but we should think about a better way to safeguard this, maybe a design pattern we can use or something.


public static List<PluginPair> Plugins(List<PluginMetadata> metadatas, PluginsSettings settings)
{
var dotnetPlugins = DotNetPlugins(metadatas);
Expand Down Expand Up @@ -59,8 +64,7 @@ private static IEnumerable<PluginPair> DotNetPlugins(List<PluginMetadata> 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;
Expand Down
20 changes: 13 additions & 7 deletions Flow.Launcher.Infrastructure/Image/ImageLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IPublicAPI>();

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<List<(string, bool)>> _storage;
Expand Down Expand Up @@ -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()}");
});
}

Expand All @@ -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
{
Expand Down Expand Up @@ -166,8 +172,8 @@ private static async ValueTask<ImageResult> 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;
Expand Down
34 changes: 0 additions & 34 deletions Flow.Launcher.Infrastructure/Stopwatch.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger;

namespace Flow.Launcher.Infrastructure
{
public static class Stopwatch
{
private static readonly Dictionary<string, long> Count = new Dictionary<string, long>();
private static readonly object Locker = new object();
/// <summary>
/// This stopwatch will appear only in Debug mode
/// </summary>
Expand Down Expand Up @@ -62,36 +59,5 @@ public static async Task<long> NormalAsync(string message, Func<Task> 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);
}
}
}
}
26 changes: 26 additions & 0 deletions Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -473,5 +473,31 @@ public interface IPublicAPI
/// </param>
/// <returns></returns>
public Task UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false);

/// <summary>
/// Log debug message of the time taken to execute a method
/// Message will only be logged in Debug mode
/// </summary>
/// <returns>The time taken to execute the method in milliseconds</returns>
public long StopwatchLogDebug(string className, string message, Action action, [CallerMemberName] string methodName = "");

/// <summary>
/// Log debug message of the time taken to execute a method asynchronously
/// Message will only be logged in Debug mode
/// </summary>
/// <returns>The time taken to execute the method in milliseconds</returns>
public Task<long> StopwatchLogDebugAsync(string className, string message, Func<Task> action, [CallerMemberName] string methodName = "");

/// <summary>
/// Log info message of the time taken to execute a method
/// </summary>
/// <returns>The time taken to execute the method in milliseconds</returns>
public long StopwatchLogInfo(string className, string message, Action action, [CallerMemberName] string methodName = "");

/// <summary>
/// Log info message of the time taken to execute a method asynchronously
/// </summary>
/// <returns>The time taken to execute the method in milliseconds</returns>
public Task<long> StopwatchLogInfoAsync(string className, string message, Func<Task> action, [CallerMemberName] string methodName = "");
}
}
7 changes: 4 additions & 3 deletions Flow.Launcher/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 ----------------------------------------------------");

Expand Down
13 changes: 13 additions & 0 deletions Flow.Launcher/PublicAPIInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
using Flow.Launcher.ViewModel;
using JetBrains.Annotations;
using Squirrel;
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;

namespace Flow.Launcher
{
Expand Down Expand Up @@ -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<long> StopwatchLogDebugAsync(string className, string message, Func<Task> 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<long> StopwatchLogInfoAsync(string className, string message, Func<Task> action, [CallerMemberName] string methodName = "") =>
Stopwatch.NormalAsync($"|{className}.{methodName}|{message}", action);

#endregion

#region Private Methods
Expand Down
20 changes: 10 additions & 10 deletions Plugins/Flow.Launcher.Plugin.Program/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@
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;
using Flow.Launcher.Plugin.Program.Views.Models;
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
{
Expand All @@ -32,6 +30,8 @@

internal static PluginInitContext Context { get; private set; }

private static readonly string ClassName = nameof(Main);

private static readonly List<Result> emptyResults = new();

private static readonly MemoryCacheOptions cacheOptions = new() { SizeLimit = 1560 };
Expand All @@ -51,7 +51,7 @@
"卸載",//zh-tw
"видалити",//uk-UA
"удалить",//ru
"désinstaller",//fr

Check notice on line 54 in Plugins/Flow.Launcher.Plugin.Program/Main.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`Line` matches candidate pattern `[a-zA-Z]*[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*` (candidate-pattern)
"アンインストール",//ja
"deïnstalleren",//nl
"odinstaluj",//pl
Expand Down Expand Up @@ -109,7 +109,7 @@
}
catch (OperationCanceledException)
{
Log.Debug("|Flow.Launcher.Plugin.Program.Main|Query operation cancelled");
Context.API.LogDebug(ClassName, "Query operation cancelled");
return emptyResults;
}
finally
Expand Down Expand Up @@ -188,7 +188,7 @@

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);
Expand Down Expand Up @@ -253,8 +253,8 @@
_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;

Expand Down Expand Up @@ -295,7 +295,7 @@
}
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
{
Expand All @@ -320,7 +320,7 @@
}
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
{
Expand All @@ -332,12 +332,12 @@
{
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);
Expand Down
Loading