mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 19:57:57 +01:00
add CmdPal_SessionDuration telemetry event
This commit is contained in:
@@ -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>
|
||||
/// Message sent when an error occurs during command execution.
|
||||
/// Used to track session error count for telemetry.
|
||||
/// </summary>
|
||||
public record ErrorOccurredMessage();
|
||||
@@ -4,4 +4,8 @@
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Message sent when an extension command or page is invoked.
|
||||
/// Captures extension usage metrics for telemetry tracking.
|
||||
/// </summary>
|
||||
public record ExtensionInvokedMessage(string ExtensionId, string CommandType, bool Success, ulong ExecutionTimeMs);
|
||||
|
||||
@@ -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>
|
||||
/// Message containing the current navigation depth (BackStack count) when navigating to a page.
|
||||
/// Used to track maximum navigation depth reached during a session for telemetry.
|
||||
/// </summary>
|
||||
public record NavigationDepthMessage(int Depth);
|
||||
@@ -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>
|
||||
/// Message sent when a search query is executed in the Command Palette.
|
||||
/// Used to track session search activity for telemetry.
|
||||
/// </summary>
|
||||
public record SearchQueryMessage();
|
||||
@@ -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>
|
||||
/// Message containing session telemetry data from Command Palette launch to dismissal.
|
||||
/// Used to aggregate metrics like duration, commands executed, pages visited, and search activity.
|
||||
/// </summary>
|
||||
public record SessionDurationMessage(ulong DurationMs, int CommandsExecuted, int PagesVisited, string DismissalReason, int SearchQueriesCount, int MaxNavigationDepth, int ErrorCount);
|
||||
@@ -269,7 +269,7 @@ public partial class ShellViewModel : ObservableObject,
|
||||
var isMainPage = command == _rootPage;
|
||||
_isNested = !isMainPage;
|
||||
|
||||
// Track extension page navigation
|
||||
// Telemetry: Track extension page navigation for session metrics
|
||||
if (host is not null)
|
||||
{
|
||||
string extensionId = host.GetExtensionDisplayName() ?? "builtin";
|
||||
@@ -347,6 +347,7 @@ public partial class ShellViewModel : ObservableObject,
|
||||
|
||||
private void SafeHandleInvokeCommandSynchronous(PerformCommandMessage message, IInvokableCommand invokable, AppExtensionHost? host)
|
||||
{
|
||||
// Telemetry: Track command execution time and success
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||||
var command = message.Command.Unsafe;
|
||||
string extensionId = host?.GetExtensionDisplayName() ?? "builtin";
|
||||
@@ -371,12 +372,16 @@ public partial class ShellViewModel : ObservableObject,
|
||||
success = false;
|
||||
_handleInvokeTask = null;
|
||||
|
||||
// Telemetry: Track errors for session metrics
|
||||
WeakReferenceMessenger.Default.Send<ErrorOccurredMessage>(new());
|
||||
|
||||
// TODO: It would be better to do this as a page exception, rather
|
||||
// than a silent log message.
|
||||
host?.Log(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Telemetry: Send extension invocation metrics (always sent, even on failure)
|
||||
stopwatch.Stop();
|
||||
WeakReferenceMessenger.Default.Send<ExtensionInvokedMessage>(
|
||||
new(extensionId, commandType, success, (ulong)stopwatch.ElapsedMilliseconds));
|
||||
|
||||
@@ -329,6 +329,12 @@ public sealed partial class SearchBar : UserControl,
|
||||
if (CurrentPageViewModel is not null)
|
||||
{
|
||||
CurrentPageViewModel.SearchTextBox = FilterBox.Text;
|
||||
|
||||
// Telemetry: Track search query count for session metrics (only non-empty queries)
|
||||
if (!string.IsNullOrWhiteSpace(FilterBox.Text))
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<SearchQueryMessage>(new());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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 Command Palette session duration from launch to close.
|
||||
/// Purpose: Understand user engagement patterns - quick actions vs. browsing behavior.
|
||||
/// </summary>
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class CmdPalSessionDuration : EventBase, IEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the session duration in milliseconds.
|
||||
/// </summary>
|
||||
public ulong DurationMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of commands executed during the session.
|
||||
/// </summary>
|
||||
public int CommandsExecuted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of pages visited during the session.
|
||||
/// </summary>
|
||||
public int PagesVisited { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason for dismissal (Escape, LostFocus, Command, etc.).
|
||||
/// </summary>
|
||||
public string DismissalReason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of search queries executed during the session.
|
||||
/// </summary>
|
||||
public int SearchQueriesCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum navigation depth reached during the session.
|
||||
/// </summary>
|
||||
public int MaxNavigationDepth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of errors encountered during the session.
|
||||
/// </summary>
|
||||
public int ErrorCount { get; set; }
|
||||
|
||||
public CmdPalSessionDuration(ulong durationMs, int commandsExecuted, int pagesVisited, string dismissalReason, int searchQueriesCount, int maxNavigationDepth, int errorCount)
|
||||
{
|
||||
EventName = "CmdPal_SessionDuration";
|
||||
DurationMs = durationMs;
|
||||
CommandsExecuted = commandsExecuted;
|
||||
PagesVisited = pagesVisited;
|
||||
DismissalReason = dismissalReason;
|
||||
SearchQueriesCount = searchQueriesCount;
|
||||
MaxNavigationDepth = maxNavigationDepth;
|
||||
ErrorCount = errorCount;
|
||||
}
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
@@ -23,13 +23,15 @@ internal sealed class TelemetryForwarder :
|
||||
ITelemetryService,
|
||||
IRecipient<BeginInvokeMessage>,
|
||||
IRecipient<CmdPalInvokeResultMessage>,
|
||||
IRecipient<ExtensionInvokedMessage>
|
||||
IRecipient<ExtensionInvokedMessage>,
|
||||
IRecipient<SessionDurationMessage>
|
||||
{
|
||||
public TelemetryForwarder()
|
||||
{
|
||||
WeakReferenceMessenger.Default.Register<BeginInvokeMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<CmdPalInvokeResultMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<ExtensionInvokedMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<SessionDurationMessage>(this);
|
||||
}
|
||||
|
||||
public void Receive(CmdPalInvokeResultMessage message)
|
||||
@@ -51,6 +53,18 @@ internal sealed class TelemetryForwarder :
|
||||
message.ExecutionTimeMs));
|
||||
}
|
||||
|
||||
public void Receive(SessionDurationMessage message)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new CmdPalSessionDuration(
|
||||
message.DurationMs,
|
||||
message.CommandsExecuted,
|
||||
message.PagesVisited,
|
||||
message.DismissalReason,
|
||||
message.SearchQueriesCount,
|
||||
message.MaxNavigationDepth,
|
||||
message.ErrorCount));
|
||||
}
|
||||
|
||||
public void LogRunQuery(string query, int resultCount, ulong durationMs)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new CmdPalRunQuery(query, resultCount, durationMs));
|
||||
|
||||
@@ -49,6 +49,11 @@ public sealed partial class MainWindow : WindowEx,
|
||||
IRecipient<ShowWindowMessage>,
|
||||
IRecipient<HideWindowMessage>,
|
||||
IRecipient<QuitMessage>,
|
||||
IRecipient<ExtensionInvokedMessage>,
|
||||
IRecipient<NavigateToPageMessage>,
|
||||
IRecipient<NavigationDepthMessage>,
|
||||
IRecipient<SearchQueryMessage>,
|
||||
IRecipient<ErrorOccurredMessage>,
|
||||
IDisposable
|
||||
{
|
||||
private const int DefaultWidth = 800;
|
||||
@@ -66,6 +71,14 @@ public sealed partial class MainWindow : WindowEx,
|
||||
private readonly HiddenOwnerWindowBehavior _hiddenOwnerBehavior = new();
|
||||
private bool _ignoreHotKeyWhenFullScreen = true;
|
||||
|
||||
// Session tracking for telemetry
|
||||
private Stopwatch? _sessionStopwatch;
|
||||
private int _sessionCommandsExecuted;
|
||||
private int _sessionPagesVisited;
|
||||
private int _sessionSearchQueriesCount;
|
||||
private int _sessionMaxNavigationDepth;
|
||||
private int _sessionErrorCount;
|
||||
|
||||
private DesktopAcrylicController? _acrylicController;
|
||||
private SystemBackdropConfiguration? _configurationSource;
|
||||
|
||||
@@ -100,6 +113,11 @@ public sealed partial class MainWindow : WindowEx,
|
||||
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<ShowWindowMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<HideWindowMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<ExtensionInvokedMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<NavigateToPageMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<NavigationDepthMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<SearchQueryMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<ErrorOccurredMessage>(this);
|
||||
|
||||
// Hide our titlebar.
|
||||
// We need to both ExtendsContentIntoTitleBar, then set the height to Collapsed
|
||||
@@ -484,6 +502,11 @@ public sealed partial class MainWindow : WindowEx,
|
||||
{
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
|
||||
// Start session tracking
|
||||
_sessionStopwatch = Stopwatch.StartNew();
|
||||
_sessionCommandsExecuted = 0;
|
||||
_sessionPagesVisited = 0;
|
||||
|
||||
ShowHwnd(message.Hwnd, settings.SummonOn);
|
||||
}
|
||||
|
||||
@@ -492,6 +515,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
// This might come in off the UI thread. Make sure to hop back.
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
EndSession("Hide");
|
||||
HideWindow();
|
||||
});
|
||||
}
|
||||
@@ -506,10 +530,64 @@ public sealed partial class MainWindow : WindowEx,
|
||||
// This might come in off the UI thread. Make sure to hop back.
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
EndSession("Dismiss");
|
||||
HideWindow();
|
||||
});
|
||||
}
|
||||
|
||||
// Session telemetry: Track metrics during the Command Palette session
|
||||
// These receivers increment counters that are sent when EndSession is called
|
||||
|
||||
public void Receive(ExtensionInvokedMessage message)
|
||||
{
|
||||
_sessionCommandsExecuted++;
|
||||
}
|
||||
|
||||
public void Receive(NavigateToPageMessage message)
|
||||
{
|
||||
_sessionPagesVisited++;
|
||||
}
|
||||
|
||||
public void Receive(NavigationDepthMessage message)
|
||||
{
|
||||
if (message.Depth > _sessionMaxNavigationDepth)
|
||||
{
|
||||
_sessionMaxNavigationDepth = message.Depth;
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(SearchQueryMessage message)
|
||||
{
|
||||
_sessionSearchQueriesCount++;
|
||||
}
|
||||
|
||||
public void Receive(ErrorOccurredMessage message)
|
||||
{
|
||||
_sessionErrorCount++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the current telemetry session and emits the CmdPal_SessionDuration event.
|
||||
/// Aggregates all session metrics collected since ShowWindow and sends them to telemetry.
|
||||
/// </summary>
|
||||
/// <param name="dismissalReason">The reason the session ended (e.g., Dismiss, Hide, LostFocus).</param>
|
||||
private void EndSession(string dismissalReason)
|
||||
{
|
||||
if (_sessionStopwatch is not null)
|
||||
{
|
||||
_sessionStopwatch.Stop();
|
||||
WeakReferenceMessenger.Default.Send<SessionDurationMessage>(new(
|
||||
(ulong)_sessionStopwatch.ElapsedMilliseconds,
|
||||
_sessionCommandsExecuted,
|
||||
_sessionPagesVisited,
|
||||
dismissalReason,
|
||||
_sessionSearchQueriesCount,
|
||||
_sessionMaxNavigationDepth,
|
||||
_sessionErrorCount));
|
||||
_sessionStopwatch = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void HideWindow()
|
||||
{
|
||||
// Cloak our HWND to avoid all animations.
|
||||
@@ -681,6 +759,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
}
|
||||
|
||||
// This will DWM cloak our window:
|
||||
EndSession("LostFocus");
|
||||
HideWindow();
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new CmdPalDismissedOnLostFocus());
|
||||
|
||||
@@ -161,6 +161,9 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new OpenPage(RootFrame.BackStackDepth, message.Page.Id));
|
||||
|
||||
// Telemetry: Send navigation depth for session max depth tracking
|
||||
WeakReferenceMessenger.Default.Send(new NavigationDepthMessage(RootFrame.BackStackDepth));
|
||||
|
||||
if (!ViewModel.IsNested)
|
||||
{
|
||||
// todo BODGY
|
||||
|
||||
Reference in New Issue
Block a user