Add telemetry data points for .NET Core 2.0

This commit is contained in:
William Li 2017-06-05 20:51:58 -07:00 committed by William Lee
parent 676fe41aca
commit 081f208942
29 changed files with 1324 additions and 77 deletions

View file

@ -0,0 +1,12 @@
// 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.Collections.Generic;
namespace Microsoft.DotNet.Cli.Utils
{
public interface ITelemetryFilter
{
IEnumerable<ApplicationInsightsEntryFormat> Filter(object o);
}
}

View file

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
namespace Microsoft.DotNet.Cli.Utils
{
public static class TelemetryEventEntry
{
public static event EventHandler<InstrumentationEventArgs> EntryPosted;
public static ITelemetryFilter TelemetryFilter { get; set; } = new BlockFilter();
public static void TrackEvent(
string eventName = null,
IDictionary<string, string> properties = null,
IDictionary<string, double> measurements = null)
{
EntryPosted?.Invoke(typeof(TelemetryEventEntry),
new InstrumentationEventArgs(eventName, properties, measurements));
}
public static void SendFiltered(object o = null)
{
if (o == null)
{
return;
}
foreach (ApplicationInsightsEntryFormat entry in TelemetryFilter.Filter(o))
{
TrackEvent(entry.EventName, entry.Properties, entry.Measurements);
}
}
public static void Subscribe(Action<string,
IDictionary<string, string>,
IDictionary<string, double>> subscriber)
{
void Handler(object sender, InstrumentationEventArgs eventArgs)
{
subscriber(eventArgs.EventName, eventArgs.Properties, eventArgs.Measurements);
}
EntryPosted += Handler;
}
}
public class BlockFilter : ITelemetryFilter
{
public IEnumerable<ApplicationInsightsEntryFormat> Filter(object o)
{
return new List<ApplicationInsightsEntryFormat>();
}
}
public class InstrumentationEventArgs : EventArgs
{
internal InstrumentationEventArgs(
string eventName,
IDictionary<string, string> properties,
IDictionary<string, double> measurements)
{
EventName = eventName;
Properties = properties;
Measurements = measurements;
}
public string EventName { get; }
public IDictionary<string, string> Properties { get; }
public IDictionary<string, double> Measurements { get; }
}
public class ApplicationInsightsEntryFormat
{
public ApplicationInsightsEntryFormat(
string eventName = null,
IDictionary<string, string> properties = null,
IDictionary<string, double> measurements = null)
{
EventName = eventName;
Properties = properties;
Measurements = measurements;
}
public string EventName { get; }
public IDictionary<string, string> Properties { get; }
public IDictionary<string, double> Measurements { get; }
}
}

View file

@ -0,0 +1,12 @@
// 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;
namespace Microsoft.DotNet.Configurer
{
public interface IUserLevelCacheWriter
{
string RunWithCache(string cacheKey, Func<string> getValueToCache);
}
}

View file

@ -0,0 +1,73 @@
// 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 Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.EnvironmentAbstractions;
namespace Microsoft.DotNet.Configurer
{
public class UserLevelCacheWriter : IUserLevelCacheWriter
{
private readonly IFile _file;
private readonly IDirectory _directory;
private string _dotnetUserProfileFolderPath;
public UserLevelCacheWriter(CliFallbackFolderPathCalculator cliFallbackFolderPathCalculator) :
this(
cliFallbackFolderPathCalculator.DotnetUserProfileFolderPath,
FileSystemWrapper.Default.File,
FileSystemWrapper.Default.Directory)
{
}
public string RunWithCache(string cacheKey, Func<string> getValueToCache)
{
var cacheFilepath = GetCacheFilePath(cacheKey);
try
{
if (!_file.Exists(cacheFilepath))
{
if (!_directory.Exists(_dotnetUserProfileFolderPath))
{
_directory.CreateDirectory(_dotnetUserProfileFolderPath);
}
var runResult = getValueToCache();
_file.WriteAllText(cacheFilepath, runResult);
return runResult;
}
else
{
return _file.ReadAllText(cacheFilepath);
}
}
catch (Exception ex)
{
if (ex is UnauthorizedAccessException
|| ex is PathTooLongException
|| ex is IOException)
{
return getValueToCache();
}
throw;
}
}
internal UserLevelCacheWriter(string dotnetUserProfileFolderPath, IFile file, IDirectory directory)
{
_file = file;
_directory = directory;
_dotnetUserProfileFolderPath = dotnetUserProfileFolderPath;
}
private string GetCacheFilePath(string cacheKey)
{
return Path.Combine(_dotnetUserProfileFolderPath, $"{Product.Version}_{cacheKey}.dotnetUserLevelCache");
}
}
}

View file

