Skip to content

Commit 9c465b4

Browse files
committed
Merge pull request #84 from Azure/release-0.9.8
Release 0.9.8
2 parents 48edc24 + 93162be commit 9c465b4

File tree

84 files changed

+9432
-698
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+9432
-698
lines changed

setup/azurecmdfiles.wxi

Lines changed: 156 additions & 0 deletions
Large diffs are not rendered by default.

src/Common/Commands.Common/AzurePSCmdlet.cs

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,17 @@ public abstract class AzurePSCmdlet : PSCmdlet
3636
protected static AzureProfile _currentProfile = null;
3737
protected static AzurePSDataCollectionProfile _dataCollectionProfile = null;
3838

39+
protected AzurePSQoSEvent QosEvent;
40+
41+
protected virtual bool IsUsageMetricEnabled {
42+
get { return false; }
43+
}
44+
45+
protected virtual bool IsErrorMetricEnabled
46+
{
47+
get { return true; }
48+
}
49+
3950
[Parameter(Mandatory = false, HelpMessage = "In-memory profile.")]
4051
public AzureProfile Profile { get; set; }
4152

@@ -208,6 +219,22 @@ protected static AzurePSDataCollectionProfile GetDataCollectionProfile()
208219
return _dataCollectionProfile;
209220
}
210221

