Merge pull request #2145 from dotnet/lakshanf/issue2066/telemetry

Lakshanf/issue2066/telemetry
This commit is contained in:
Eric Erhardt 2016-04-05 19:37:58 -05:00
commit 965547bd8a
13 changed files with 259 additions and 48 deletions

View file

@ -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;
}
}
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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);
}
}

View 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);
}
}

View 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;
}
}
}

View 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;
}
}
}
}

View file

@ -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",

View file

@ -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;

View file

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("dotnet.Tests")]

View file

@ -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);
}
}
}

View 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]);
}
}
}

View file

@ -6,6 +6,9 @@
"Microsoft.DotNet.Tools.Tests.Utilities": {
"target": "project"
},
"dotnet": {
"target": "project"
},
"Microsoft.DotNet.Cli.Utils": {
"target": "project",
"type": "build"