mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
refactor Command Palette telemetry to use telemetry-specific messages. Core layer now sends TelemetryBeginInvokeMessage, TelemetryInvokeResultMessage, and TelemetryExtensionInvokedMessage, which TelemetryForwarder receives and forwards to PowerToys telemetry. Update DATA_AND_PRIVACY for the CmdPal_ExtensionInvoked and CmdPal_SessionDuration
This commit is contained in:
@@ -257,6 +257,23 @@ _If you want to find diagnostic data events in the source code, these two links
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
### Command Palette
|
||||||
|
|
||||||
|
<table style="width:100%">
|
||||||
|
<tr>
|
||||||
|
<th>Event Name</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Microsoft.PowerToys.CmdPal_ExtensionInvoked</td>
|
||||||
|
<td>Tracks extension usage including extension ID, command details, success status, and execution time.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Microsoft.PowerToys.CmdPal_SessionDuration</td>
|
||||||
|
<td>Logs session metrics from launch to dismissal including duration, commands executed, pages visited, search queries, navigation depth, and errors.</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
### Crop And Lock
|
### Crop And Lock
|
||||||
<table style="width:100%">
|
<table style="width:100%">
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Telemetry message sent when command invocation begins.
|
||||||
|
/// </summary>
|
||||||
|
public record TelemetryBeginInvokeMessage;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Telemetry message sent when an extension command or page is invoked.
|
||||||
|
/// Captures extension usage metrics for telemetry tracking.
|
||||||
|
/// </summary>
|
||||||
|
public record TelemetryExtensionInvokedMessage(string ExtensionId, string CommandId, string CommandName, bool Success, ulong ExecutionTimeMs);
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Telemetry message sent when command invocation completes with a result.
|
||||||
|
/// </summary>
|
||||||
|
public record TelemetryInvokeResultMessage(Microsoft.CommandPalette.Extensions.CommandResultKind Kind);
|
||||||
@@ -275,7 +275,7 @@ public partial class ShellViewModel : ObservableObject,
|
|||||||
string extensionId = host.GetExtensionDisplayName() ?? "builtin";
|
string extensionId = host.GetExtensionDisplayName() ?? "builtin";
|
||||||
string commandId = command?.Id ?? "unknown";
|
string commandId = command?.Id ?? "unknown";
|
||||||
string commandName = command?.Name ?? "unknown";
|
string commandName = command?.Name ?? "unknown";
|
||||||
WeakReferenceMessenger.Default.Send<ExtensionInvokedMessage>(
|
WeakReferenceMessenger.Default.Send<TelemetryExtensionInvokedMessage>(
|
||||||
new(extensionId, commandId, commandName, true, 0));
|
new(extensionId, commandId, commandName, true, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,7 +315,7 @@ public partial class ShellViewModel : ObservableObject,
|
|||||||
{
|
{
|
||||||
CoreLogger.LogDebug($"Invoking command");
|
CoreLogger.LogDebug($"Invoking command");
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send<BeginInvokeMessage>();
|
WeakReferenceMessenger.Default.Send<TelemetryBeginInvokeMessage>();
|
||||||
StartInvoke(message, invokable, host);
|
StartInvoke(message, invokable, host);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -385,7 +385,7 @@ public partial class ShellViewModel : ObservableObject,
|
|||||||
{
|
{
|
||||||
// Telemetry: Send extension invocation metrics (always sent, even on failure)
|
// Telemetry: Send extension invocation metrics (always sent, even on failure)
|
||||||
stopwatch.Stop();
|
stopwatch.Stop();
|
||||||
WeakReferenceMessenger.Default.Send<ExtensionInvokedMessage>(
|
WeakReferenceMessenger.Default.Send<TelemetryExtensionInvokedMessage>(
|
||||||
new(extensionId, commandId, commandName, success, (ulong)stopwatch.ElapsedMilliseconds));
|
new(extensionId, commandId, commandName, success, (ulong)stopwatch.ElapsedMilliseconds));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,7 +401,7 @@ public partial class ShellViewModel : ObservableObject,
|
|||||||
var kind = result.Kind;
|
var kind = result.Kind;
|
||||||
CoreLogger.LogDebug($"handling {kind.ToString()}");
|
CoreLogger.LogDebug($"handling {kind.ToString()}");
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send<CmdPalInvokeResultMessage>(new(kind));
|
WeakReferenceMessenger.Default.Send<TelemetryInvokeResultMessage>(new(kind));
|
||||||
switch (kind)
|
switch (kind)
|
||||||
{
|
{
|
||||||
case CommandResultKind.Dismiss:
|
case CommandResultKind.Dismiss:
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public class CmdPalInvokeResult : EventBase, IEvent
|
|||||||
|
|
||||||
public CmdPalInvokeResult(CommandResultKind resultKind)
|
public CmdPalInvokeResult(CommandResultKind resultKind)
|
||||||
{
|
{
|
||||||
|
EventName = "CmdPal_InvokeResult";
|
||||||
ResultKind = resultKind.ToString();
|
ResultKind = resultKind.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,45 +6,42 @@ using CommunityToolkit.Mvvm.Messaging;
|
|||||||
using Microsoft.CmdPal.Core.Common.Services;
|
using Microsoft.CmdPal.Core.Common.Services;
|
||||||
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||||
using Microsoft.CmdPal.UI.Events;
|
using Microsoft.CmdPal.UI.Events;
|
||||||
|
using Microsoft.CommandPalette.Extensions;
|
||||||
using Microsoft.PowerToys.Telemetry;
|
using Microsoft.PowerToys.Telemetry;
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.UI;
|
namespace Microsoft.CmdPal.UI;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// TelemetryForwarder is responsible for forwarding telemetry events from the
|
/// TelemetryForwarder is responsible for forwarding telemetry events from the
|
||||||
/// command palette core to PowerToys Telemetry.
|
/// command palette to PowerToys Telemetry.
|
||||||
/// This allows us to emit telemetry events as messages from the core,
|
/// Listens to telemetry-specific messages from the core layer and logs them to PowerToys telemetry.
|
||||||
/// and then handle them by logging to our PT telemetry provider.
|
/// Also implements ITelemetryService for dependency injection in extensions.
|
||||||
///
|
|
||||||
/// We may in the future want to replace this with a more generic "ITelemetryService"
|
|
||||||
/// or something similar, but this works for now.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class TelemetryForwarder :
|
internal sealed class TelemetryForwarder :
|
||||||
ITelemetryService,
|
ITelemetryService,
|
||||||
IRecipient<BeginInvokeMessage>,
|
IRecipient<TelemetryBeginInvokeMessage>,
|
||||||
IRecipient<CmdPalInvokeResultMessage>,
|
IRecipient<TelemetryInvokeResultMessage>,
|
||||||
IRecipient<ExtensionInvokedMessage>,
|
IRecipient<TelemetryExtensionInvokedMessage>
|
||||||
IRecipient<SessionDurationMessage>
|
|
||||||
{
|
{
|
||||||
public TelemetryForwarder()
|
public TelemetryForwarder()
|
||||||
{
|
{
|
||||||
WeakReferenceMessenger.Default.Register<BeginInvokeMessage>(this);
|
WeakReferenceMessenger.Default.Register<TelemetryBeginInvokeMessage>(this);
|
||||||
WeakReferenceMessenger.Default.Register<CmdPalInvokeResultMessage>(this);
|
WeakReferenceMessenger.Default.Register<TelemetryInvokeResultMessage>(this);
|
||||||
WeakReferenceMessenger.Default.Register<ExtensionInvokedMessage>(this);
|
WeakReferenceMessenger.Default.Register<TelemetryExtensionInvokedMessage>(this);
|
||||||
WeakReferenceMessenger.Default.Register<SessionDurationMessage>(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Receive(CmdPalInvokeResultMessage message)
|
// Message handlers for telemetry events from core layer
|
||||||
{
|
public void Receive(TelemetryBeginInvokeMessage message)
|
||||||
PowerToysTelemetry.Log.WriteEvent(new CmdPalInvokeResult(message.Kind));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Receive(BeginInvokeMessage message)
|
|
||||||
{
|
{
|
||||||
PowerToysTelemetry.Log.WriteEvent(new BeginInvoke());
|
PowerToysTelemetry.Log.WriteEvent(new BeginInvoke());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Receive(ExtensionInvokedMessage message)
|
public void Receive(TelemetryInvokeResultMessage message)
|
||||||
|
{
|
||||||
|
PowerToysTelemetry.Log.WriteEvent(new CmdPalInvokeResult(message.Kind));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(TelemetryExtensionInvokedMessage message)
|
||||||
{
|
{
|
||||||
PowerToysTelemetry.Log.WriteEvent(new CmdPalExtensionInvoked(
|
PowerToysTelemetry.Log.WriteEvent(new CmdPalExtensionInvoked(
|
||||||
message.ExtensionId,
|
message.ExtensionId,
|
||||||
@@ -52,20 +49,35 @@ internal sealed class TelemetryForwarder :
|
|||||||
message.CommandName,
|
message.CommandName,
|
||||||
message.Success,
|
message.Success,
|
||||||
message.ExecutionTimeMs));
|
message.ExecutionTimeMs));
|
||||||
|
|
||||||
|
// Increment session counter for commands executed
|
||||||
|
if (App.Current.AppWindow is MainWindow mainWindow)
|
||||||
|
{
|
||||||
|
mainWindow.IncrementCommandsExecuted();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Receive(SessionDurationMessage message)
|
// Static method for logging session duration from UI layer
|
||||||
|
public static void LogSessionDuration(
|
||||||
|
ulong durationMs,
|
||||||
|
int commandsExecuted,
|
||||||
|
int pagesVisited,
|
||||||
|
string dismissalReason,
|
||||||
|
int searchQueriesCount,
|
||||||
|
int maxNavigationDepth,
|
||||||
|
int errorCount)
|
||||||
{
|
{
|
||||||
PowerToysTelemetry.Log.WriteEvent(new CmdPalSessionDuration(
|
PowerToysTelemetry.Log.WriteEvent(new CmdPalSessionDuration(
|
||||||
message.DurationMs,
|
durationMs,
|
||||||
message.CommandsExecuted,
|
commandsExecuted,
|
||||||
message.PagesVisited,
|
pagesVisited,
|
||||||
message.DismissalReason,
|
dismissalReason,
|
||||||
message.SearchQueriesCount,
|
searchQueriesCount,
|
||||||
message.MaxNavigationDepth,
|
maxNavigationDepth,
|
||||||
message.ErrorCount));
|
errorCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ITelemetryService implementation for dependency injection in extensions
|
||||||
public void LogRunQuery(string query, int resultCount, ulong durationMs)
|
public void LogRunQuery(string query, int resultCount, ulong durationMs)
|
||||||
{
|
{
|
||||||
PowerToysTelemetry.Log.WriteEvent(new CmdPalRunQuery(query, resultCount, durationMs));
|
PowerToysTelemetry.Log.WriteEvent(new CmdPalRunQuery(query, resultCount, durationMs));
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ public sealed partial class MainWindow : WindowEx,
|
|||||||
IRecipient<ShowWindowMessage>,
|
IRecipient<ShowWindowMessage>,
|
||||||
IRecipient<HideWindowMessage>,
|
IRecipient<HideWindowMessage>,
|
||||||
IRecipient<QuitMessage>,
|
IRecipient<QuitMessage>,
|
||||||
IRecipient<ExtensionInvokedMessage>,
|
|
||||||
IRecipient<NavigateToPageMessage>,
|
IRecipient<NavigateToPageMessage>,
|
||||||
IRecipient<NavigationDepthMessage>,
|
IRecipient<NavigationDepthMessage>,
|
||||||
IRecipient<SearchQueryMessage>,
|
IRecipient<SearchQueryMessage>,
|
||||||
@@ -113,7 +112,6 @@ public sealed partial class MainWindow : WindowEx,
|
|||||||
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
|
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
|
||||||
WeakReferenceMessenger.Default.Register<ShowWindowMessage>(this);
|
WeakReferenceMessenger.Default.Register<ShowWindowMessage>(this);
|
||||||
WeakReferenceMessenger.Default.Register<HideWindowMessage>(this);
|
WeakReferenceMessenger.Default.Register<HideWindowMessage>(this);
|
||||||
WeakReferenceMessenger.Default.Register<ExtensionInvokedMessage>(this);
|
|
||||||
WeakReferenceMessenger.Default.Register<NavigateToPageMessage>(this);
|
WeakReferenceMessenger.Default.Register<NavigateToPageMessage>(this);
|
||||||
WeakReferenceMessenger.Default.Register<NavigationDepthMessage>(this);
|
WeakReferenceMessenger.Default.Register<NavigationDepthMessage>(this);
|
||||||
WeakReferenceMessenger.Default.Register<SearchQueryMessage>(this);
|
WeakReferenceMessenger.Default.Register<SearchQueryMessage>(this);
|
||||||
@@ -537,12 +535,6 @@ public sealed partial class MainWindow : WindowEx,
|
|||||||
|
|
||||||
// Session telemetry: Track metrics during the Command Palette session
|
// Session telemetry: Track metrics during the Command Palette session
|
||||||
// These receivers increment counters that are sent when EndSession is called
|
// These receivers increment counters that are sent when EndSession is called
|
||||||
|
|
||||||
public void Receive(ExtensionInvokedMessage message)
|
|
||||||
{
|
|
||||||
_sessionCommandsExecuted++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Receive(NavigateToPageMessage message)
|
public void Receive(NavigateToPageMessage message)
|
||||||
{
|
{
|
||||||
_sessionPagesVisited++;
|
_sessionPagesVisited++;
|
||||||
@@ -576,18 +568,27 @@ public sealed partial class MainWindow : WindowEx,
|
|||||||
if (_sessionStopwatch is not null)
|
if (_sessionStopwatch is not null)
|
||||||
{
|
{
|
||||||
_sessionStopwatch.Stop();
|
_sessionStopwatch.Stop();
|
||||||
WeakReferenceMessenger.Default.Send<SessionDurationMessage>(new(
|
TelemetryForwarder.LogSessionDuration(
|
||||||
(ulong)_sessionStopwatch.ElapsedMilliseconds,
|
(ulong)_sessionStopwatch.ElapsedMilliseconds,
|
||||||
_sessionCommandsExecuted,
|
_sessionCommandsExecuted,
|
||||||
_sessionPagesVisited,
|
_sessionPagesVisited,
|
||||||
dismissalReason,
|
dismissalReason,
|
||||||
_sessionSearchQueriesCount,
|
_sessionSearchQueriesCount,
|
||||||
_sessionMaxNavigationDepth,
|
_sessionMaxNavigationDepth,
|
||||||
_sessionErrorCount));
|
_sessionErrorCount);
|
||||||
_sessionStopwatch = null;
|
_sessionStopwatch = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Increments the session commands executed counter for telemetry.
|
||||||
|
/// Called by TelemetryForwarder when an extension command is invoked.
|
||||||
|
/// </summary>
|
||||||
|
internal void IncrementCommandsExecuted()
|
||||||
|
{
|
||||||
|
_sessionCommandsExecuted++;
|
||||||
|
}
|
||||||
|
|
||||||
private void HideWindow()
|
private void HideWindow()
|
||||||
{
|
{
|
||||||
// Cloak our HWND to avoid all animations.
|
// Cloak our HWND to avoid all animations.
|
||||||
|
|||||||
Reference in New Issue
Block a user