Merge pull request #2145 from dotnet/lakshanf/issue2066/telemetry
Lakshanf/issue2066/telemetry
This commit is contained in:
commit
965547bd8a
13 changed files with 259 additions and 48 deletions
|
@ -14,8 +14,8 @@ namespace Microsoft.DotNet.Cli.Utils
|
||||||
public static readonly string AnsiPassThru = Prefix + "ANSI_PASS_THRU";
|
public static readonly string AnsiPassThru = Prefix + "ANSI_PASS_THRU";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Lazy<bool> _verbose = new Lazy<bool>(() => GetBool(Variables.Verbose));
|
private static Lazy<bool> _verbose = new Lazy<bool>(() => Env.GetEnvironmentVariableAsBool(Variables.Verbose));
|
||||||
private static Lazy<bool> _ansiPassThru = new Lazy<bool>(() => GetBool(Variables.AnsiPassThru));
|
private static Lazy<bool> _ansiPassThru = new Lazy<bool>(() => Env.GetEnvironmentVariableAsBool(Variables.AnsiPassThru));
|
||||||
|
|
||||||
public static bool IsVerbose()
|
public static bool IsVerbose()
|
||||||
{
|
{
|
||||||
|
@ -26,28 +26,5 @@ namespace Microsoft.DotNet.Cli.Utils
|
||||||
{
|
{
|
||||||
return _ansiPassThru.Value;
|
return _ansiPassThru.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool GetBool(string name, bool defaultValue = false)
|
|
||||||
{
|
|
||||||
var str = Environment.GetEnvironmentVariable(name);
|
|
||||||
if (string.IsNullOrEmpty(str))
|
|
||||||
{
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (str.ToLowerInvariant())
|
|
||||||
{
|
|
||||||
case "true":
|
|
||||||
case "1":
|
|
||||||
case "yes":
|
|
||||||
return true;
|
|
||||||
case "false":
|
|
||||||
case "0":
|
|
||||||
case "no":
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,5 +33,10 @@ namespace Microsoft.DotNet.Cli.Utils
|
||||||
{
|
{
|
||||||
return _environment.GetCommandPathFromRootPath(rootPath, commandName, extensions);
|
return _environment.GetCommandPathFromRootPath(rootPath, commandName, extensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool GetEnvironmentVariableAsBool(string name, bool defaultValue = false)
|
||||||
|
{
|
||||||
|
return _environment.GetEnvironmentVariableAsBool(name, defaultValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,5 +93,29 @@ namespace Microsoft.DotNet.Cli.Utils
|
||||||
|
|
||||||
return GetCommandPathFromRootPath(rootPath, commandName, extensionsArr);
|
return GetCommandPathFromRootPath(rootPath, commandName, extensionsArr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool GetEnvironmentVariableAsBool(string name, bool defaultValue)
|
||||||
|
{
|
||||||
|
var str = Environment.GetEnvironmentVariable(name);
|
||||||
|
if (string.IsNullOrEmpty(str))
|
||||||
|
{
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (str.ToLowerInvariant())
|
||||||
|
{
|
||||||
|
case "true":
|
||||||
|
case "1":
|
||||||
|
case "yes":
|
||||||
|
return true;
|
||||||
|
case "false":
|
||||||
|
case "0":
|
||||||
|
case "no":
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,5 +16,7 @@ namespace Microsoft.DotNet.Cli.Utils
|
||||||
string GetCommandPathFromRootPath(string rootPath, string commandName, params string[] extensions);
|
string GetCommandPathFromRootPath(string rootPath, string commandName, params string[] extensions);
|
||||||
|
|
||||||
string GetCommandPathFromRootPath(string rootPath, string commandName, IEnumerable<string> extensions);
|
string GetCommandPathFromRootPath(string rootPath, string commandName, IEnumerable<string> extensions);
|
||||||
|
|
||||||
|
bool GetEnvironmentVariableAsBool(string name, bool defaultValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
src/Microsoft.DotNet.Cli.Utils/ITelemetry.cs
Normal file
13
src/Microsoft.DotNet.Cli.Utils/ITelemetry.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.Cli.Utils
|
||||||
|
{
|
||||||
|
public interface ITelemetry
|
||||||
|
{
|
||||||
|
void TrackEvent(string eventName, IDictionary<string, string> properties, IDictionary<string, double> measurements);
|
||||||
|
}
|
||||||
|
}
|
17
src/Microsoft.DotNet.Cli.Utils/Product.cs
Normal file
17
src/Microsoft.DotNet.Cli.Utils/Product.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.Cli.Utils
|
||||||
|
{
|
||||||
|
public class Product
|
||||||
|
{
|
||||||
|
public static readonly string LongName = ".NET Command Line Tools";
|
||||||
|
public static readonly string Version = GetProductVersion();
|
||||||
|
|
||||||
|
private static string GetProductVersion()
|
||||||
|
{
|
||||||
|
var attr = typeof(Product).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
|
||||||
|
return attr?.InformationalVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
125
src/Microsoft.DotNet.Cli.Utils/Telemetry.cs
Normal file
125
src/Microsoft.DotNet.Cli.Utils/Telemetry.cs
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.ApplicationInsights;
|
||||||
|
using Microsoft.Extensions.PlatformAbstractions;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.Cli.Utils
|
||||||
|
{
|
||||||
|
public class Telemetry : ITelemetry
|
||||||
|
{
|
||||||
|
private bool _isInitialized = false;
|
||||||
|
private TelemetryClient _client = null;
|
||||||
|
|
||||||
|
private Dictionary<string, string> _commonProperties = null;
|
||||||
|
private Dictionary<string, double> _commonMeasurements = null;
|
||||||
|
|
||||||
|
private const string InstrumentationKey = "74cc1c9e-3e6e-4d05-b3fc-dde9101d0254";
|
||||||
|
private const string TelemetryOptout = "DOTNET_CLI_TELEMETRY_OPTOUT";
|
||||||
|
private const string OSVersion = "OS Version";
|
||||||
|
private const string OSPlatform = "OS Platform";
|
||||||
|
private const string RuntimeId = "Runtime Id";
|
||||||
|
private const string ProductVersion = "Product Version";
|
||||||
|
|
||||||
|
public Telemetry()
|
||||||
|
{
|
||||||
|
bool optout = Env.GetEnvironmentVariableAsBool(TelemetryOptout);
|
||||||
|
|
||||||
|
if (optout)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_client = new TelemetryClient();
|
||||||
|
_client.InstrumentationKey = InstrumentationKey;
|
||||||
|
_client.Context.Session.Id = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
var runtimeEnvironment = PlatformServices.Default.Runtime;
|
||||||
|
_client.Context.Device.OperatingSystem = runtimeEnvironment.OperatingSystem;
|
||||||
|
|
||||||
|
_commonProperties = new Dictionary<string, string>();
|
||||||
|
_commonProperties.Add(OSVersion, runtimeEnvironment.OperatingSystemVersion);
|
||||||
|
_commonProperties.Add(OSPlatform, runtimeEnvironment.OperatingSystemPlatform.ToString());
|
||||||
|
_commonProperties.Add(RuntimeId, runtimeEnvironment.GetRuntimeIdentifier());
|
||||||
|
_commonProperties.Add(ProductVersion, Product.Version);
|
||||||
|
_commonMeasurements = new Dictionary<string, double>();
|
||||||
|
|
||||||
|
_isInitialized = true;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// we dont want to fail the tool if telemetry fais. We should be able to detect abnormalities from data
|
||||||
|
// at the server end
|
||||||
|
Debug.Fail("Exception during telemetry initialization");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TrackEvent(string eventName, IDictionary<string, string> properties, IDictionary<string, double> measurements)
|
||||||
|
{
|
||||||
|
if (!_isInitialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<string, double> eventMeasurements = GetEventMeasures(measurements);
|
||||||
|
Dictionary<string, string> eventProperties = GetEventProperties(properties);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_client.TrackEvent(eventName, eventProperties, eventMeasurements);
|
||||||
|
_client.Flush();
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Debug.Fail("Exception during TrackEvent");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Dictionary<string, double> GetEventMeasures(IDictionary<string, double> measurements)
|
||||||
|
{
|
||||||
|
Dictionary<string, double> eventMeasurements = new Dictionary<string, double>(_commonMeasurements);
|
||||||
|
if (measurements != null)
|
||||||
|
{
|
||||||
|
foreach (var measurement in measurements)
|
||||||
|
{
|
||||||
|
if (eventMeasurements.ContainsKey(measurement.Key))
|
||||||
|
{
|
||||||
|
eventMeasurements[measurement.Key] = measurement.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
eventMeasurements.Add(measurement.Key, measurement.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return eventMeasurements;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<string, string> GetEventProperties(IDictionary<string, string> properties)
|
||||||
|
{
|
||||||
|
if (properties != null)
|
||||||
|
{
|
||||||
|
var eventProperties = new Dictionary<string, string>(_commonProperties);
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
if (eventProperties.ContainsKey(property.Key))
|
||||||
|
{
|
||||||
|
eventProperties[property.Key] = property.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
eventProperties.Add(property.Key, property.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return eventProperties;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _commonProperties;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@
|
||||||
"warningsAsErrors": true
|
"warningsAsErrors": true
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"Microsoft.ApplicationInsights": "2.0.0-rc1",
|
||||||
"Microsoft.DotNet.ProjectModel": "1.0.0-*",
|
"Microsoft.DotNet.ProjectModel": "1.0.0-*",
|
||||||
"Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-20100",
|
"Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-20100",
|
||||||
"NuGet.Versioning": "3.5.0-beta-1130",
|
"NuGet.Versioning": "3.5.0-beta-1130",
|
||||||
|
|
|
@ -26,14 +26,13 @@ namespace Microsoft.DotNet.Cli
|
||||||
{
|
{
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
|
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
DebugHelper.HandleDebugSwitch(ref args);
|
DebugHelper.HandleDebugSwitch(ref args);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return ProcessArgs(args);
|
return Program.ProcessArgs(args, new Telemetry());
|
||||||
}
|
}
|
||||||
catch (CommandUnknownException e)
|
catch (CommandUnknownException e)
|
||||||
{
|
{
|
||||||
|
@ -44,7 +43,7 @@ namespace Microsoft.DotNet.Cli
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int ProcessArgs(string[] args)
|
internal static int ProcessArgs(string[] args, ITelemetry telemetryClient)
|
||||||
{
|
{
|
||||||
// CommandLineApplication is a bit restrictive, so we parse things ourselves here. Individual apps should use CLA.
|
// CommandLineApplication is a bit restrictive, so we parse things ourselves here. Individual apps should use CLA.
|
||||||
|
|
||||||
|
@ -117,22 +116,36 @@ namespace Microsoft.DotNet.Cli
|
||||||
["test"] = TestCommand.Run
|
["test"] = TestCommand.Run
|
||||||
};
|
};
|
||||||
|
|
||||||
|
int exitCode;
|
||||||
Func<string[], int> builtIn;
|
Func<string[], int> builtIn;
|
||||||
if (builtIns.TryGetValue(command, out builtIn))
|
if (builtIns.TryGetValue(command, out builtIn))
|
||||||
{
|
{
|
||||||
return builtIn(appArgs.ToArray());
|
exitCode = builtIn(appArgs.ToArray());
|
||||||
}
|
}
|
||||||
|
else
|
||||||
return Command.Create("dotnet-" + command, appArgs, FrameworkConstants.CommonFrameworks.NetStandardApp15)
|
{
|
||||||
|
CommandResult result = Command.Create("dotnet-" + command, appArgs, FrameworkConstants.CommonFrameworks.NetStandardApp15)
|
||||||
.ForwardStdErr()
|
.ForwardStdErr()
|
||||||
.ForwardStdOut()
|
.ForwardStdOut()
|
||||||
.Execute()
|
.Execute();
|
||||||
.ExitCode;
|
exitCode = result.ExitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
telemetryClient.TrackEvent(
|
||||||
|
command,
|
||||||
|
null,
|
||||||
|
new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
["ExitCode"] = exitCode
|
||||||
|
});
|
||||||
|
|
||||||
|
return exitCode;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PrintVersion()
|
private static void PrintVersion()
|
||||||
{
|
{
|
||||||
Reporter.Output.WriteLine(HelpCommand.ProductVersion);
|
Reporter.Output.WriteLine(Product.Version);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PrintInfo()
|
private static void PrintInfo()
|
||||||
|
@ -142,7 +155,7 @@ namespace Microsoft.DotNet.Cli
|
||||||
var commitSha = GetCommitSha() ?? "N/A";
|
var commitSha = GetCommitSha() ?? "N/A";
|
||||||
Reporter.Output.WriteLine();
|
Reporter.Output.WriteLine();
|
||||||
Reporter.Output.WriteLine("Product Information:");
|
Reporter.Output.WriteLine("Product Information:");
|
||||||
Reporter.Output.WriteLine($" Version: {HelpCommand.ProductVersion}");
|
Reporter.Output.WriteLine($" Version: {Product.Version}");
|
||||||
Reporter.Output.WriteLine($" Commit Sha: {commitSha}");
|
Reporter.Output.WriteLine($" Commit Sha: {commitSha}");
|
||||||
Reporter.Output.WriteLine();
|
Reporter.Output.WriteLine();
|
||||||
var runtimeEnvironment = PlatformServices.Default.Runtime;
|
var runtimeEnvironment = PlatformServices.Default.Runtime;
|
||||||
|
|
3
src/dotnet/Properties/AssemblyInfo.cs
Normal file
3
src/dotnet/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("dotnet.Tests")]
|
|
@ -8,7 +8,6 @@ namespace Microsoft.DotNet.Tools.Help
|
||||||
{
|
{
|
||||||
public class HelpCommand
|
public class HelpCommand
|
||||||
{
|
{
|
||||||
private const string ProductLongName = ".NET Command Line Tools";
|
|
||||||
private const string UsageText = @"Usage: dotnet [common-options] [command] [arguments]
|
private const string UsageText = @"Usage: dotnet [common-options] [command] [arguments]
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
@ -29,13 +28,6 @@ Common Commands:
|
||||||
test Executes tests in a test project
|
test Executes tests in a test project
|
||||||
repl Launch an interactive session (read, eval, print, loop)
|
repl Launch an interactive session (read, eval, print, loop)
|
||||||
pack Creates a NuGet package";
|
pack Creates a NuGet package";
|
||||||
public static readonly string ProductVersion = GetProductVersion();
|
|
||||||
|
|
||||||
private static string GetProductVersion()
|
|
||||||
{
|
|
||||||
var attr = typeof(HelpCommand).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
|
|
||||||
return attr?.InformationalVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int Run(string[] args)
|
public static int Run(string[] args)
|
||||||
{
|
{
|
||||||
|
@ -58,10 +50,10 @@ Common Commands:
|
||||||
|
|
||||||
public static void PrintVersionHeader()
|
public static void PrintVersionHeader()
|
||||||
{
|
{
|
||||||
var versionString = string.IsNullOrEmpty(ProductVersion) ?
|
var versionString = string.IsNullOrEmpty(Product.Version) ?
|
||||||
string.Empty :
|
string.Empty :
|
||||||
$" ({ProductVersion})";
|
$" ({Product.Version})";
|
||||||
Reporter.Output.WriteLine(ProductLongName + versionString);
|
Reporter.Output.WriteLine(Product.LongName + versionString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
36
test/dotnet.Tests/TelemetryCommandTest.cs
Normal file
36
test/dotnet.Tests/TelemetryCommandTest.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
|
using Microsoft.DotNet.Cli;
|
||||||
|
using Microsoft.DotNet.Tools.Test.Utilities;
|
||||||
|
using Xunit;
|
||||||
|
using FluentAssertions;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.Tests
|
||||||
|
{
|
||||||
|
public class MockTelemetry : ITelemetry
|
||||||
|
{
|
||||||
|
public string EventName{get;set;}
|
||||||
|
public void TrackEvent(string eventName, IDictionary<string, string> properties, IDictionary<string, double> measurements)
|
||||||
|
{
|
||||||
|
EventName = eventName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class TelemetryCommandTests : TestBase
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void TestProjectDependencyIsNotAvailableThroughDriver()
|
||||||
|
{
|
||||||
|
MockTelemetry mockTelemetry = new MockTelemetry();
|
||||||
|
string[] args = { "help" };
|
||||||
|
Program.ProcessArgs(args, mockTelemetry);
|
||||||
|
Assert.Equal(mockTelemetry.EventName, args[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,9 @@
|
||||||
"Microsoft.DotNet.Tools.Tests.Utilities": {
|
"Microsoft.DotNet.Tools.Tests.Utilities": {
|
||||||
"target": "project"
|
"target": "project"
|
||||||
},
|
},
|
||||||
|
"dotnet": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
"Microsoft.DotNet.Cli.Utils": {
|
"Microsoft.DotNet.Cli.Utils": {
|
||||||
"target": "project",
|
"target": "project",
|
||||||
"type": "build"
|
"type": "build"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue