From 4fdc02b6986d5e07cff6f486a95cad41a88c0bc9 Mon Sep 17 00:00:00 2001 From: "James Truher [MSFT]" Date: Mon, 16 Sep 2024 16:28:19 -0700 Subject: [PATCH] Add telemetry to track the use of features (#24247) --- .../CoreCLR/CorePsAssemblyLoadContext.cs | 8 ++- .../engine/Modules/ModuleUtils.cs | 2 - .../FeedbackSubsystem/IFeedbackProvider.cs | 16 +++-- .../engine/Subsystem/SubsystemInfo.cs | 2 + .../utils/Telemetry.cs | 71 +++++++++++++++++-- 5 files changed, 83 insertions(+), 16 deletions(-) diff --git a/src/System.Management.Automation/CoreCLR/CorePsAssemblyLoadContext.cs b/src/System.Management.Automation/CoreCLR/CorePsAssemblyLoadContext.cs index 6be8d3c595..5a15df53ca 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsAssemblyLoadContext.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsAssemblyLoadContext.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Reflection; using System.Runtime.Loader; +using Microsoft.PowerShell.Telemetry; namespace System.Management.Automation { @@ -607,16 +608,19 @@ namespace System.Management.Automation [UnmanagedCallersOnly] public static int LoadAssemblyFromNativeMemory(IntPtr data, int size) { + int result = 0; try { using var stream = new UnmanagedMemoryStream((byte*)data, size); AssemblyLoadContext.Default.LoadFromStream(stream); - return 0; } catch { - return -1; + result = -1; } + + ApplicationInsightsTelemetry.SendUseTelemetry("PowerShellUnsafeAssemblyLoad", result == 0 ? "1" : "0"); + return result; } } } diff --git a/src/System.Management.Automation/engine/Modules/ModuleUtils.cs b/src/System.Management.Automation/engine/Modules/ModuleUtils.cs index f58f51dfd3..41bf4ac352 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleUtils.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleUtils.cs @@ -44,8 +44,6 @@ namespace System.Management.Automation.Internal | FileAttributes.Offline | (FileAttributes)FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS | (FileAttributes)FILE_ATTRIBUTE_RECALL_ON_OPEN; - - return; } /// diff --git a/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/IFeedbackProvider.cs b/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/IFeedbackProvider.cs index aba105c8fa..328eb4eee8 100644 --- a/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/IFeedbackProvider.cs +++ b/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/IFeedbackProvider.cs @@ -9,6 +9,7 @@ using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; using System.Threading; +using Microsoft.PowerShell.Telemetry; namespace System.Management.Automation.Subsystem.Feedback { @@ -287,13 +288,14 @@ namespace System.Management.Automation.Subsystem.Feedback .AddParameter("ExpandProperty", "Name") .Invoke(); - if (results.Count > 0) - { - return new FeedbackItem( - SuggestionStrings.Suggestion_CommandNotFound, - new List(results), - FeedbackDisplayLayout.Landscape); - } + if (results.Count > 0) + { + ApplicationInsightsTelemetry.SendUseTelemetry("FuzzyMatching", "CommandNotFound"); + return new FeedbackItem( + SuggestionStrings.Suggestion_CommandNotFound, + new List(results), + FeedbackDisplayLayout.Landscape); + } return null; } diff --git a/src/System.Management.Automation/engine/Subsystem/SubsystemInfo.cs b/src/System.Management.Automation/engine/Subsystem/SubsystemInfo.cs index c290e807ce..8756fd69c9 100644 --- a/src/System.Management.Automation/engine/Subsystem/SubsystemInfo.cs +++ b/src/System.Management.Automation/engine/Subsystem/SubsystemInfo.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation.Internal; +using Microsoft.PowerShell.Telemetry; namespace System.Management.Automation.Subsystem { @@ -96,6 +97,7 @@ namespace System.Management.Automation.Subsystem internal void RegisterImplementation(ISubsystem impl) { AddImplementation(impl); + ApplicationInsightsTelemetry.SendUseTelemetry(ApplicationInsightsTelemetry.s_subsystemRegistration, impl.Name); } internal ISubsystem UnregisterImplementation(Guid id) diff --git a/src/System.Management.Automation/utils/Telemetry.cs b/src/System.Management.Automation/utils/Telemetry.cs index ff655bbe60..9e40e60a4d 100644 --- a/src/System.Management.Automation/utils/Telemetry.cs +++ b/src/System.Management.Automation/utils/Telemetry.cs @@ -83,6 +83,13 @@ namespace Microsoft.PowerShell.Telemetry /// Remote session creation. /// RemoteSessionOpen, + + /// + /// Send telemetry for a stable feature when used. + /// By making a distinction between this and experimental feature use, it will make + /// queries much easier. + /// + FeatureUse, } /// @@ -110,6 +117,9 @@ namespace Microsoft.PowerShell.Telemetry /// public static class ApplicationInsightsTelemetry { + // The string for SubsystermRegistration + internal const string s_subsystemRegistration = "Subsystem.Registration"; + // If this env var is true, yes, or 1, telemetry will NOT be sent. private const string _telemetryOptoutEnvVar = "POWERSHELL_TELEMETRY_OPTOUT"; @@ -152,6 +162,8 @@ namespace Microsoft.PowerShell.Telemetry private static readonly HashSet s_knownModules; private static readonly HashSet s_knownModuleTags; + private static readonly HashSet s_knownSubsystemNames; + /// Gets a value indicating whether telemetry can be sent. public static bool CanSendTelemetry { get; private set; } = false; @@ -620,6 +632,13 @@ namespace Microsoft.PowerShell.Telemetry }; s_uniqueUserIdentifier = GetUniqueIdentifier().ToString(); + s_knownSubsystemNames = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Completion", + "general", + "Windows Package Manager - WinGet", + "Az Predictor" + }; } } @@ -715,7 +734,7 @@ namespace Microsoft.PowerShell.Telemetry s_telemetryClient. GetMetric(new MetricIdentifier(string.Empty, telemetryType.ToString(), "uuid", "SessionId", "ModuleName", "Version", "Tag")). - TrackValue(metricValue: 1.0, s_uniqueUserIdentifier, s_sessionId, allowedModuleName, allowedModuleVersion, allowedModuleTagString); + TrackValue(metricValue: 1.0, s_uniqueUserIdentifier, s_sessionId, allowedModuleName, allowedModuleVersion, allowedModuleTagString); } catch { @@ -754,7 +773,8 @@ namespace Microsoft.PowerShell.Telemetry /// /// The type of telemetry that we'll be sending. /// The specific details about the telemetry. - internal static void SendTelemetryMetric(TelemetryType metricId, string data) + /// The count of instances for the telemetry payload. + internal static void SendTelemetryMetric(TelemetryType metricId, string data, double value = 1.0) { if (!CanSendTelemetry) { @@ -776,12 +796,13 @@ namespace Microsoft.PowerShell.Telemetry case TelemetryType.ExperimentalEngineFeatureActivation: case TelemetryType.ExperimentalEngineFeatureDeactivation: case TelemetryType.ExperimentalFeatureUse: - s_telemetryClient.GetMetric(metricName, "uuid", "SessionId", "Detail").TrackValue(metricValue: 1.0, s_uniqueUserIdentifier, s_sessionId, data); + case TelemetryType.FeatureUse: + s_telemetryClient.GetMetric(metricName, "uuid", "SessionId", "Detail").TrackValue(metricValue: value, s_uniqueUserIdentifier, s_sessionId, data); break; case TelemetryType.ExperimentalModuleFeatureActivation: case TelemetryType.ExperimentalModuleFeatureDeactivation: string experimentalFeatureName = GetExperimentalFeatureName(data); - s_telemetryClient.GetMetric(metricName, "uuid", "SessionId", "Detail").TrackValue(metricValue: 1.0, s_uniqueUserIdentifier, s_sessionId, experimentalFeatureName); + s_telemetryClient.GetMetric(metricName, "uuid", "SessionId", "Detail").TrackValue(metricValue: value, s_uniqueUserIdentifier, s_sessionId, experimentalFeatureName); break; } } @@ -792,6 +813,35 @@ namespace Microsoft.PowerShell.Telemetry } } + /// + /// Send additional information about an feature as it is used. + /// + /// The name of the feature. + /// The details about the feature use. + /// The value to report when sending the payload. + internal static void SendUseTelemetry(string featureName, string detail, double value = 1.0) + { + if (!CanSendTelemetry) + { + return; + } + + // keep payload small + if (featureName is null || detail is null || featureName.Length > 33 || detail.Length > 33) + { + return; + } + + if (string.Compare(featureName, s_subsystemRegistration, true) == 0) + { + ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.FeatureUse, string.Join(":", featureName, GetSubsystemName(detail)), value); + } + else + { + ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.FeatureUse, string.Join(":", featureName, detail), value); + } + } + /// /// Send additional information about an experimental feature as it is used. /// @@ -822,7 +872,18 @@ namespace Microsoft.PowerShell.Telemetry return Anonymous; } - // Get the module name. If we can report it, we'll return the name, otherwise, we'll return "anonymous" + // Get the module name. If we can report it, we'll return the name, otherwise, we'll return the string "anonymous" + private static string GetSubsystemName(string subsystemNameToValidate) + { + if (s_knownSubsystemNames.Contains(subsystemNameToValidate)) + { + return subsystemNameToValidate; + } + + return Anonymous; + } + + // Get the module name. If we can report it, we'll return the name, otherwise, we'll return anonymous. private static string GetModuleName(string moduleNameToValidate) { if (s_knownModules.Contains(moduleNameToValidate))