@ -2,9 +2,11 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Telemetry;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Configurer;
using Microsoft.DotNet.PlatformAbstractions;
@ -43,8 +45,8 @@ namespace Microsoft.DotNet.Cli
}
catch (Exception e) when (e.ShouldBeDisplayedAsError())
{
Reporter.Error.WriteLine(CommandContext.IsVerbose()
? e.ToString().Red().Bold()
Reporter.Error.WriteLine(CommandContext.IsVerbose()
? e.ToString().Red().Bold()
: e.Message.Red().Bold());
var commandParsingException = e as CommandParsingException;
@ -101,9 +103,9 @@ namespace Microsoft.DotNet.Cli
PrintInfo();
return 0;
}
else if (IsArg(args[lastArg], "h", "help") ||
args[lastArg] == "-?" ||
args[lastArg] == "/?")
else if (IsArg(args[lastArg], "h", "help") ||
args[lastArg] == "-?" ||
args[lastArg] == "/?")
{
HelpCommand.PrintHelp();
return 0;
@ -139,11 +141,16 @@ namespace Microsoft.DotNet.Cli
if (telemetryClient == null)
{
telemetryClient = new Telemetry(firstTimeUseNoticeSentinel);
telemetryClient = new Telemetry.Telemetry(firstTimeUseNoticeSentinel);
}
TelemetryEventEntry.Subscribe(telemetryClient.TrackEvent);
TelemetryEventEntry.TelemetryFilter = new TelemetryFilter();
}
var appArgs = (lastArg + 1) >= args.Length ? Enumerable.Empty<string>() : args.Skip(lastArg + 1).ToArray();
IEnumerable<string> appArgs =
(lastArg + 1) >= args.Length
? Enumerable.Empty<string>()
: args.Skip(lastArg + 1).ToArray();
if (verbose.HasValue)
{
@ -156,12 +163,12 @@ namespace Microsoft.DotNet.Cli
command = "help";
}
telemetryClient.TrackEvent(command, null, null);
TelemetryEventEntry.TrackEvent(command, null, null);
int exitCode;
BuiltInCommandMetadata builtIn;
if (BuiltInCommandsCatalog.Commands.TryGetValue(command, out builtIn))
if (BuiltInCommandsCatalog.Commands.TryGetValue(command, out var builtIn))
{
TelemetryEventEntry.SendFiltered(Parser.Instance.ParseFrom($"dotnet {command}", appArgs.ToArray()));
exitCode = builtIn.Command(appArgs.ToArray());
}
else
@ -173,7 +180,6 @@ namespace Microsoft.DotNet.Cli
.Execute();
exitCode = result.ExitCode;
}
return exitCode;
}

View file

@ -0,0 +1,42 @@
// 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.Collections.Generic;
using System.Linq;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal class AllowListToSendFirstAppliedOptions : IParseResultLogRule
{
public AllowListToSendFirstAppliedOptions(
HashSet<string> topLevelCommandNameAllowList)
{
_topLevelCommandNameAllowList = topLevelCommandNameAllowList;
}
private HashSet<string> _topLevelCommandNameAllowList { get; }
public List<ApplicationInsightsEntryFormat> AllowList(ParseResult parseResult)
{
var topLevelCommandNameFromParse = parseResult["dotnet"]?.AppliedOptions?.FirstOrDefault()?.Name;
var result = new List<ApplicationInsightsEntryFormat>();
if (_topLevelCommandNameAllowList.Contains(topLevelCommandNameFromParse))
{
var firstOption = parseResult["dotnet"]?[topLevelCommandNameFromParse]
?.AppliedOptions?.FirstOrDefault()?.Name;
if (firstOption != null)
{
result.Add(new ApplicationInsightsEntryFormat(
"dotnet-" + topLevelCommandNameFromParse,
new Dictionary<string, string>
{
{"argument", firstOption}
}));
}
}
return result;
}
}
}

View file

@ -0,0 +1,45 @@
// 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.Collections.Generic;
using System.Linq;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal class AllowListToSendFirstArgument : IParseResultLogRule
{
public AllowListToSendFirstArgument(
HashSet<string> topLevelCommandNameAllowList)
{
_topLevelCommandNameAllowList = topLevelCommandNameAllowList;
}
private HashSet<string> _topLevelCommandNameAllowList { get; }
public List<ApplicationInsightsEntryFormat> AllowList(ParseResult parseResult)
{
var result = new List<ApplicationInsightsEntryFormat>();
var topLevelCommandNameFromParse = parseResult["dotnet"]?.AppliedOptions?.FirstOrDefault()?.Name;
if (topLevelCommandNameFromParse != null)
{
if (_topLevelCommandNameAllowList.Contains(topLevelCommandNameFromParse))
{
var firstArgument = parseResult["dotnet"][topLevelCommandNameFromParse].Arguments
?.FirstOrDefault();
if (firstArgument != null)
{
result.Add(new ApplicationInsightsEntryFormat(
"dotnet-" + topLevelCommandNameFromParse,
new Dictionary<string, string>
{
{"argument", firstArgument}
}));
}
}
}
return result;
}
}
}

View file

@ -0,0 +1,58 @@
// 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.IO;
using System.Security;
using Microsoft.Win32;
using Microsoft.DotNet.PlatformAbstractions;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal class DockerContainerDetectorForTelemetry : IDockerContainerDetector
{
public IsDockerContainer IsDockerContainer()
{
switch (RuntimeEnvironment.OperatingSystemPlatform)
{
case Platform.Windows:
try
{
using (RegistryKey subkey
= Registry.LocalMachine.OpenSubKey("System\\CurrentControlSet\\Control"))
{
return subkey?.GetValue("ContainerType") != null
? Cli.Telemetry.IsDockerContainer.True
: Cli.Telemetry.IsDockerContainer.False;
}
}
catch (SecurityException)
{
return Cli.Telemetry.IsDockerContainer.Unknown;
}
case Platform.Linux:
return ReadProcToDetectDockerInLinux()
? Cli.Telemetry.IsDockerContainer.True
: Cli.Telemetry.IsDockerContainer.False;
case Platform.Unknown:
return Cli.Telemetry.IsDockerContainer.Unknown;
case Platform.Darwin:
default:
return Cli.Telemetry.IsDockerContainer.False;
}
}
private static bool ReadProcToDetectDockerInLinux()
{
return File
.ReadAllText("/proc/1/cgroup")
.Contains("/docker/");
}
}
internal enum IsDockerContainer
{
True,
False,
Unknown
}
}

View file

@ -0,0 +1,10 @@
// 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.
namespace Microsoft.DotNet.Cli.Telemetry
{
internal interface IDockerContainerDetector
{
IsDockerContainer IsDockerContainer();
}
}

View file

@ -0,0 +1,14 @@
// 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.Collections.Generic;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal interface IParseResultLogRule
{
List<ApplicationInsightsEntryFormat> AllowList(ParseResult parseResult);
}
}

View file