222+
/// <summary>
223+
/// Check whether the data collection is opted in from user
224+
/// </summary>
225+
/// <returns>true if allowed</returns>
226+
public static bool IsDataCollectionAllowed()
227+
{
228+
if (_dataCollectionProfile != null &&
229+
_dataCollectionProfile.EnableAzureDataCollection.HasValue &&
230+
_dataCollectionProfile.EnableAzureDataCollection.Value)
231+
{
232+
return true;
233+
}
234+
235+
return false;
236+
}
237+
211238
/// <summary>
212239
/// Save the current data collection profile Json data into the default file path
213240
/// </summary>
@@ -305,7 +332,7 @@ protected override void BeginProcessing()
305332
{
306333
InitializeProfile();
307334
PromptForDataCollectionProfileIfNotExists();
308-
335+
InitializeQosEvent();
309336
if (string.IsNullOrEmpty(ParameterSetName))
310337
{
311338
WriteDebugWithTimestamp(string.Format(Resources.BeginProcessingWithoutParameterSetLog, this.GetType().Name));
@@ -346,6 +373,7 @@ protected virtual void InitializeProfile()
346373
/// </summary>
347374
protected override void EndProcessing()
348375
{
376+
LogQosEvent();
349377
string message = string.Format(Resources.EndProcessingLog, this.GetType().Name);
350378
WriteDebugWithTimestamp(message);
351379

@@ -379,6 +407,13 @@ protected bool IsVerbose()
379407
public new void WriteError(ErrorRecord errorRecord)
380408
{
381409
FlushDebugMessages();
410+
if (QosEvent != null && errorRecord != null)
411+
{
412+
QosEvent.Exception = errorRecord.Exception;
413+
QosEvent.IsSuccess = false;
414+
LogQosEvent(true);
415+
}
416+
382417
base.WriteError(errorRecord);
383418
}
384419

@@ -506,6 +541,62 @@ private void FlushDebugMessages()
506541
}
507542
}
508543

544+
protected void InitializeQosEvent()
545+
{
546+
QosEvent = new AzurePSQoSEvent()
547+
{
548+
CmdletType = this.GetType().Name,
549+
IsSuccess = true,
550+
};
551+
552+
if (this.Profile != null && this.Profile.DefaultSubscription != null)
553+
{
554+
QosEvent.Uid = MetricHelper.GenerateSha256HashString(
555+
this.Profile.DefaultSubscription.Id.ToString());
556+
}
557+
else
558+
{
559+
QosEvent.Uid = "defaultid";
560+
}
561+
}
562+
563+
/// <summary>
564+
/// Invoke this method when the cmdlet is completed or terminated.
565+
/// </summary>
566+
protected void LogQosEvent(bool waitForMetricSending = false)
567+
{
568+
if (QosEvent == null)
569+
{
570+
return;
571+
}
572+
573+
QosEvent.FinishQosEvent();
574+
575+
if (!IsUsageMetricEnabled && (!IsErrorMetricEnabled || QosEvent.IsSuccess))
576+
{
577+
return;
578+
}
579+
580+
if (!IsDataCollectionAllowed())
581+
{
582+
return;
583+
}
584+
585+
WriteDebug(QosEvent.ToString());
586+
587+
try
588+
{
589+
MetricHelper.LogQoSEvent(QosEvent, IsUsageMetricEnabled, IsErrorMetricEnabled);
590+
MetricHelper.FlushMetric(waitForMetricSending);
591+
WriteDebug("Finish sending metric.");
592+
}
593+
catch (Exception e)
594+
{
595+
//Swallow error from Application Insights event collection.
596+
WriteWarning(e.ToString());
597+
}
598+
}
599+
509600
/// <summary>
510601
/// Asks for confirmation before executing the action.
511602
/// </summary>
@@ -516,10 +607,19 @@ private void FlushDebugMessages()
516607
/// <param name="action">The action code</param>
517608
protected void ConfirmAction(bool force, string actionMessage, string processMessage, string target, Action action)
518609
{
610+
if (QosEvent != null)
611+
{
612+
QosEvent.PauseQoSTimer();
613+
}
614+
519615
if (force || ShouldContinue(actionMessage, ""))
520616
{
521617
if (ShouldProcess(target, processMessage))
522-
{
618+
{
619+
if (QosEvent != null)
620+
{
621+
QosEvent.ResumeQosTimer();
622+
}
523623
action();
524624
}
525625
}

src/Common/Commands.Common/Commands.Common.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
1616
<RestorePackages>true</RestorePackages>
1717
<CodeAnalysisAdditionalOptions>/assemblyCompareMode:StrongNameIgnoringVersion</CodeAnalysisAdditionalOptions>
18+
<NuGetPackageImportStamp>06e19c11</NuGetPackageImportStamp>
1819
</PropertyGroup>
1920
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
2021
<DebugSymbols>true</DebugSymbols>
@@ -54,6 +55,10 @@
5455
<SpecificVersion>False</SpecificVersion>
5556
<HintPath>..\..\packages\Hyak.Common.1.0.2\lib\portable-net403+win+wpa81\Hyak.Common.dll</HintPath>
5657
</Reference>
58+
<Reference Include="Microsoft.ApplicationInsights, Version=1.1.0.1899, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
59+
<HintPath>..\..\packages\Microsoft.ApplicationInsights.1.1.1-beta\lib\net45\Microsoft.ApplicationInsights.dll</HintPath>
60+
<Private>True</Private>
61+
</Reference>
5762
<Reference Include="Microsoft.Azure.Common">
5863
<SpecificVersion>False</SpecificVersion>
5964
<HintPath>..\..\packages\Microsoft.Azure.Common.2.1.0\lib\net45\Microsoft.Azure.Common.dll</HintPath>
@@ -147,6 +152,7 @@
147152
<DesignTime>True</DesignTime>
148153
<DependentUpon>Resources.resx</DependentUpon>
149154
</Compile>
155+
<Compile Include="MetricHelper.cs" />
150156
<Compile Include="SecureStringExtensions.cs" />
151157
<Compile Include="ConversionUtilities.cs" />
152158
<Compile Include="DebugStreamTraceListener.cs" />
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Security.Cryptography;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using Microsoft.ApplicationInsights;
8+
using Microsoft.ApplicationInsights.Channel;
9+
using Microsoft.ApplicationInsights.DataContracts;
10+
using Microsoft.ApplicationInsights.Extensibility;
11+
using Microsoft.WindowsAzure.Commands.Utilities.Common;
12+
13+
namespace Microsoft.WindowsAzure.Commands.Common
14+
{
15+
public static class MetricHelper
16+
{
17+
private const int FlushTimeoutInMilli = 5000;
18+
private static readonly TelemetryClient TelemetryClient;
19+
20+
static MetricHelper()
21+
{
22+
TelemetryClient = new TelemetryClient();
23+
// TODO: InstrumentationKey shall be injected in build server
24+
TelemetryClient.InstrumentationKey = "7df6ff70-8353-4672-80d6-568517fed090";
25+
// Disable IP collection
26+
TelemetryClient.Context.Location.Ip = "0.0.0.0";
27+
28+
if (TestMockSupport.RunningMocked)
29+
{
30+
TelemetryConfiguration.Active.DisableTelemetry = true;
31+
}
32+
}
33+
34+
public static void LogQoSEvent(AzurePSQoSEvent qos, bool isUsageMetricEnabled, bool isErrorMetricEnabled)
35+
{
36+
if (!IsMetricTermAccepted())
37+
{
38+
return;
39+
}
40+
41+
if (isUsageMetricEnabled)
42+
{
43+
LogUsageEvent(qos);
44+
}
45+
46+
if (isErrorMetricEnabled && qos.Exception != null)
47+
{
48+
LogExceptionEvent(qos);
49+
}
50+
}
51+
52+
private static void LogUsageEvent(AzurePSQoSEvent qos)
53+
{
54+
var tcEvent = new RequestTelemetry(qos.CmdletType, qos.StartTime, qos.Duration, string.Empty, qos.IsSuccess);
55+
tcEvent.Context.User.Id = qos.Uid;
56+
tcEvent.Context.User.UserAgent = AzurePowerShell.UserAgentValue.ToString();
57+
tcEvent.Context.Device.OperatingSystem = Environment.OSVersion.VersionString;
58+
59+
TelemetryClient.TrackRequest(tcEvent);
60+
}
61+
62+
private static void LogExceptionEvent(AzurePSQoSEvent qos)
63+
{
64+
//Log as custome event to exclude actual exception message
65+
var tcEvent = new EventTelemetry("CmdletError");
66+
tcEvent.Properties.Add("ExceptionType", qos.Exception.GetType().FullName);
67+
tcEvent.Properties.Add("StackTrace", qos.Exception.StackTrace);
68+
if (qos.Exception.InnerException != null)
69+
{
70+
tcEvent.Properties.Add("InnerExceptionType", qos.Exception.InnerException.GetType().FullName);
71+
tcEvent.Properties.Add("InnerStackTrace", qos.Exception.InnerException.StackTrace);
72+
}
73+
74+
tcEvent.Context.User.Id = qos.Uid;
75+
tcEvent.Properties.Add("CmdletType", qos.CmdletType);
76+
77+
TelemetryClient.TrackEvent(tcEvent);
78+
}
79+
80+
public static bool IsMetricTermAccepted()
81+
{
82+
return AzurePSCmdlet.IsDataCollectionAllowed();
83+
}
84+
85+
public static void FlushMetric(bool waitForMetricSending)
86+
{
87+
if (!IsMetricTermAccepted())
88+
{
89+
return;
90+
}
91+
92+
var flushTask = Task.Run(() => FlushAi());
93+
if (waitForMetricSending)
94+
{
95+
Task.WaitAll(new[] { flushTask }, FlushTimeoutInMilli);
96+
}
97+
}
98+
99+
private static void FlushAi()
100+
{
101+
try
102+
{
103+
TelemetryClient.Flush();
104+
}
105+
catch
106+
{
107+
// ignored
108+
}
109+
}
110+
111+
/// <summary>
112+
/// Gereate a SHA256 Hash string from the originInput.
113+
/// </summary>
114+
/// <param name="originInput"></param>
115+
/// <returns></returns>
116+
public static string GenerateSha256HashString(string originInput)
117+
{
118+
SHA256 sha256 = SHA256.Create();
119+
var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(originInput));
120+
return Encoding.UTF8.GetString(bytes);
121+
}
122+
}
123+
}
124+
125+
public class AzurePSQoSEvent
126+
{
127+
private readonly Stopwatch _timer;
128+
129+
public DateTimeOffset StartTime { get; set; }
130+
public TimeSpan Duration { get; set; }
131+
public bool IsSuccess { get; set; }
132+
public string CmdletType { get; set; }
133+
public Exception Exception { get; set; }
134+
public string Uid { get; set; }
135+
136+
public AzurePSQoSEvent()
137+
{
138+
StartTime = DateTimeOffset.Now;
139+
_timer = new Stopwatch();
140+
_timer.Start();
141+
}
142+
143+
public void PauseQoSTimer()
144+
{
145+
_timer.Stop();
146+
}
147+
148+
public void ResumeQosTimer()
149+
{
150+
_timer.Start();
151+
}
152+
153+
public void FinishQosEvent()
154+
{
155+
_timer.Stop();
156+
Duration = _timer.Elapsed;
157+
}
158+
159+
public override string ToString()
160+
{
161+
return string.Format(
162+
"AzureQoSEvent: CmdletType - {0}; IsSuccess - {1}; Duration - {2}; Exception - {3};",
163+
CmdletType, IsSuccess, Duration, Exception);
164+
}
165+
}

src/Common/Commands.Common/packages.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<packages>
33
<package id="Hyak.Common" version="1.0.2" targetFramework="net45" />
4+
<package id="Microsoft.ApplicationInsights" version="1.1.1-beta" targetFramework="net45" />
45
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.2" targetFramework="net45" />
56
<package id="Microsoft.Azure.Common" version="2.1.0" targetFramework="net45" />
67
<package id="Microsoft.Azure.Common.Authentication" version="1.1.3-preview" targetFramework="net45" />

src/Common/Commands.ScenarioTest/Commands.ScenarioTest.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,10 @@
518518
<Project>{58a78f29-8c0c-4a5e-893e-3953c0f29c8a}</Project>
519519
<Name>Commands.ServiceManagement.Test</Name>
520520
</ProjectReference>
521+
<ProjectReference Include="..\..\ServiceManagement\RemoteApp\Commands.RemoteApp\Commands.RemoteApp.csproj">
522+
<Project>{492d2af2-950b-4f2e-8079-8794305313fd}</Project>
523+
<Name>Commands.RemoteApp</Name>
524+
</ProjectReference>
521525
<ProjectReference Include="..\..\ServiceManagement\Services\Commands.Test.Utilities\Commands.Test.Utilities.csproj">
522526
<Project>{bc420543-c04e-4bf3-96e1-cd81b823bdd7}</Project>
523527
<Name>Commands.Test.Utilities</Name>

0 commit comments

Comments
 (0)