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";
|
||||
}
|
||||
|
||||
private static Lazy<bool> _verbose = new Lazy<bool>(() => GetBool(Variables.Verbose));
|
||||
private static Lazy<bool> _ansiPassThru = new Lazy<bool>(() => GetBool(Variables.AnsiPassThru));
|
||||
private static Lazy<bool> _verbose = new Lazy<bool>(() => Env.GetEnvironmentVariableAsBool(Variables.Verbose));
|
||||
private static Lazy<bool> _ansiPassThru = new Lazy<bool>(() => Env.GetEnvironmentVariableAsBool(Variables.AnsiPassThru));
|
||||
|
||||
public static bool IsVerbose()
|
||||
{
|
||||
|
@ -25,29 +25,6 @@ namespace Microsoft.DotNet.Cli.Utils
|
|||
public static bool ShouldPassAnsiCodesThrough()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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, 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
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.ApplicationInsights": "2.0.0-rc1",
|
||||
"Microsoft.DotNet.ProjectModel": "1.0.0-*",
|
||||
"Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-20100",
|
||||
"NuGet.Versioning": "3.5.0-beta-1130",
|
||||
|
|
|
@ -26,14 +26,13 @@ namespace Microsoft.DotNet.Cli
|
|||
{
|
||||
public class Program
|
||||
{
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
DebugHelper.HandleDebugSwitch(ref args);
|
||||
|
||||
try
|
||||
{
|
||||
return ProcessArgs(args);
|
||||
return Program.ProcessArgs(args, new Telemetry());
|
||||
}
|
||||
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.
|
||||
|
||||
|
@ -117,22 +116,36 @@ namespace Microsoft.DotNet.Cli
|
|||
["test"] = TestCommand.Run
|
||||
};
|
||||
|
||||
int exitCode;
|
||||
Func<string[], int> builtIn;
|
||||
if (builtIns.TryGetValue(command, out builtIn))
|
||||
{
|
||||
return builtIn(appArgs.ToArray());
|
||||
exitCode = builtIn(appArgs.ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
CommandResult result = Command.Create("dotnet-" + command, appArgs, FrameworkConstants.CommonFrameworks.NetStandardApp15)
|
||||
.ForwardStdErr()
|
||||
.ForwardStdOut()
|
||||
.Execute();
|
||||
exitCode = result.ExitCode;
|
||||
}
|
||||
|
||||
return Command.Create("dotnet-" + command, appArgs, FrameworkConstants.CommonFrameworks.NetStandardApp15)
|
||||
.ForwardStdErr()
|
||||
.ForwardStdOut()
|
||||
.Execute()
|
||||
.ExitCode;
|
||||
telemetryClient.TrackEvent(
|
||||
command,
|
||||
null,
|
||||
new Dictionary<string, double>
|
||||
{
|
||||
["ExitCode"] = exitCode
|
||||
});
|
||||
|
||||
return exitCode;
|
||||
|
||||
}
|
||||
|
||||
private static void PrintVersion()
|
||||
{
|
||||
Reporter.Output.WriteLine(HelpCommand.ProductVersion);
|
||||
Reporter.Output.WriteLine(Product.Version);
|
||||
}
|
||||
|
||||
private static void PrintInfo()
|
||||
|
@ -142,7 +155,7 @@ namespace Microsoft.DotNet.Cli
|
|||
var commitSha = GetCommitSha() ?? "N/A";
|
||||
Reporter.Output.WriteLine();
|
||||
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();
|
||||
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
|
||||
{
|
||||
private const string ProductLongName = ".NET Command Line Tools";
|
||||
private const string UsageText = @"Usage: dotnet [common-options] [command] [arguments]
|
||||
|
||||
Arguments:
|
||||
|
@ -29,13 +28,6 @@ Common Commands:
|
|||
test Executes tests in a test project
|
||||
repl Launch an interactive session (read, eval, print, loop)
|
||||
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)
|
||||
{
|
||||
|
@ -58,10 +50,10 @@ Common Commands:
|
|||
|
||||
public static void PrintVersionHeader()
|
||||
{
|
||||
var versionString = string.IsNullOrEmpty(ProductVersion) ?
|
||||
var versionString = string.IsNullOrEmpty(Product.Version) ?
|
||||
string.Empty :
|
||||
$" ({ProductVersion})";
|
||||
Reporter.Output.WriteLine(ProductLongName + versionString);
|
||||
$" ({Product.Version})";
|
||||
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": {
|
||||
"target": "project"
|
||||
},
|
||||
"dotnet": {
|
||||
"target": "project"
|
||||
},
|
||||
"Microsoft.DotNet.Cli.Utils": {
|
||||
"target": "project",
|
||||
"type": "build"
|
||||
|
|
Loading…
Add table
Reference in a new issue