@ -1,10 +1,9 @@
// 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
namespace Microsoft.DotNet.Cli.Telemetry
{
public interface ITelemetry
{

View file

@ -0,0 +1,168 @@
// 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.Linq;
using System.Diagnostics;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Net.NetworkInformation;
using System.ComponentModel;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal static class MacAddressGetter
{
private const string MacRegex = @"(?:[a-z0-9]{2}[:\-]){5}[a-z0-9]{2}";
private const string ZeroRegex = @"(?:00[:\-]){5}00";
private const int ErrorFileNotFound = 0x2;
public static string GetMacAddress()
{
try
{
var shelloutput = GetShellOutMacAddressOutput();
if (shelloutput == null)
{
return null;
}
return ParseMACAddress(shelloutput);
}
catch (Win32Exception e)
{
if (e.NativeErrorCode == ErrorFileNotFound)
{
return GetMacAddressByNetworkInterface();
}
else
{
throw;
}
}
}
private static string ParseMACAddress(string shelloutput)
{
string macAddress = null;
foreach (Match match in Regex.Matches(shelloutput, MacRegex, RegexOptions.IgnoreCase))
{
if (!Regex.IsMatch(match.Value, ZeroRegex))
{
macAddress = match.Value;
break;
}
}
if (macAddress != null)
{
return macAddress;
}
return null;
}
private static string GetIpCommandOutput()
{
var ipResult = new ProcessStartInfo
{
FileName = "ip",
Arguments = "link",
UseShellExecute = false
}.ExecuteAndCaptureOutput(out string ipStdOut, out string ipStdErr);
if (ipResult == 0)
{
return ipStdOut;
}
else
{
return null;
}
}
private static string GetShellOutMacAddressOutput()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var result = new ProcessStartInfo
{
FileName = "getmac.exe",
UseShellExecute = false
}.ExecuteAndCaptureOutput(out string stdOut, out string stdErr);
if (result == 0)
{
return stdOut;
}
else
{
return null;
}
}
else
{
try
{
var ifconfigResult = new ProcessStartInfo
{
FileName = "ifconfig",
Arguments = "-a",
UseShellExecute = false
}.ExecuteAndCaptureOutput(out string ifconfigStdOut, out string ifconfigStdErr);
if (ifconfigResult == 0)
{
return ifconfigStdOut;
}
else
{
return GetIpCommandOutput();
}
}
catch (Win32Exception e)
{
if (e.NativeErrorCode == ErrorFileNotFound)
{
return GetIpCommandOutput();
}
else
{
throw;
}
}
}
}
private static string GetMacAddressByNetworkInterface()
{
return GetMacAddressesByNetworkInterface().FirstOrDefault();
}
private static List<string> GetMacAddressesByNetworkInterface()
{
NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
var macs = new List<string>();
if (nics == null || nics.Length < 1)
{
macs.Add(string.Empty);
return macs;
}
foreach (NetworkInterface adapter in nics)
{
IPInterfaceProperties properties = adapter.GetIPProperties();
PhysicalAddress address = adapter.GetPhysicalAddress();
byte[] bytes = address.GetAddressBytes();
macs.Add(string.Join("-", bytes.Select(x => x.ToString("X2"))));
if (macs.Count >= 10)
{
break;
}
}
return macs;
}
}
}

View file

@ -0,0 +1,32 @@
// 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.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal static class Sha256Hasher
{
/// <summary>
/// // The hashed mac address needs to be the same hashed value as produced by the other distinct sources given the same input. (e.g. VsCode)
/// </summary>
public static string Hash(string text)
{
var sha256 = SHA256.Create();
return HashInFormat(sha256, text);
}
private static string HashInFormat(SHA256 sha256, string text)
{
byte[] bytes = Encoding.UTF8.GetBytes(text);
byte[] hash = sha256.ComputeHash(bytes);
StringBuilder hashString = new StringBuilder();
foreach (byte x in hash)
{
hashString.AppendFormat("{0:x2}", x);
}
return hashString.ToString();
}
}
}

View file

