add CmdPal_ExtensionInvoked telemetry event

This commit is contained in:
chatasweetie
2025-11-25 12:42:11 -08:00
parent 2c9a9e9fca
commit 8bb900eee2
5 changed files with 94 additions and 3 deletions

View File

@@ -0,0 +1,7 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record ExtensionInvokedMessage(string ExtensionId, string CommandType, bool Success, ulong ExecutionTimeMs);

View File

@@ -269,8 +269,17 @@ public partial class ShellViewModel : ObservableObject,
var isMainPage = command == _rootPage;
_isNested = !isMainPage;
// Track extension page navigation
if (host is not null)
{
string extensionId = host.GetExtensionDisplayName() ?? "builtin";
string commandType = command?.Name ?? command?.Id ?? "unknown";
WeakReferenceMessenger.Default.Send<ExtensionInvokedMessage>(
new(extensionId, commandType, true, 0));
}
// Construct our ViewModel of the appropriate type and pass it the UI Thread context.
var pageViewModel = _pageViewModelFactory.TryCreatePageViewModel(page, _isNested, host);
var pageViewModel = _pageViewModelFactory.TryCreatePageViewModel(page, _isNested, host!);
if (pageViewModel is null)
{
CoreLogger.LogError($"Failed to create ViewModel for page {page.GetType().Name}");
@@ -338,6 +347,12 @@ public partial class ShellViewModel : ObservableObject,
private void SafeHandleInvokeCommandSynchronous(PerformCommandMessage message, IInvokableCommand invokable, AppExtensionHost? host)
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
var command = message.Command.Unsafe;
string extensionId = host?.GetExtensionDisplayName() ?? "builtin";
string commandType = command?.Name ?? command?.Id ?? "unknown";
bool success = false;
try
{
// Call out to extension process.
@@ -348,16 +363,24 @@ public partial class ShellViewModel : ObservableObject,
// But if it did succeed, we need to handle the result.
UnsafeHandleCommandResult(result);
success = true;
_handleInvokeTask = null;
}
catch (Exception ex)
{
success = false;
_handleInvokeTask = null;
// TODO: It would be better to do this as a page exception, rather
// than a silent log message.
host?.Log(ex.Message);
}
finally
{
stopwatch.Stop();
WeakReferenceMessenger.Default.Send<ExtensionInvokedMessage>(
new(extensionId, commandType, success, (ulong)stopwatch.ElapsedMilliseconds));
}
}
private void UnsafeHandleCommandResult(ICommandResult? result)

View File

@@ -34,6 +34,6 @@ public sealed partial class CommandPaletteHost : AppExtensionHost, IExtensionHos
public override string? GetExtensionDisplayName()
{
return Extension?.ExtensionDisplayName;
return Extension?.ExtensionDisplayName ?? _builtInProvider?.DisplayName ?? _builtInProvider?.Id;
}
}

View File

@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.CmdPal.UI.Events;
/// <summary>
/// Tracks extension usage with extension name and invocation details.
/// Purpose: Identify popular vs. unused plugins and track extension performance.
/// </summary>
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class CmdPalExtensionInvoked : EventBase, IEvent
{
/// <summary>
/// Gets or sets the unique identifier of the extension provider.
/// </summary>
public string ExtensionId { get; set; }
/// <summary>
/// Gets or sets the display name of the command being invoked.
/// </summary>
public string CommandType { get; set; }
/// <summary>
/// Gets or sets whether the command executed successfully.
/// </summary>
public bool Success { get; set; }
/// <summary>
/// Gets or sets the execution time in milliseconds.
/// </summary>
public ulong ExecutionTimeMs { get; set; }
public CmdPalExtensionInvoked(string extensionId, string commandType, bool success, ulong executionTimeMs)
{
EventName = "CmdPal_ExtensionInvoked";
ExtensionId = extensionId;
CommandType = commandType;
Success = success;
ExecutionTimeMs = executionTimeMs;
}
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}

View File

@@ -22,12 +22,14 @@ namespace Microsoft.CmdPal.UI;
internal sealed class TelemetryForwarder :
ITelemetryService,
IRecipient<BeginInvokeMessage>,
IRecipient<CmdPalInvokeResultMessage>
IRecipient<CmdPalInvokeResultMessage>,
IRecipient<ExtensionInvokedMessage>
{
public TelemetryForwarder()
{
WeakReferenceMessenger.Default.Register<BeginInvokeMessage>(this);
WeakReferenceMessenger.Default.Register<CmdPalInvokeResultMessage>(this);
WeakReferenceMessenger.Default.Register<ExtensionInvokedMessage>(this);
}
public void Receive(CmdPalInvokeResultMessage message)
@@ -40,6 +42,15 @@ internal sealed class TelemetryForwarder :
PowerToysTelemetry.Log.WriteEvent(new BeginInvoke());
}
public void Receive(ExtensionInvokedMessage message)
{
PowerToysTelemetry.Log.WriteEvent(new CmdPalExtensionInvoked(
message.ExtensionId,
message.CommandType,
message.Success,
message.ExecutionTimeMs));
}
public void LogRunQuery(string query, int resultCount, ulong durationMs)
{
PowerToysTelemetry.Log.WriteEvent(new CmdPalRunQuery(query, resultCount, durationMs));