Skip to content

Commit e3c0f90

Browse files
authored
Collect environment information in telemetry (Azure#22074)
* Record InstallationId * Fixed one unit test after the buffer for the telememetry is changed. * Collect the host env * Fix getting mac address on Linux. - On Linux, the first up network interface is the loopback device which has the same physicial address on all machines. That is not a good indicator about telling apart different environment. In the fix, we only check the physcial address of the first up network interface which is an ethernet or a wifi device. * Update unit test and fix potential data corruption in telemetry - Update the unit tests after all the previous changes. - We now always check if we have enough buffer to add info about displayed suggestions and accepted suggestion. This may fix potential data corruption in the telemetry event. The issue is that the telemetry property value is still truncated. With this change, we always check to make sure there is enough buffer before adding new info. So we should not see the truncation anymore. * clen up code * Improve the way to collect Az version - We check both Az and AzPreview for the Az version.
1 parent 4ce771f commit e3c0f90

File tree

8 files changed

+166
-58
lines changed

8 files changed

+166
-58
lines changed

tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorTelemetryTests.cs

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,9 @@
1616
using Microsoft.Azure.PowerShell.Tools.AzPredictor.Test.Mocks;
1717
using System;
1818
using System.Collections.Generic;
19-
using System.Globalization;
2019
using System.Linq;
2120
using System.Management.Automation.Subsystem.Prediction;
2221
using System.Text.Json;
23-
using System.Text.Json.Serialization;
2422
using System.Threading;
2523
using System.Threading.Tasks;
2624
using Xunit;
@@ -922,7 +920,7 @@ public void VerifyAggregationDataSplitAtGetSuggestion()
922920

923921
for (int i = 0; i < expectedTelemetryCount; ++i)
924922
{
925-
// Call the methods a few times to make sure the telemetry data is less than 8092 but the next such call will
923+
// Call the methods a few times to make sure the telemetry data is less than AzPredictorTelemetryClient.MaxPropertyValueSizeWithBuffer but the next such call will
926924
// make it larger than it.
927925
var predictionContext = PredictionContext.Create($"Clear-Content -Path '*' -Filter '{i}.log'");
928926
var _ = azPredictor.GetSuggestion(MockObjects.PredictionClient, predictionContext, CancellationToken.None);
@@ -961,7 +959,7 @@ public void VerifyAggregationDataSplitAtGetSuggestion()
961959
/// Verifies that the Suggestion field is divided into events when SuggestionDisplayedTelemetryData is added.
962960
/// </summary>
963961
[Fact]
964-
public void VerifyAggregationDataSplitAtAcceptSuggestion()
962+
public void VerifyAggregationDataSplitAtDisplaySuggestion()
965963
{
966964
var expectedTelemetryCount = 64;
967965
var expectedSuggestionSessionInFirstBatch = expectedTelemetryCount;
@@ -971,15 +969,14 @@ public void VerifyAggregationDataSplitAtAcceptSuggestion()
971969

972970
for (int i = 0; i < expectedTelemetryCount - 1; ++i)
973971
{
974-
// Call the methods a few times to make sure the telemetry data is less than 8092 but the next such call will
975-
// make it larger than it.
972+
// Call the methods a few times to make sure the telemetry data is less than AzPredictorTelemetryClient.MaxPropertyValueSizeWithBuffer.
976973
predictionContext = PredictionContext.Create($"Clear-Variable -Name my* -Scop");
977974
var _ = azPredictor.GetSuggestion(MockObjects.PredictionClient, predictionContext, CancellationToken.None);
978975
}
979976

980-
// It's easier to pad the cached telemetry event with the command without parameters. With parameters, it's more likely to exceed the
981-
// buffer size.
977+
// This call just to make sure that the size is close enough to AzPredictorTelemetryClient.MaxPropertyValueSizeWithBuffer.
982978
predictionContext = PredictionContext.Create("Get-ChildIte");
979+
983980
suggestionPackage = azPredictor.GetSuggestion(MockObjects.PredictionClient, predictionContext, CancellationToken.None);
984981

985982
VerifyTelemetryDispatchCount(expectedTelemetryCount, telemetryClient);
@@ -991,9 +988,8 @@ public void VerifyAggregationDataSplitAtAcceptSuggestion()
991988
telemetryClient.ExceptedTelemetryDispatchCount = expectedTelemetryCount;
992989

993990
// OnSuggestionDisplayed makes the property value size larger than AzPredictorTelemetryClient.MaxPropertyValueSizeWithBuffer.
994-
// But the additional data from SuggestionDisplayedTelemetryData is small so we are still less than the maximum application insight property value size.
995991
azPredictor.OnSuggestionDisplayed(MockObjects.PredictionClient, suggestionPackage.Session.Value, 1);
996-
// We'll send the first batch contains the found suggestions and displayed info when we process SuggestionAcceptedTelemetryData.
992+
// We'll send the first batch that contains the found suggestions when we process SuggestionDisplayedTelemetryData.
997993
azPredictor.OnSuggestionAccepted(MockObjects.PredictionClient, suggestionPackage.Session.Value, "Get-ChildItem");
998994

999995
VerifyTelemetryDispatchCount(expectedTelemetryCount, telemetryClient);
@@ -1004,10 +1000,8 @@ public void VerifyAggregationDataSplitAtAcceptSuggestion()
10041000
Assert.Equal(expectedSuggestionSessionInFirstBatch, suggestionSessions.Count());
10051001
Assert.True(suggestionSessions.All((s) => s.ContainsKey(GetSuggestionTelemetryData.PropertyNameFound) && s.ContainsKey(GetSuggestionTelemetryData.PropertyNameUserInput)));
10061002
Assert.True(suggestionSessions.All((s) => !s.ContainsKey(SuggestionAcceptedTelemetryData.PropertyNameAccepted)));
1007-
Assert.True(suggestionSessions.SkipLast(1).All((s) => !s.ContainsKey(SuggestionDisplayedTelemetryData.PropertyNameDisplayed) && !s.ContainsKey(GetSuggestionTelemetryData.PropertyNameSuggestionSessionId)));
1003+
Assert.True(suggestionSessions.All((s) => !s.ContainsKey(SuggestionDisplayedTelemetryData.PropertyNameDisplayed)));
10081004
Assert.Equal(suggestionPackage.Session.Value, ((JsonElement)suggestionSessions.Last()[GetSuggestionTelemetryData.PropertyNameSuggestionSessionId]).GetUInt32());
1009-
Assert.Equal(1, ((JsonElement)suggestionSessions.Last()[SuggestionDisplayedTelemetryData.PropertyNameDisplayed])[0].GetInt32());
1010-
Assert.Equal(1, ((JsonElement)suggestionSessions.Last()[SuggestionDisplayedTelemetryData.PropertyNameDisplayed])[1].GetInt32());
10111005

10121006
recordedTelemetry = telemetryClient.RecordedTelemetry[1];
10131007
suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(recordedTelemetry.Properties[GetSuggestionTelemetryData.PropertyNamePrediction]);
@@ -1016,57 +1010,55 @@ public void VerifyAggregationDataSplitAtAcceptSuggestion()
10161010
Assert.False(suggestionSessions[0].ContainsKey(GetSuggestionTelemetryData.PropertyNameUserInput));
10171011
Assert.False(suggestionSessions[0].ContainsKey(GetSuggestionTelemetryData.PropertyNameIsCancelled));
10181012
Assert.Equal(suggestionPackage.Session.Value, ((JsonElement)suggestionSessions[0][GetSuggestionTelemetryData.PropertyNameSuggestionSessionId]).GetUInt32());
1013+
Assert.Equal(1, ((JsonElement)suggestionSessions[0][SuggestionDisplayedTelemetryData.PropertyNameDisplayed])[0].GetInt32());
1014+
Assert.Equal(1, ((JsonElement)suggestionSessions[0][SuggestionDisplayedTelemetryData.PropertyNameDisplayed])[1].GetInt32());
10191015
Assert.Equal("Get-ChildItem", ((JsonElement)suggestionSessions[0][SuggestionAcceptedTelemetryData.PropertyNameAccepted]).GetString());
10201016
}
10211017

10221018
/// <summary>
1023-
/// Verifies that the Suggestion field is divided into events when history is added.
1019+
/// Verifies that the Suggestion field is divided into events when SuggestionAcceptedTelemetryData is added.
10241020
/// </summary>
10251021
[Fact]
1026-
public void VerifyAggregationDataSplitAtCommandHistory()
1022+
public void VerifyAggregationDataSplitAtAcceptSuggestion()
10271023
{
10281024
var expectedTelemetryCount = 64;
1029-
var expectedSuggestionSessionInFirstBatch = expectedTelemetryCount;
1025+
var expectedSuggestionSessionInFirstBatch = expectedTelemetryCount - 1; // The display info is in the same suggstion session as the GetSuggestionTelemetryData
10301026
var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount, flushTelemetry: false);
10311027
PredictionContext predictionContext = default;
10321028
SuggestionPackage suggestionPackage = default;
10331029

10341030
for (int i = 0; i < expectedTelemetryCount - 1; ++i)
10351031
{
1036-
// Call the methods a few times to make sure the telemetry data is less than 8092 but the CommandAccepted and CommandExecuted events
1037-
// make it larger than it.
1038-
predictionContext = PredictionContext.Create($"Clear-Variable -Name my* -Scop");
1039-
var _ = suggestionPackage = azPredictor.GetSuggestion(MockObjects.PredictionClient, predictionContext, CancellationToken.None);
1032+
// Call the methods a few times to make sure the telemetry data is less than AzPredictorTelemetryClient.MaxPropertyValueSizeWithBuffer.
1033+
predictionContext = PredictionContext.Create($"Clear-Variable -Name my* -Scope");
1034+
suggestionPackage = azPredictor.GetSuggestion(MockObjects.PredictionClient, predictionContext, CancellationToken.None);
10401035
}
10411036

1042-
// It's easier to pad the cached telemetry event with the command without parameters. With parameters, it's more likely to exceed the
1043-
// buffer size.
1044-
predictionContext = PredictionContext.Create("Get-ChildIte");
1045-
suggestionPackage = azPredictor.GetSuggestion(MockObjects.PredictionClient, predictionContext, CancellationToken.None);
1037+
// With this call, the size is still less than AzPredictorTelemetryClient.MaxPropertyValueSizeWithBuffer.
1038+
azPredictor.OnSuggestionDisplayed(MockObjects.PredictionClient, suggestionPackage.Session.Value, 1);
10461039

10471040
VerifyTelemetryDispatchCount(expectedTelemetryCount, telemetryClient);
10481041
Assert.True(telemetryClient.RecordedAggregatedData.EstimateSuggestionSessionSize < AzPredictorTelemetryClient.MaxPropertyValueSizeWithBuffer);
10491042

1050-
expectedTelemetryCount = 3;
1043+
expectedTelemetryCount = 1;
10511044
var expectedSuggestionSessionInSecondBatch = 1;
10521045
telemetryClient.ResetWaitingTasks();
10531046
telemetryClient.ExceptedTelemetryDispatchCount = expectedTelemetryCount;
10541047

1055-
// OnSuggestionDisplayed makes the property value size larger than AzPredictorTelemetryClient.MaxPropertyValueSizeWithBuffer.
1056-
// But the additional data from SuggestionDisplayedTelemetryData is small so we are still less than the maximum application insight property value size.
1057-
azPredictor.OnSuggestionDisplayed(MockObjects.PredictionClient, suggestionPackage.Session.Value, 1);
1048+
// With this call, the size will exceed AzPredictorTelemetryClient.MaxPropertyValueSizeWithBuffer.
10581049
// We'll send the first batch contains the found suggestions and displayed info when we process SuggestionAcceptedTelemetryData.
1059-
azPredictor.OnCommandLineAccepted(MockObjects.PredictionClient, new string[] { "Get-ChildItem" });
1060-
azPredictor.OnCommandLineExecuted(MockObjects.PredictionClient, "Get-ChildItem", success: true);
1050+
var acceptedSuggestion = "Clear-Variable -Name my* -Scope Global";
1051+
azPredictor.OnSuggestionAccepted(MockObjects.PredictionClient, suggestionPackage.Session.Value, acceptedSuggestion);
10611052

10621053
VerifyTelemetryDispatchCount(expectedTelemetryCount, telemetryClient);
1054+
telemetryClient.FlushTelemetry();
10631055

10641056
var recordedTelemetry = telemetryClient.RecordedTelemetry[0];
10651057
var suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(recordedTelemetry.Properties[GetSuggestionTelemetryData.PropertyNamePrediction]);
10661058
Assert.Equal(expectedSuggestionSessionInFirstBatch, suggestionSessions.Count());
10671059
Assert.True(suggestionSessions.All((s) => s.ContainsKey(GetSuggestionTelemetryData.PropertyNameFound) && s.ContainsKey(GetSuggestionTelemetryData.PropertyNameUserInput)));
10681060
Assert.True(suggestionSessions.All((s) => !s.ContainsKey(SuggestionAcceptedTelemetryData.PropertyNameAccepted)));
1069-
Assert.True(suggestionSessions.SkipLast(1).All((s) => !s.ContainsKey(SuggestionDisplayedTelemetryData.PropertyNameDisplayed)));
1061+
Assert.True(suggestionSessions.SkipLast(1).All((s) => !s.ContainsKey(SuggestionDisplayedTelemetryData.PropertyNameDisplayed) && !s.ContainsKey(GetSuggestionTelemetryData.PropertyNameSuggestionSessionId)));
10701062
Assert.Equal(suggestionPackage.Session.Value, ((JsonElement)suggestionSessions.Last()[GetSuggestionTelemetryData.PropertyNameSuggestionSessionId]).GetUInt32());
10711063
Assert.Equal(1, ((JsonElement)suggestionSessions.Last()[SuggestionDisplayedTelemetryData.PropertyNameDisplayed])[0].GetInt32());
10721064
Assert.Equal(1, ((JsonElement)suggestionSessions.Last()[SuggestionDisplayedTelemetryData.PropertyNameDisplayed])[1].GetInt32());
@@ -1078,7 +1070,7 @@ public void VerifyAggregationDataSplitAtCommandHistory()
10781070
Assert.False(suggestionSessions[0].ContainsKey(GetSuggestionTelemetryData.PropertyNameUserInput));
10791071
Assert.False(suggestionSessions[0].ContainsKey(GetSuggestionTelemetryData.PropertyNameIsCancelled));
10801072
Assert.Equal(suggestionPackage.Session.Value, ((JsonElement)suggestionSessions[0][GetSuggestionTelemetryData.PropertyNameSuggestionSessionId]).GetUInt32());
1081-
Assert.Equal("Get-ChildItem", recordedTelemetry.Properties[HistoryTelemetryData.PropertyNameCommand]);
1073+
Assert.Equal(acceptedSuggestion, ((JsonElement)suggestionSessions[0][SuggestionAcceptedTelemetryData.PropertyNameAccepted]).GetString());
10821074
}
10831075

10841076
private (AzPredictor, MockAzPredictorTelemetryClient) CreateTestObjects(bool throwException, int expectedTelemetryEvent, bool flushTelemetry = true)

tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzContext.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,26 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Test.Mocks
1919
{
2020
sealed class MockAzContext : IAzContext
2121
{
22+
public string InstallationId => Guid.Empty.ToString();
23+
2224
public string HashUserId => "TestUserId";
2325

2426
public string MacAddress => "TestMacAddress";
2527

2628
public string OSVersion => "TestOSVersion";
2729

28-
public Version PowerShellVersion => Version.Parse("0.0.0.0");
30+
public Version PowerShellVersion => new();
2931

30-
public Version ModuleVersion => Version.Parse("0.0.0.0");
32+
public Version ModuleVersion => new();
3133

32-
public Version AzVersion => Version.Parse("0.0.0.0");
34+
public Version AzVersion => new();
3335

3436
public int Cohort { get; set; } = -1;
3537

3638
public bool IsInternal => true;
3739

40+
public string HostEnvironment => "TestEnvironment";
41+
3842
public Runspace DefaultRunspace => default;
3943

4044
public void UpdateContext()
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using Newtonsoft.Json;
16+
using System;
17+
using System.IO;
18+
19+
// This is copied from src/Accounts/Authentication/Modules/AzCliProfileInfo.cs
20+
21+
namespace Microsoft.Azure.PowerShell.Tools.AzPredictor
22+
{
23+
/// <summary>
24+
/// this class defines installation id field in Azure CLI context. This information is shared between Azure CLI and Azure PowerShell
25+
/// </summary>
26+
internal class AzCLIProfileInfo
27+
{
28+
[JsonProperty(PropertyName = "installationId")]
29+
internal string installationId { get; set; }
30+
31+
public static readonly string AzCLIProfileFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".Azure", "AzureProfile.json");
32+
}
33+
}

0 commit comments

Comments
 (0)