From 14d7bfee1d5950215acf861b7165a3231e07c77e Mon Sep 17 00:00:00 2001 From: "James Truher [MSFT]" Date: Wed, 8 Mar 2023 09:29:11 -0800 Subject: [PATCH] Telemetry improvements for tracking experimental feature optout (#18762) --- .../ExperimentalFeature.cs | 18 ++++++++- .../engine/NativeCommandProcessor.cs | 33 ++++++++++++++- .../utils/Telemetry.cs | 40 ++++++++++++++++++- 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index 58ba1aa107..f7c2a73ba2 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -157,6 +157,20 @@ namespace System.Management.Automation EnabledExperimentalFeatureNames = ProcessEnabledFeatures(enabledFeatures); } + /// + /// We need to notify which features were not enabled. + /// + private static void SendTelemetryForDeactivatedFeatures(ReadOnlyBag enabledFeatures) + { + foreach (var feature in EngineExperimentalFeatures) + { + if (!enabledFeatures.Contains(feature.Name)) + { + ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ExperimentalEngineFeatureDeactivation, feature.Name); + } + } + } + /// /// Process the array of enabled feature names retrieved from configuration. /// Ignore invalid feature names and unavailable engine feature names, and @@ -198,7 +212,9 @@ namespace System.Management.Automation } } - return new ReadOnlyBag(new HashSet(list, StringComparer.OrdinalIgnoreCase)); + ReadOnlyBag features = new(new HashSet(list, StringComparer.OrdinalIgnoreCase)); + SendTelemetryForDeactivatedFeatures(features); + return features; } /// diff --git a/src/System.Management.Automation/engine/NativeCommandProcessor.cs b/src/System.Management.Automation/engine/NativeCommandProcessor.cs index b825c024ef..f34fc1987b 100644 --- a/src/System.Management.Automation/engine/NativeCommandProcessor.cs +++ b/src/System.Management.Automation/engine/NativeCommandProcessor.cs @@ -18,6 +18,7 @@ using System.Runtime.Serialization; using System.Text; using System.Threading; using System.Xml; +using Microsoft.PowerShell.Telemetry; using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation @@ -872,8 +873,36 @@ namespace System.Management.Automation this.commandRuntime.PipelineProcessor.ExecutionFailed = true; - if (!ExperimentalFeature.IsEnabled(ExperimentalFeature.PSNativeCommandErrorActionPreferenceFeatureName) - || !Command.Context.GetBooleanPreference(SpecialVariables.PSNativeCommandUseErrorActionPreferenceVarPath, defaultPref: false, out _)) + // Feature is not enabled, so return + if (!ExperimentalFeature.IsEnabled(ExperimentalFeature.PSNativeCommandErrorActionPreferenceFeatureName)) + { + return; + } + + // We send telemetry information only if the feature is enabled. + // This shouldn't be done once, because it's a run-time check we should send telemetry every time. + // Report on the following conditions: + // - The variable is not present + // - The value is not set (variable is null) + // - The value is set to true or false + bool useDefaultSetting; + bool nativeErrorActionPreferenceSetting = Command.Context.GetBooleanPreference( + SpecialVariables.PSNativeCommandUseErrorActionPreferenceVarPath, + defaultPref: false, + out useDefaultSetting); + + // The variable is unset + if (useDefaultSetting) + { + ApplicationInsightsTelemetry.SendExperimentalUseData(ExperimentalFeature.PSNativeCommandErrorActionPreferenceFeatureName, "unset"); + return; + } + + // Send the value that was set. + ApplicationInsightsTelemetry.SendExperimentalUseData(ExperimentalFeature.PSNativeCommandErrorActionPreferenceFeatureName, nativeErrorActionPreferenceSetting.ToString()); + + // if it was explicitly set to false, return + if (!nativeErrorActionPreferenceSetting) { return; } diff --git a/src/System.Management.Automation/utils/Telemetry.cs b/src/System.Management.Automation/utils/Telemetry.cs index 6ae1a93d6d..88193cc03a 100644 --- a/src/System.Management.Automation/utils/Telemetry.cs +++ b/src/System.Management.Automation/utils/Telemetry.cs @@ -38,12 +38,31 @@ namespace Microsoft.PowerShell.Telemetry /// WinCompatModuleLoad, + /// + /// Send telemetry for experimental module feature deactivation. + /// All experimental engine features will be have telemetry. + /// + ExperimentalEngineFeatureDeactivation, + /// /// Send telemetry for experimental module feature activation. /// All experimental engine features will be have telemetry. /// ExperimentalEngineFeatureActivation, + /// + /// Send telemetry for an experimental feature when use. + /// + ExperimentalFeatureUse, + + /// + /// Send telemetry for experimental module feature deactivation. + /// Experimental module features will send telemetry based on the module it is in. + /// If we send telemetry for the module, we will also do so for any experimental feature + /// in that module. + /// + ExperimentalModuleFeatureDeactivation, + /// /// Send telemetry for experimental module feature activation. /// Experimental module features will send telemetry based on the module it is in. @@ -126,7 +145,7 @@ namespace Microsoft.PowerShell.Telemetry private static readonly HashSet s_knownModules; /// Gets a value indicating whether telemetry can be sent. - public static bool CanSendTelemetry { get; private set; } + public static bool CanSendTelemetry { get; private set; } = false; /// /// Initializes static members of the class. @@ -703,9 +722,12 @@ namespace Microsoft.PowerShell.Telemetry case TelemetryType.PowerShellCreate: case TelemetryType.RemoteSessionOpen: 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); 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); break; @@ -718,6 +740,21 @@ namespace Microsoft.PowerShell.Telemetry } } + /// + /// Send additional information about an experimental feature as it is used. + /// + /// The name of the experimental feature. + /// The details about the experimental feature use. + internal static void SendExperimentalUseData(string featureName, string detail) + { + if (!CanSendTelemetry) + { + return; + } + + ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ExperimentalFeatureUse, string.Join(":", featureName, detail)); + } + // Get the experimental feature name. If we can report it, we'll return the name of the feature, otherwise, we'll return "anonymous" private static string GetExperimentalFeatureName(string featureNameToValidate) { @@ -779,6 +816,7 @@ namespace Microsoft.PowerShell.Telemetry properties.Add("UUID", s_uniqueUserIdentifier); properties.Add("GitCommitID", PSVersionInfo.GitCommitId); properties.Add("OSDescription", RuntimeInformation.OSDescription); + properties.Add("RuntimeIdentifier", RuntimeInformation.RuntimeIdentifier); properties.Add("OSChannel", string.IsNullOrEmpty(channel) ? "unknown" : channel); properties.Add("StartMode", string.IsNullOrEmpty(mode) ? "unknown" : mode);