@ -4,36 +4,28 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Configurer;
using Microsoft.DotNet.PlatformAbstractions;
namespace Microsoft.DotNet.Cli
namespace Microsoft.DotNet.Cli.Telemetry
{
public class Telemetry : ITelemetry
{
internal static string CurrentSessionId = null;
private TelemetryClient _client = null;
private Dictionary<string, string> _commonProperties = null;
private Dictionary<string, double> _commonMeasurements = null;
private Task _trackEventTask = null;
private const string InstrumentationKey = "74cc1c9e-3e6e-4d05-b3fc-dde9101d0254";
private const string TelemetryOptout = "DOTNET_CLI_TELEMETRY_OPTOUT";
private const string TelemetryProfileEnvironmentVariable = "DOTNET_CLI_TELEMETRY_PROFILE";
private const string OSVersion = "OS Version";
private const string OSPlatform = "OS Platform";
private const string RuntimeId = "Runtime Id";
private const string ProductVersion = "Product Version";
private const string TelemetryProfile = "Telemetry Profile";
public bool Enabled { get; }
public Telemetry () : this(null) { }
public Telemetry() : this(null) { }
public Telemetry(IFirstTimeUseNoticeSentinel sentinel) : this(sentinel, null) { }
@ -70,7 +62,8 @@ namespace Microsoft.DotNet.Cli
return sentinel.Exists();
}
public void TrackEvent(string eventName, IDictionary<string, string> properties, IDictionary<string, double> measurements)
public void TrackEvent(string eventName, IDictionary<string, string> properties,
IDictionary<string, double> measurements)
{
if (!Enabled)
{
@ -99,26 +92,23 @@ namespace Microsoft.DotNet.Cli
_client = new TelemetryClient();
_client.InstrumentationKey = InstrumentationKey;
_client.Context.Session.Id = CurrentSessionId;
_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);
_commonProperties.Add(TelemetryProfile, Environment.GetEnvironmentVariable(TelemetryProfileEnvironmentVariable));
_commonProperties = new TelemetryCommonProperties().GetTelemetryCommonProperties();
_commonMeasurements = new Dictionary<string, double>();
}
catch (Exception)
catch (Exception e)
{
_client = null;
// we dont want to fail the tool if telemetry fails.
Debug.Fail("Exception during telemetry initialization");
Debug.Fail(e.ToString());
}
}
private void TrackEventTask(string eventName, IDictionary<string, string> properties, IDictionary<string, double> measurements)
private void TrackEventTask(
string eventName,
IDictionary<string, string> properties,
IDictionary<string, double> measurements)
{
if (_client == null)
{
@ -127,8 +117,8 @@ namespace Microsoft.DotNet.Cli
try
{
var eventProperties = GetEventProperties(properties);
var eventMeasurements = GetEventMeasures(measurements);
Dictionary<string, string> eventProperties = GetEventProperties(properties);
Dictionary<string, double> eventMeasurements = GetEventMeasures(measurements);
_client.TrackEvent(eventName, eventProperties, eventMeasurements);
_client.Flush();
@ -144,7 +134,7 @@ namespace Microsoft.DotNet.Cli
Dictionary<string, double> eventMeasurements = new Dictionary<string, double>(_commonMeasurements);
if (measurements != null)
{
foreach (var measurement in measurements)
foreach (KeyValuePair<string, double> measurement in measurements)
{
if (eventMeasurements.ContainsKey(measurement.Key))
{
@ -164,7 +154,7 @@ namespace Microsoft.DotNet.Cli
if (properties != null)
{
var eventProperties = new Dictionary<string, string>(_commonProperties);
foreach (var property in properties)
foreach (KeyValuePair<string, string> property in properties)
{
if (eventProperties.ContainsKey(property.Key))
{

View file

@ -0,0 +1,77 @@
// 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;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.PlatformAbstractions;
using System.IO;
using Microsoft.DotNet.Configurer;
using System.Linq;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal class TelemetryCommonProperties
{
public TelemetryCommonProperties(
Func<string> getCurrentDirectory = null,
Func<string, string> hasher = null,
Func<string> getMACAddress = null,
IDockerContainerDetector dockerContainerDetector = null,
IUserLevelCacheWriter userLevelCacheWriter = null)
{
_getCurrentDirectory = getCurrentDirectory ?? Directory.GetCurrentDirectory;
_hasher = hasher ?? Sha256Hasher.Hash;
_getMACAddress = getMACAddress ?? MacAddressGetter.GetMacAddress;
_dockerContainerDetector = dockerContainerDetector ?? new DockerContainerDetectorForTelemetry();
_userLevelCacheWriter = userLevelCacheWriter ?? new UserLevelCacheWriter(new CliFallbackFolderPathCalculator());
}
private readonly IDockerContainerDetector _dockerContainerDetector;
private Func<string> _getCurrentDirectory;
private Func<string, string> _hasher;
private Func<string> _getMACAddress;
private IUserLevelCacheWriter _userLevelCacheWriter;
private const string OSVersion = "OS Version";
private const string OSPlatform = "OS Platform";
private const string RuntimeId = "Runtime Id";
private const string ProductVersion = "Product Version";
private const string TelemetryProfile = "Telemetry Profile";
private const string CurrentPathHash = "Current Path Hash";
private const string MachineId = "Machine ID";
private const string DockerContainer = "Docker Container";
private const string TelemetryProfileEnvironmentVariable = "DOTNET_CLI_TELEMETRY_PROFILE";
private const string CannotFindMacAddress = "Unknown";
private const string MachineIdCacheKey = "MachineId";
private const string IsDockerContainerCacheKey = "IsDockerContainer";
public Dictionary<string, string> GetTelemetryCommonProperties()
{
return new Dictionary<string, string>
{
{OSVersion, RuntimeEnvironment.OperatingSystemVersion},
{OSPlatform, RuntimeEnvironment.OperatingSystemPlatform.ToString()},
{RuntimeId, RuntimeEnvironment.GetRuntimeIdentifier()},
{ProductVersion, Product.Version},
{TelemetryProfile, Environment.GetEnvironmentVariable(TelemetryProfileEnvironmentVariable)},
{DockerContainer, _userLevelCacheWriter.RunWithCache(IsDockerContainerCacheKey, () => _dockerContainerDetector.IsDockerContainer().ToString("G") )},
{CurrentPathHash, _hasher(_getCurrentDirectory())},
{MachineId, _userLevelCacheWriter.RunWithCache(MachineIdCacheKey, GetMachineId)}
};
}
private string GetMachineId()
{
var macAddress = _getMACAddress();
if (macAddress != null)
{
return _hasher(macAddress);
}
else
{
return Guid.NewGuid().ToString();
}
}
}
}

View file

@ -0,0 +1,95 @@
// 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.Collections.Generic;
using System.Linq;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal class TelemetryFilter : ITelemetryFilter
{
private const string DotnetName = "dotnet";
public IEnumerable<ApplicationInsightsEntryFormat> Filter(object objectToFilter)
{
var ruleSet = new List<IParseResultLogRule>
{ new AllowListToSendFirstArgument(new HashSet<string>{ "new", "help" }),
new AllowListToSendFirstAppliedOptions(new HashSet<string>{ "add", "remove", "list", "sln", "nuget" }),
new TopLevelCommandNameAndOptionToLog
(
topLevelCommandName: new HashSet<string> { "new" },
optionsToLog: new HashSet<string> { "language" }
),
new TopLevelCommandNameAndOptionToLog
(
topLevelCommandName: new HashSet<string> { "build", "publish" },
optionsToLog: new HashSet<string> { "framework", "runtime", "configuration" }
),
new TopLevelCommandNameAndOptionToLog
(
topLevelCommandName: new HashSet<string> { "run", "clean", "test" },
optionsToLog: new HashSet<string> { "framework", "configuration" }
),
new TopLevelCommandNameAndOptionToLog
(
topLevelCommandName: new HashSet<string> { "pack" },
optionsToLog: new HashSet<string> { "configuration" }
),
new TopLevelCommandNameAndOptionToLog
(
topLevelCommandName: new HashSet<string> { "migrate" },
optionsToLog: new HashSet<string> { "sdk-package-version" }
),
new TopLevelCommandNameAndOptionToLog
(
topLevelCommandName: new HashSet<string> { "vstest" },
optionsToLog: new HashSet<string> { "platform", "framework", "logger" }
),
new TopLevelCommandNameAndOptionToLog
(
topLevelCommandName: new HashSet<string> { "publish" },
optionsToLog: new HashSet<string> { "runtime" }
)
};
var result = new List<ApplicationInsightsEntryFormat>();
if (objectToFilter is ParseResult parseResult)
{
var topLevelCommandName = parseResult[DotnetName]?.AppliedOptions?.FirstOrDefault()?.Name;
if (topLevelCommandName != null)
{
LogVerbosityForAllTopLevelCommand(result, parseResult, topLevelCommandName);
foreach (IParseResultLogRule rule in ruleSet)
{
result.AddRange(rule.AllowList(parseResult));
}
}
}
return result;
}
private static void LogVerbosityForAllTopLevelCommand(
ICollection<ApplicationInsightsEntryFormat> result,
ParseResult parseResult,
string topLevelCommandName)
{
if (parseResult[DotnetName][topLevelCommandName]?.AppliedOptions != null &&
parseResult[DotnetName][topLevelCommandName].AppliedOptions.Contains("verbosity"))
{
AppliedOption appliedOptions =
parseResult[DotnetName][topLevelCommandName].AppliedOptions["verbosity"];
result.Add(new ApplicationInsightsEntryFormat(
"dotnet-" + topLevelCommandName,
new Dictionary<string, string>()
{
{"verbosity", appliedOptions.Arguments.ElementAt(0)}
}));
}
}
}
}

View file

@ -0,0 +1,49 @@
// 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.Collections.Generic;
using System.Linq;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal class TopLevelCommandNameAndOptionToLog : IParseResultLogRule
{
public TopLevelCommandNameAndOptionToLog(
HashSet<string> topLevelCommandName,
HashSet<string> optionsToLog)
{
_topLevelCommandName = topLevelCommandName;
_optionsToLog = optionsToLog;
}
private HashSet<string> _topLevelCommandName { get; }
private HashSet<string> _optionsToLog { get; }
private const string DotnetName = "dotnet";
public List<ApplicationInsightsEntryFormat> AllowList(ParseResult parseResult)
{
var topLevelCommandName = parseResult[DotnetName]?.AppliedOptions?.FirstOrDefault()?.Name;
var result = new List<ApplicationInsightsEntryFormat>();
foreach (var option in _optionsToLog)
{
if (_topLevelCommandName.Contains(topLevelCommandName)
&& parseResult[DotnetName]?[topLevelCommandName]?.AppliedOptions != null
&& parseResult[DotnetName][topLevelCommandName].AppliedOptions.Contains(option))
{
AppliedOption appliedOptions =
parseResult[DotnetName][topLevelCommandName]
.AppliedOptions[option];
result.Add(new ApplicationInsightsEntryFormat(
"dotnet-" + topLevelCommandName,
new Dictionary<string, string>
{
{option, appliedOptions.Arguments.ElementAt(0)}
}));
}
}
return result;
}
}
}

View file

@ -6,6 +6,7 @@ using System.Linq;
using System.IO;
using System.Collections.Generic;
using Microsoft.DotNet.Configurer;
using Microsoft.DotNet.Cli.Telemetry;
namespace Microsoft.DotNet.Cli
{
@ -37,13 +38,13 @@ namespace Microsoft.DotNet.Cli
internal class ThreadBlockingTelemetry : ITelemetry
{
private Telemetry telemetry;
private Telemetry.Telemetry telemetry;
internal ThreadBlockingTelemetry()
{
var sessionId =
Environment.GetEnvironmentVariable(TelemetrySessionIdEnvironmentVariableName);
telemetry = new Telemetry(new NoOpFirstTimeUseNoticeSentinel(), sessionId, blockThreadInitialization: true);
telemetry = new Telemetry.Telemetry(new NoOpFirstTimeUseNoticeSentinel(), sessionId, blockThreadInitialization: true);
}
public bool Enabled => telemetry.Enabled;

View file

@ -10,6 +10,7 @@ using System.Runtime.InteropServices;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.CommandLine;
using System.Diagnostics;
using Microsoft.DotNet.Cli.Telemetry;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Tools.MSBuild

View file

@ -5,6 +5,7 @@ using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.Telemetry;
using Microsoft.DotNet.Configurer;
namespace Microsoft.DotNet.Tools.MSBuild
@ -39,15 +40,13 @@ namespace Microsoft.DotNet.Tools.MSBuild
{
if (_telemetry != null && _telemetry.Enabled)
{
IEventSource2 eventSource2 = eventSource as IEventSource2;
if (eventSource2 != null)
if (eventSource is IEventSource2 eventSource2)
{
eventSource2.TelemetryLogged += OnTelemetryLogged;
}
}
}
catch(Exception)
catch (Exception)
{
// Exceptions during telemetry shouldn't cause anything else to fail
}

View file

@ -8,6 +8,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.Telemetry;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Configurer;
using Microsoft.DotNet.Tools.MSBuild;

View file

@ -32,7 +32,7 @@ namespace Microsoft.DotNet.Tools.NuGet
private class NuGetCommandRunner : ICommandRunner
{
public int Run(string [] args)
public int Run(string[] args)
{
var nugetApp = new NuGetForwardingApp(args);

View file

@ -0,0 +1,165 @@
// 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;
using System.IO;
using System.Linq;
using FluentAssertions;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Configurer;
using Microsoft.Extensions.DependencyModel.Tests;
using Microsoft.Extensions.EnvironmentAbstractions;
using Moq;
using Xunit;
namespace Microsoft.DotNet.Configurer.UnitTests
{
public class GivenAFunctionReturnStringAndFakeFileSystem
{
private const string DOTNET_USER_PROFILE_FOLDER_PATH = "some path";
private FileSystemMockBuilder _fileSystemMockBuilder;
private UserLevelCacheWriter _userLevelCacheWriter;
private IFileSystem _fileSystemMock;
public GivenAFunctionReturnStringAndFakeFileSystem()
{
_fileSystemMockBuilder = FileSystemMockBuilder.Create();
_fileSystemMock = _fileSystemMockBuilder.Build();
_userLevelCacheWriter =
new UserLevelCacheWriter(
DOTNET_USER_PROFILE_FOLDER_PATH,
_fileSystemMock.File,
_fileSystemMock.Directory);
}
[Fact]
public void ItReturnsTheFunctionResult()
{
_userLevelCacheWriter.RunWithCache("fooKey", () => "foo").Should().Be("foo");
}
[Fact]
public void ItRunsTheFunctionOnlyOnceWhenInvokeTwice()
{
var counter = new Counter();
Func<string> func = () =>
{
counter.Increase();
return "foo";
};
_userLevelCacheWriter.RunWithCache("fookey", func).Should().Be("foo");
_userLevelCacheWriter.RunWithCache("fookey", func).Should().Be("foo");
counter.Count.Should().Be(1);
}
[Fact]
public void ItKeepsTheCacheInUserProfileWithCacheKey()
{
_userLevelCacheWriter.RunWithCache("fooKey", () => "foo");
var path = Path.Combine("some path", $"{Product.Version}_fooKey.dotnetUserLevelCache");
_fileSystemMock.File.Exists(path);
_fileSystemMock.File.ReadAllText(path).Should().Be("foo");
}
[Fact]
public void ItRunsAndReturnsTheValueIfCacheCreationFailed()
{
var mockFile = new Mock<IFile>();
var systemUndertest =
new UserLevelCacheWriter(
DOTNET_USER_PROFILE_FOLDER_PATH,
new NoPermissionFileFake(),
new NoPermissionDirectoryFake());
var counter = new Counter();
Func<string> func = () =>
{
counter.Increase();
return "foo";
};
systemUndertest.RunWithCache("fookey", func).Should().Be("foo");
systemUndertest.RunWithCache("fookey", func).Should().Be("foo");
counter.Count.Should().Be(2);
}
private class NoPermissionFileFake : IFile
{
public bool Exists(string path)
{
return false;
}
public string ReadAllText(string path)
{
throw new UnauthorizedAccessException();
}
public Stream OpenRead(string path)
{
throw new UnauthorizedAccessException();
}
public Stream OpenFile(
string path,
FileMode fileMode,
FileAccess fileAccess,
FileShare fileShare,
int bufferSize,
FileOptions fileOptions)
{
throw new NotImplementedException();
}
public void CreateEmptyFile(string path)
{
throw new UnauthorizedAccessException();
}
public void WriteAllText(string path, string content)
{
throw new UnauthorizedAccessException();
}
}
private class NoPermissionDirectoryFake : IDirectory
{
public ITemporaryDirectory CreateTemporaryDirectory()
{
throw new NotImplementedException();
}
public IEnumerable<string> GetFiles(string path, string searchPattern)
{
throw new UnauthorizedAccessException();
}
public string GetDirectoryFullName(string path)
{
throw new NotImplementedException();
}
public bool Exists(string path)
{
return false;
}
public void CreateDirectory(string path)
{
throw new UnauthorizedAccessException();
}
}
private class Counter
{
public int Count { get; private set; }
public void Increase() { Count++; }
}
}
}

View file

@ -18,6 +18,7 @@ using MSBuildCommand = Microsoft.DotNet.Tools.Test.Utilities.MSBuildCommand;
using System.Diagnostics;
using System.Threading;
// There are tests which modify static Telemetry.CurrentSessionId and they cannot run in parallel
[assembly: CollectionBehavior(DisableTestParallelization = true)]
@ -129,7 +130,7 @@ namespace Microsoft.DotNet.Cli.MSBuild.Tests
[Fact]
public void WhenTelemetryIsEnabledTheLoggerIsAddedToTheCommandLine()
{
Telemetry telemetry;
Telemetry.Telemetry telemetry;
string[] allArgs = GetArgsForMSBuild(() => true, out telemetry);
// telemetry will still be disabled if environment variable is set
if (telemetry.Enabled)
@ -156,14 +157,15 @@ namespace Microsoft.DotNet.Cli.MSBuild.Tests
private string[] GetArgsForMSBuild(Func<bool> sentinelExists)
{
Telemetry telemetry;
Telemetry.Telemetry telemetry;
return GetArgsForMSBuild(sentinelExists, out telemetry);
}
private string[] GetArgsForMSBuild(Func<bool> sentinelExists, out Telemetry telemetry)
private string[] GetArgsForMSBuild(Func<bool> sentinelExists, out Telemetry.Telemetry telemetry)
{
Telemetry.CurrentSessionId = null; // reset static session id modified by telemetry constructor
telemetry = new Telemetry(new MockNuGetCacheSentinel(sentinelExists));
Telemetry.Telemetry.CurrentSessionId = null; // reset static session id modified by telemetry constructor
telemetry = new Telemetry.Telemetry(new MockNuGetCacheSentinel(sentinelExists));
MSBuildForwardingApp msBuildForwardingApp = new MSBuildForwardingApp(Enumerable.Empty<string>());

View file

@ -0,0 +1,39 @@
// 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.Collections.Concurrent;
using System.Collections.Generic;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.Telemetry;
namespace Microsoft.DotNet.Tests
{
public class FakeRecordEventNameTelemetry : ITelemetry
{
public bool Enabled { get; set; }
public string EventName { get; set; }
public void TrackEvent(string eventName,
IDictionary<string, string> properties,
IDictionary<string, double> measurements)
{
LogEntries.Add(
new LogEntry
{
EventName = eventName,
Measurement = measurements,
Properties = properties
});
}
public ConcurrentBag<LogEntry> LogEntries { get; set; } = new ConcurrentBag<LogEntry>();
public class LogEntry
{
public string EventName { get; set; }
public IDictionary<string, string> Properties { get; set; }
public IDictionary<string, double> Measurement { get; set; }
}
}
}

View file

@ -106,8 +106,9 @@ namespace Microsoft.DotNet.Tests
command.ExecuteWithCapturedOutput("internal-reportinstallsuccess test").Should().Pass();
var emptyHomeFolder = new DirectoryInfo(Path.Combine(emptyHome, ".dotnet"));
emptyHomeFolder.Should().NotExist();
var homeFolder = new DirectoryInfo(Path.Combine(emptyHome, ".dotnet"));
string[] fileEntries = Directory.GetFiles(homeFolder.ToString());
fileEntries.Should().OnlyContain(x => !x.Contains(".dotnetFirstUseSentinel"));
}
[Fact]

View file

@ -1,52 +1,229 @@
// 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;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.Telemetry;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Tools.Test.Utilities;
using System.Collections.Generic;
using Xunit;
namespace Microsoft.DotNet.Tests
{
public class MockTelemetry : ITelemetry
public class TelemetryCommandTests : TestBase
{
public bool Enabled { get; set; }
private readonly FakeRecordEventNameTelemetry _fakeTelemetry;
public string EventName { get; set; }
public IDictionary<string, string> Properties { get; set; }
public void TrackEvent(string eventName, IDictionary<string, string> properties, IDictionary<string, double> measurements)
public TelemetryCommandTests()
{
EventName = eventName;
Properties = properties;
_fakeTelemetry = new FakeRecordEventNameTelemetry();
TelemetryEventEntry.Subscribe(_fakeTelemetry.TrackEvent);
TelemetryEventEntry.TelemetryFilter = new TelemetryFilter();
}
}
public class TelemetryCommandTests : TestBase
{
[Fact]
public void TestProjectDependencyIsNotAvailableThroughDriver()
public void TopLevelCommandNameShouldBeSentToTelemetry()
{
MockTelemetry mockTelemetry = new MockTelemetry();
string[] args = { "help" };
Microsoft.DotNet.Cli.Program.ProcessArgs(args, mockTelemetry);
Assert.Equal(mockTelemetry.EventName, args[0]);
string[] args = {"help"};
Cli.Program.ProcessArgs(args);
_fakeTelemetry.LogEntries.Should().Contain(e => e.EventName == args[0]);
}
[Fact]
public void DotnetNewCommandFirstArgumentShouldBeSentToTelemetry()
{
const string argumentToSend = "console";
string[] args = {"new", argumentToSend};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-new" && e.Properties.ContainsKey("argument") &&
e.Properties["argument"] == argumentToSend);
}
[Fact]
public void DotnetHelpCommandFirstArgumentShouldBeSentToTelemetry()
{
const string argumentToSend = "something";
string[] args = {"help", argumentToSend};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-help" && e.Properties.ContainsKey("argument") &&
e.Properties["argument"] == argumentToSend);
}
[Fact]
public void DotnetAddCommandFirstArgumentShouldBeSentToTelemetry()
{
const string argumentToSend = "package";
string[] args = {"add", argumentToSend, "aPackageName"};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-add" && e.Properties.ContainsKey("argument") &&
e.Properties["argument"] == argumentToSend);
}
[Fact]
public void DotnetAddCommandFirstArgumentShouldBeSentToTelemetry2()
{
const string argumentToSend = "reference";
string[] args = {"add", argumentToSend, "aPackageName"};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-add" && e.Properties.ContainsKey("argument") &&
e.Properties["argument"] == argumentToSend);
}
[Fact]
public void DotnetRemoveCommandFirstArgumentShouldBeSentToTelemetry()
{
const string argumentToSend = "package";
string[] args = {"remove", argumentToSend, "aPackageName"};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-remove" && e.Properties.ContainsKey("argument") &&
e.Properties["argument"] == argumentToSend);
}
[Fact]
public void DotnetListCommandFirstArgumentShouldBeSentToTelemetry()
{
const string argumentToSend = "reference";
string[] args = {"list", argumentToSend, "aPackageName"};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-list" && e.Properties.ContainsKey("argument") &&
e.Properties["argument"] == argumentToSend);
}
[Fact]
public void DotnetSlnCommandFirstArgumentShouldBeSentToTelemetry()
{
const string argumentToSend = "list";
string[] args = {"sln", "aSolution", argumentToSend};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-sln" && e.Properties.ContainsKey("argument") &&
e.Properties["argument"] == argumentToSend);
}
[Fact]
public void DotnetNugetCommandFirstArgumentShouldBeSentToTelemetry()
{
const string argumentToSend = "push";
string[] args = {"nuget", argumentToSend, "aRoot"};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-nuget" && e.Properties.ContainsKey("argument") &&
e.Properties["argument"] == argumentToSend);
}
[Fact]
public void DotnetNewCommandLanguageOpinionShouldBeSentToTelemetry()
{
const string optionKey = "language";
const string optionValueToSend = "c#";
string[] args = {"new", "console", "--" + optionKey, optionValueToSend};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-new" && e.Properties.ContainsKey(optionKey) &&
e.Properties[optionKey] == optionValueToSend);
}
[Fact]
public void AnyDotnetCommandVerbosityOpinionShouldBeSentToTelemetry()
{
const string optionKey = "verbosity";
const string optionValueToSend = "minimal";
string[] args = {"restore", "--" + optionKey, optionValueToSend};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-restore" && e.Properties.ContainsKey(optionKey) &&
e.Properties[optionKey] == optionValueToSend);
}
[Fact]
public void DotnetBuildAndPublishCommandOpinionsShouldBeSentToTelemetry()
{
const string optionKey = "configuration";
const string optionValueToSend = "Debug";
string[] args = {"build", "--" + optionKey, optionValueToSend};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-build" && e.Properties.ContainsKey(optionKey) &&
e.Properties[optionKey] == optionValueToSend);
}
[Fact]
public void DotnetPublishCommandRuntimeOpinionsShouldBeSentToTelemetry()
{
const string optionKey = "runtime";
const string optionValueToSend = "win10-x64";
string[] args = { "publish", "--" + optionKey, optionValueToSend };
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-publish" && e.Properties.ContainsKey(optionKey) &&
e.Properties[optionKey] == optionValueToSend);
}
[Fact]
public void DotnetBuildAndPublishCommandOpinionsShouldBeSentToTelemetryWhenThereIsMultipleOption()
{
string[] args = {"build", "--configuration", "Debug", "--runtime", "osx.10.11-x64"};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-build" && e.Properties.ContainsKey("configuration") &&
e.Properties["configuration"] == "Debug");
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-build" && e.Properties.ContainsKey("runtime") &&
e.Properties["runtime"] == "osx.10.11-x64");
}
[Fact]
public void DotnetRunCleanTestCommandOpinionsShouldBeSentToTelemetryWhenThereIsMultipleOption()
{
string[] args = {"clean", "--configuration", "Debug", "--framework", "netcoreapp1.0"};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-clean" && e.Properties.ContainsKey("configuration") &&
e.Properties["configuration"] == "Debug");
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-clean" && e.Properties.ContainsKey("framework") &&
e.Properties["framework"] == "netcoreapp1.0");
}
[WindowsOnlyFact]
public void InternalreportinstallsuccessCommandCollectExeNameWithEventname()
{
MockTelemetry mockTelemetry = new MockTelemetry();
FakeRecordEventNameTelemetry fakeTelemetry = new FakeRecordEventNameTelemetry();
string[] args = { "c:\\mypath\\dotnet-sdk-latest-win-x64.exe" };
InternalReportinstallsuccess.ProcessInputAndSendTelemetry(args, mockTelemetry);
InternalReportinstallsuccess.ProcessInputAndSendTelemetry(args, fakeTelemetry);
mockTelemetry.EventName.Should().Be("reportinstallsuccess");
mockTelemetry.Properties["exeName"].Should().Be("dotnet-sdk-latest-win-x64.exe");
fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "reportinstallsuccess" && e.Properties.ContainsKey("exeName") &&
e.Properties["exeName"] == "dotnet-sdk-latest-win-x64.exe");
}
[Fact]

