diff --git a/src/Common/Commands.Common/AzurePSCmdlet.cs b/src/Common/Commands.Common/AzurePSCmdlet.cs
index ed00e2e32a4c..43555c5eb5f1 100644
--- a/src/Common/Commands.Common/AzurePSCmdlet.cs
+++ b/src/Common/Commands.Common/AzurePSCmdlet.cs
@@ -36,6 +36,17 @@ public abstract class AzurePSCmdlet : PSCmdlet
protected static AzureProfile _currentProfile = null;
protected static AzurePSDataCollectionProfile _dataCollectionProfile = null;
+ protected AzurePSQoSEvent QosEvent;
+
+ protected virtual bool IsUsageMetricEnabled {
+ get { return false; }
+ }
+
+ protected virtual bool IsErrorMetricEnabled
+ {
+ get { return true; }
+ }
+
[Parameter(Mandatory = false, HelpMessage = "In-memory profile.")]
public AzureProfile Profile { get; set; }
@@ -208,6 +219,22 @@ protected static AzurePSDataCollectionProfile GetDataCollectionProfile()
return _dataCollectionProfile;
}
+ ///
+ /// Check whether the data collection is opted in from user
+ ///
+ /// true if allowed
+ public static bool IsDataCollectionAllowed()
+ {
+ if (_dataCollectionProfile != null &&
+ _dataCollectionProfile.EnableAzureDataCollection.HasValue &&
+ _dataCollectionProfile.EnableAzureDataCollection.Value)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
///
/// Save the current data collection profile Json data into the default file path
///
@@ -305,7 +332,7 @@ protected override void BeginProcessing()
{
InitializeProfile();
PromptForDataCollectionProfileIfNotExists();
-
+ InitializeQosEvent();
if (string.IsNullOrEmpty(ParameterSetName))
{
WriteDebugWithTimestamp(string.Format(Resources.BeginProcessingWithoutParameterSetLog, this.GetType().Name));
@@ -346,6 +373,7 @@ protected virtual void InitializeProfile()
///
protected override void EndProcessing()
{
+ LogQosEvent();
string message = string.Format(Resources.EndProcessingLog, this.GetType().Name);
WriteDebugWithTimestamp(message);
@@ -379,6 +407,9 @@ protected bool IsVerbose()
public new void WriteError(ErrorRecord errorRecord)
{
FlushDebugMessages();
+ QosEvent.Exception = errorRecord.Exception;
+ QosEvent.IsSuccess = false;
+ LogQosEvent(true);
base.WriteError(errorRecord);
}
@@ -506,6 +537,62 @@ private void FlushDebugMessages()
}
}
+ protected void InitializeQosEvent()
+ {
+ QosEvent = new AzurePSQoSEvent()
+ {
+ CmdletType = this.GetType().Name,
+ IsSuccess = true,
+ };
+
+ if (this.Profile != null && this.Profile.DefaultSubscription != null)
+ {
+ QosEvent.Uid = MetricHelper.GenerateSha256HashString(
+ this.Profile.DefaultSubscription.Id.ToString());
+ }
+ else
+ {
+ QosEvent.Uid = "defaultid";
+ }
+ }
+
+ ///
+ /// Invoke this method when the cmdlet is completed or terminated.
+ ///
+ protected void LogQosEvent(bool waitForMetricSending = false)
+ {
+ if (QosEvent == null)
+ {
+ return;
+ }
+
+ QosEvent.FinishQosEvent();
+
+ if (!IsUsageMetricEnabled && (!IsErrorMetricEnabled || QosEvent.IsSuccess))
+ {
+ return;
+ }
+
+ if (!IsDataCollectionAllowed())
+ {
+ return;
+ }
+
+ WriteDebug(QosEvent.ToString());
+
+ try
+ {
+ MetricHelper.LogQoSEvent(QosEvent, IsUsageMetricEnabled, IsErrorMetricEnabled);
+ MetricHelper.FlushMetric(waitForMetricSending);
+ WriteDebug("Finish sending metric.");
+ }
+ catch (Exception e)
+ {
+ //Swallow error from Application Insights event collection.
+ WriteWarning(e.ToString());
+ }
+ }
+
///
/// Asks for confirmation before executing the action.
///
@@ -516,10 +603,19 @@ private void FlushDebugMessages()
/// The action code
protected void ConfirmAction(bool force, string actionMessage, string processMessage, string target, Action action)
{
+ if (QosEvent != null)
+ {
+ QosEvent.PauseQoSTimer();
+ }
+
if (force || ShouldContinue(actionMessage, ""))
{
if (ShouldProcess(target, processMessage))
- {
+ {
+ if (QosEvent != null)
+ {
+ QosEvent.ResumeQosTimer();
+ }
action();
}
}
diff --git a/src/Common/Commands.Common/Commands.Common.csproj b/src/Common/Commands.Common/Commands.Common.csproj
index b908e8a2341a..945d7c23cfec 100644
--- a/src/Common/Commands.Common/Commands.Common.csproj
+++ b/src/Common/Commands.Common/Commands.Common.csproj
@@ -15,6 +15,7 @@
..\..\
true
/assemblyCompareMode:StrongNameIgnoringVersion
+ 06e19c11
true
@@ -54,6 +55,10 @@
False
..\..\packages\Hyak.Common.1.0.2\lib\portable-net403+win+wpa81\Hyak.Common.dll
+
+ ..\..\packages\Microsoft.ApplicationInsights.1.1.1-beta\lib\net45\Microsoft.ApplicationInsights.dll
+ True
+
False
..\..\packages\Microsoft.Azure.Common.2.1.0\lib\net45\Microsoft.Azure.Common.dll
@@ -147,6 +152,7 @@
True
Resources.resx
+
diff --git a/src/Common/Commands.Common/MetricHelper.cs b/src/Common/Commands.Common/MetricHelper.cs
new file mode 100644
index 000000000000..9bb3d20e4296
--- /dev/null
+++ b/src/Common/Commands.Common/MetricHelper.cs
@@ -0,0 +1,165 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.ApplicationInsights;
+using Microsoft.ApplicationInsights.Channel;
+using Microsoft.ApplicationInsights.DataContracts;
+using Microsoft.ApplicationInsights.Extensibility;
+using Microsoft.WindowsAzure.Commands.Utilities.Common;
+
+namespace Microsoft.WindowsAzure.Commands.Common
+{
+ public static class MetricHelper
+ {
+ private const int FlushTimeoutInMilli = 5000;
+ private static readonly TelemetryClient TelemetryClient;
+
+ static MetricHelper()
+ {
+ TelemetryClient = new TelemetryClient();
+ // TODO: InstrumentationKey shall be injected in build server
+ TelemetryClient.InstrumentationKey = "7df6ff70-8353-4672-80d6-568517fed090";
+ // Disable IP collection
+ TelemetryClient.Context.Location.Ip = "0.0.0.0";
+
+ if (TestMockSupport.RunningMocked)
+ {
+ TelemetryConfiguration.Active.DisableTelemetry = true;
+ }
+ }
+
+ public static void LogQoSEvent(AzurePSQoSEvent qos, bool isUsageMetricEnabled, bool isErrorMetricEnabled)
+ {
+ if (!IsMetricTermAccepted())
+ {
+ return;
+ }
+
+ if (isUsageMetricEnabled)
+ {
+ LogUsageEvent(qos);
+ }
+
+ if (isErrorMetricEnabled && qos.Exception != null)
+ {
+ LogExceptionEvent(qos);
+ }
+ }
+
+ private static void LogUsageEvent(AzurePSQoSEvent qos)
+ {
+ var tcEvent = new RequestTelemetry(qos.CmdletType, qos.StartTime, qos.Duration, string.Empty, qos.IsSuccess);
+ tcEvent.Context.User.Id = qos.Uid;
+ tcEvent.Context.User.UserAgent = AzurePowerShell.UserAgentValue.ToString();
+ tcEvent.Context.Device.OperatingSystem = Environment.OSVersion.VersionString;
+
+ TelemetryClient.TrackRequest(tcEvent);
+ }
+
+ private static void LogExceptionEvent(AzurePSQoSEvent qos)
+ {
+ //Log as custome event to exclude actual exception message
+ var tcEvent = new EventTelemetry("CmdletError");
+ tcEvent.Properties.Add("ExceptionType", qos.Exception.GetType().FullName);
+ tcEvent.Properties.Add("StackTrace", qos.Exception.StackTrace);
+ if (qos.Exception.InnerException != null)
+ {
+ tcEvent.Properties.Add("InnerExceptionType", qos.Exception.InnerException.GetType().FullName);
+ tcEvent.Properties.Add("InnerStackTrace", qos.Exception.InnerException.StackTrace);
+ }
+
+ tcEvent.Context.User.Id = qos.Uid;
+ tcEvent.Properties.Add("CmdletType", qos.CmdletType);
+
+ TelemetryClient.TrackEvent(tcEvent);
+ }
+
+ public static bool IsMetricTermAccepted()
+ {
+ return AzurePSCmdlet.IsDataCollectionAllowed();
+ }
+
+ public static void FlushMetric(bool waitForMetricSending)
+ {
+ if (!IsMetricTermAccepted())
+ {
+ return;
+ }
+
+ var flushTask = Task.Run(() => FlushAi());
+ if (waitForMetricSending)
+ {
+ Task.WaitAll(new[] { flushTask }, FlushTimeoutInMilli);
+ }
+ }
+
+ private static void FlushAi()
+ {
+ try
+ {
+ TelemetryClient.Flush();
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+
+ ///
+ /// Gereate a SHA256 Hash string from the originInput.
+ ///
+ ///
+ ///
+ public static string GenerateSha256HashString(string originInput)
+ {
+ SHA256 sha256 = SHA256.Create();
+ var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(originInput));
+ return Encoding.UTF8.GetString(bytes);
+ }
+ }
+}
+
+public class AzurePSQoSEvent
+{
+ private readonly Stopwatch _timer;
+
+ public DateTimeOffset StartTime { get; set; }
+ public TimeSpan Duration { get; set; }
+ public bool IsSuccess { get; set; }
+ public string CmdletType { get; set; }
+ public Exception Exception { get; set; }
+ public string Uid { get; set; }
+
+ public AzurePSQoSEvent()
+ {
+ StartTime = DateTimeOffset.Now;
+ _timer = new Stopwatch();
+ _timer.Start();
+ }
+
+ public void PauseQoSTimer()
+ {
+ _timer.Stop();
+ }
+
+ public void ResumeQosTimer()
+ {
+ _timer.Start();
+ }
+
+ public void FinishQosEvent()
+ {
+ _timer.Stop();
+ Duration = _timer.Elapsed;
+ }
+
+ public override string ToString()
+ {
+ return string.Format(
+ "AzureQoSEvent: CmdletType - {0}; IsSuccess - {1}; Duration - {2}; Exception - {3};",
+ CmdletType, IsSuccess, Duration, Exception);
+ }
+}
diff --git a/src/Common/Commands.Common/packages.config b/src/Common/Commands.Common/packages.config
index abdfbbed145e..90ec260fe139 100644
--- a/src/Common/Commands.Common/packages.config
+++ b/src/Common/Commands.Common/packages.config
@@ -1,6 +1,7 @@
+
diff --git a/src/ResourceManager/Compute/Commands.Compute/Common/ComputeClientBaseCmdlet.cs b/src/ResourceManager/Compute/Commands.Compute/Common/ComputeClientBaseCmdlet.cs
index d26840fadf17..340bc49506d2 100644
--- a/src/ResourceManager/Compute/Commands.Compute/Common/ComputeClientBaseCmdlet.cs
+++ b/src/ResourceManager/Compute/Commands.Compute/Common/ComputeClientBaseCmdlet.cs
@@ -23,6 +23,11 @@ public abstract class ComputeClientBaseCmdlet : AzurePSCmdlet
{
protected const string VirtualMachineExtensionType = "Microsoft.Compute/virtualMachines/extensions";
+ protected override bool IsUsageMetricEnabled
+ {
+ get { return true; }
+ }
+
private ComputeClient computeClient;
public ComputeClient ComputeClient
@@ -54,18 +59,11 @@ protected void ExecuteClientAction(Action action)
{
try
{
- try
- {
- action();
- }
- catch (CloudException ex)
- {
- throw new ComputeCloudException(ex);
- }
+ action();
}
- catch (Exception ex)
+ catch (CloudException ex)
{
- WriteExceptionError(ex);
+ throw new ComputeCloudException(ex);
}
}
}
diff --git a/src/ResourceManager/Compute/Commands.Compute/VirtualMachine/Config/NewAzureVMConfigCommand.cs b/src/ResourceManager/Compute/Commands.Compute/VirtualMachine/Config/NewAzureVMConfigCommand.cs
index 2f88fa1fd386..25293d78aa91 100644
--- a/src/ResourceManager/Compute/Commands.Compute/VirtualMachine/Config/NewAzureVMConfigCommand.cs
+++ b/src/ResourceManager/Compute/Commands.Compute/VirtualMachine/Config/NewAzureVMConfigCommand.cs
@@ -51,6 +51,11 @@ public class NewAzureVMConfigCommand : AzurePSCmdlet
[ValidateNotNullOrEmpty]
public string AvailabilitySetId { get; set; }
+ protected override bool IsUsageMetricEnabled
+ {
+ get { return true; }
+ }
+
public override void ExecuteCmdlet()
{
var vm = new PSVirtualMachine