View file

@ -0,0 +1,54 @@
// 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 FluentAssertions;
using Microsoft.DotNet.Tools.Test.Utilities;
using Xunit;
using System;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.Telemetry;
using Microsoft.DotNet.Configurer;
namespace Microsoft.DotNet.Tests
{
public class TelemetryCommonPropertiesTests : TestBase
{
[Fact]
public void TelemetryCommonPropertiesShouldContainIfItIsInDockerOrNot()
{
var unitUnderTest = new TelemetryCommonProperties(userLevelCacheWriter: new NothingCache());
unitUnderTest.GetTelemetryCommonProperties().Should().ContainKey("Docker Container");
}
[Fact]
public void TelemetryCommonPropertiesShouldReturnHashedPath()
{
var unitUnderTest = new TelemetryCommonProperties(() => "ADirectory", userLevelCacheWriter: new NothingCache());
unitUnderTest.GetTelemetryCommonProperties()["Current Path Hash"].Should().NotBe("ADirectory");
}
[Fact]
public void TelemetryCommonPropertiesShouldReturnHashedMachineId()
{
var unitUnderTest = new TelemetryCommonProperties(getMACAddress: () => "plaintext", userLevelCacheWriter: new NothingCache());
unitUnderTest.GetTelemetryCommonProperties()["Machine ID"].Should().NotBe("plaintext");
}
[Fact]
public void TelemetryCommonPropertiesShouldReturnNewGuidWhenCannotGetMacAddress()
{
var unitUnderTest = new TelemetryCommonProperties(getMACAddress: () => null, userLevelCacheWriter: new NothingCache());
var assignedMachineId = unitUnderTest.GetTelemetryCommonProperties()["Machine ID"];
Guid.TryParse(assignedMachineId, out var _).Should().BeTrue("it should be a guid");
}
private class NothingCache : IUserLevelCacheWriter
{
public string RunWithCache(string cacheKey, Func<string> getValueToCache)
{
return getValueToCache();
}
}
}
}

View file

@ -0,0 +1,38 @@
// 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.Collections.Concurrent;
using System.Collections.Generic;
using Microsoft.DotNet.Cli;
namespace Microsoft.DotNet.Cli.MSBuild.IntegrationTests
{
public class FakeRecordEventNameTelemetry
{
public bool Enabled { get; set; }
public string EventName { get; set; }
public void TrackEvent(string eventName,
IDictionary<string, string> properties,
IDictionary<string, double> measurements)
{
LogEntries.Add(
new LogEntry
{
EventName = eventName,
Measurement = measurements,
Properties = properties
});
}
public ConcurrentBag<LogEntry> LogEntries { get; set; } = new ConcurrentBag<LogEntry>();
public class LogEntry
{
public string EventName { get; set; }
public IDictionary<string, string> Properties { get; set; }
public IDictionary<string, double> Measurement { get; set; }
}
}
}