Fix "runtime description" used for internal analytics (#519)

* remove use of RuntimeInformation in NativeMethods and ContainerInfo

* add new FrameworkDescription class

* use new FrameworkDescription class from Api class to set http headers

* clean up xml-doc comments

* add "OS" to Resharper's spell-check abbreviations

* change Resharper's editor style: don't _always_ chop a ternary expression into multiple lines

* revert change to nuget package reference

* refactor code into GetVersionFromAssemblyAttributes()

* make nuget reference private and add comment

* remove unnecessary call to GetTypeInfo()

* coalesce more null values to "unknown"

* add comment

* move the "try/catch" inside the "if" instead of the other way around

* remove try/catch, update comment

* update log messages

* simplify ToString()
This commit is contained in:
Lucas Pimentel-Ordyna 2019-10-04 16:32:23 -04:00 committed by GitHub
parent 07e420f92d
commit fb0ac61098
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 282 additions and 45 deletions

View File

@ -35,7 +35,7 @@
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_CHAINED_METHOD_CALLS/@EntryValue">CHOP_IF_LONG</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LINES/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_TERNARY_EXPR_STYLE/@EntryValue">CHOP_ALWAYS</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CppUseAuto/AutoWasChosen/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/ShowEditorConfigStatusBarIndicator/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/EncapsulateField/UpdateExternalUsagesOnly/@EntryValue">False</s:Boolean>
@ -43,6 +43,7 @@
<s:String x:Key="/Default/CodeStyle/Generate/=Implementations/Options/=Async/@EntryIndexedValue">True</s:String>
<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpAutoNaming/IsNamingAutoDetectionCompleted/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpAutoNaming/IsNotificationDisabled/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OS/@EntryIndexedValue">OS</s:String>
<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpNaming/ApplyAutoDetectedRules/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/AutoDetectedNamingRules/=Constants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/AutoDetectedNamingRules/=EnumMember/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>

View File

@ -1,3 +1,4 @@
using System;
using System.Runtime.InteropServices;
// ReSharper disable MemberHidesStaticFromOuterClass
@ -5,9 +6,11 @@ namespace Datadog.Trace.ClrProfiler
{
internal static class NativeMethods
{
private static readonly bool IsWindows = string.Equals(FrameworkDescription.Create().OSPlatform, "Windows", StringComparison.OrdinalIgnoreCase);
public static bool IsProfilerAttached()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (IsWindows)
{
return Windows.IsProfilerAttached();
}

View File

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Datadog.Trace.Containers;
using Datadog.Trace.Logging;
@ -15,7 +14,7 @@ namespace Datadog.Trace.Agent
{
private const string TracesPath = "/v0.4/traces";
private static readonly ILog Log = LogProvider.For<Api>();
private static readonly ILog Log = LogProvider.GetCurrentClassLogger();
private static readonly SerializationContext SerializationContext = new SerializationContext();
private static readonly SpanMessagePackSerializer Serializer = new SpanMessagePackSerializer(SerializationContext);
@ -41,20 +40,31 @@ namespace Datadog.Trace.Agent
_tracesEndpoint = new Uri(baseEndpoint, TracesPath);
GetFrameworkDescription(out string frameworkName, out string frameworkVersion);
var tracerVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
var containerId = ContainerInfo.GetContainerId();
_client.DefaultRequestHeaders.Add(AgentHttpHeaderNames.Language, ".NET");
// report runtime details
_client.DefaultRequestHeaders.Add(AgentHttpHeaderNames.Language, ".NET");
_client.DefaultRequestHeaders.Add(AgentHttpHeaderNames.LanguageInterpreter, frameworkName);
_client.DefaultRequestHeaders.Add(AgentHttpHeaderNames.LanguageVersion, frameworkVersion);
try
{
var frameworkDescription = FrameworkDescription.Create();
if (frameworkDescription != null)
{
_client.DefaultRequestHeaders.Add(AgentHttpHeaderNames.LanguageInterpreter, frameworkDescription.Name);
_client.DefaultRequestHeaders.Add(AgentHttpHeaderNames.LanguageVersion, frameworkDescription.ProductVersion);
}
}
catch (Exception e)
{
Log.ErrorException("Error getting framework description", e);
}
// report Tracer version
var tracerVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
_client.DefaultRequestHeaders.Add(AgentHttpHeaderNames.TracerVersion, tracerVersion);
// report container id (only Linux containers supported for now)
var containerId = ContainerInfo.GetContainerId();
if (containerId != null)
{
_client.DefaultRequestHeaders.Add(AgentHttpHeaderNames.ContainerId, containerId);
@ -145,20 +155,6 @@ namespace Datadog.Trace.Agent
return uniqueTraceIds;
}
private static void GetFrameworkDescription(out string name, out string version)
{
// RuntimeInformation.FrameworkDescription returns string like ".NET Framework 4.7.2" or ".NET Core 2.1",
// we want to split the runtime from the version so we can report them as separate values
string frameworkDescription = RuntimeInformation.FrameworkDescription;
int index = RuntimeInformation.FrameworkDescription.LastIndexOf(' ');
// everything before the last space
name = frameworkDescription.Substring(0, index).Trim();
// everything after the last space
version = frameworkDescription.Substring(index).Trim();
}
internal class ApiResponse
{
[JsonProperty("rate_by_service")]

View File

@ -15,13 +15,11 @@ namespace Datadog.Trace
/// <summary>
/// The interpreter for the given language, e.g. ".NET Framework" or ".NET Core".
/// The value of <see cref="RuntimeInformation.FrameworkDescription"/>.
/// </summary>
public const string LanguageInterpreter = "Datadog-Meta-Lang-Interpreter";
/// <summary>
/// The interpreter version for the given language, e.g. "4.7.2" for .NET Framework or "2.1" for .NET Core.
/// The value of <see cref="RuntimeInformation.FrameworkDescription"/>.
/// </summary>
public const string LanguageVersion = "Datadog-Meta-Lang-Version";

View File

@ -59,21 +59,12 @@ namespace Datadog.Trace.Containers
private static string GetContainerIdInternal()
{
bool isLinux;
try
{
isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
}
catch (Exception ex)
{
Log.WarnException("Unable to determine OS. Will not report container id.", ex);
return null;
}
var isLinux = string.Equals(FrameworkDescription.Create().OSPlatform, "Linux", StringComparison.OrdinalIgnoreCase);
try
{
if (isLinux && File.Exists(ControlGroupsFilePath))
if (isLinux &&
File.Exists(ControlGroupsFilePath))
{
var lines = File.ReadLines(ControlGroupsFilePath);
return ParseCgroupLines(lines);
@ -82,7 +73,6 @@ namespace Datadog.Trace.Containers
catch (Exception ex)
{
Log.WarnException("Error reading cgroup file. Will not report container id.", ex);
return null;
}
return null;

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- NuGet -->
@ -13,10 +13,16 @@
<Reference Include="System.Web" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<!--
This reference allows us to build the code without precompiler directives,
but the logic at runtime will never try to use the Registry if it's not available
-->
<PackageReference Include="Microsoft.Win32.Registry" Version="4.6.0" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="LibLog" Version="5.0.6">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="LibLog" Version="5.0.6" PrivateAssets="all" />
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
@ -24,4 +30,4 @@
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Version="4.3.0" />
</ItemGroup>
</Project>
</Project>

View File

@ -0,0 +1,243 @@
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Datadog.Trace.Logging;
using Microsoft.Win32;
namespace Datadog.Trace
{
internal class FrameworkDescription
{
private static readonly ILog Log = LogProvider.GetCurrentClassLogger();
private static readonly Assembly RootAssembly = typeof(object).Assembly;
private static readonly Tuple<int, string>[] DotNetFrameworkVersionMapping =
{
// highest known value is 528049
Tuple.Create(528050, "4.8+"),
// known min value for each framework version
Tuple.Create(528040, "4.8"),
Tuple.Create(461808, "4.7.2"),
Tuple.Create(461308, "4.7.1"),
Tuple.Create(460798, "4.7"),
Tuple.Create(394802, "4.6.2"),
Tuple.Create(394254, "4.6.1"),
Tuple.Create(393295, "4.6"),
Tuple.Create(379893, "4.5.2"),
Tuple.Create(378675, "4.5.1"),
Tuple.Create(378389, "4.5"),
};
private FrameworkDescription(
string name,
string productVersion,
string osPlatform,
string osArchitecture,
string processArchitecture)
{
Name = name;
ProductVersion = productVersion;
OSPlatform = osPlatform;
OSArchitecture = osArchitecture;
ProcessArchitecture = processArchitecture;
}
public string Name { get; }
public string ProductVersion { get; }
public string OSPlatform { get; }
public string OSArchitecture { get; }
public string ProcessArchitecture { get; }
public static FrameworkDescription Create()
{
var assemblyName = RootAssembly.GetName();
if (string.Equals(assemblyName.Name, "mscorlib", StringComparison.OrdinalIgnoreCase))
{
// .NET Framework
return new FrameworkDescription(
".NET Framework",
GetNetFrameworkVersion() ?? "unknown",
"Windows",
Environment.Is64BitOperatingSystem ? "x64" : "x86",
Environment.Is64BitProcess ? "x64" : "x86");
}
// .NET Core
return CreateFromRuntimeInformation();
}
public override string ToString()
{
// examples:
// .NET Framework 4.8 x86 on Windows x64
// .NET Core 3.0.0 x64 on Linux x64
return $"{Name} {ProductVersion} {ProcessArchitecture} on {OSPlatform} {OSArchitecture}";
}
private static FrameworkDescription CreateFromRuntimeInformation()
{
string frameworkName = null;
string osPlatform = null;
try
{
// RuntimeInformation.FrameworkDescription returns a string like ".NET Framework 4.7.2" or ".NET Core 2.1",
// we want to return everything before the last space
string frameworkDescription = RuntimeInformation.FrameworkDescription;
int index = frameworkDescription.LastIndexOf(' ');
frameworkName = frameworkDescription.Substring(0, index).Trim();
}
catch (Exception e)
{
Log.ErrorException("Error getting framework name from RuntimeInformation", e);
}
if (RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
{
osPlatform = "Windows";
}
else if (RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux))
{
osPlatform = "Linux";
}
else if (RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX))
{
osPlatform = "MacOS";
}
return new FrameworkDescription(
frameworkName ?? "unknown",
GetNetCoreVersion() ?? "unknown",
osPlatform ?? "unknown",
RuntimeInformation.OSArchitecture.ToString().ToLowerInvariant(),
RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant());
}
private static string GetNetFrameworkVersion()
{
string productVersion = null;
try
{
object registryValue;
using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default))
using (var subKey = baseKey.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\"))
{
registryValue = subKey?.GetValue("Release");
}
if (registryValue is int release)
{
// find the known version on the list with the largest release number
// that is lower than or equal to the release number in the Windows Registry
productVersion = DotNetFrameworkVersionMapping.FirstOrDefault(t => release >= t.Item1)?.Item2;
}
}
catch (Exception e)
{
Log.ErrorException("Error getting .NET Framework version from Windows Registry", e);
}
if (productVersion == null)
{
// if we fail to extract version from assembly path,
// fall back to the [AssemblyInformationalVersion] or [AssemblyFileVersion]
productVersion = GetVersionFromAssemblyAttributes();
}
return productVersion;
}
private static string GetNetCoreVersion()
{
string productVersion = null;
if (Environment.Version.Major == 3 || Environment.Version.Major >= 5)
{
// Environment.Version returns "4.x" in .NET Core 2.x,
// but it is correct since .NET Core 3.0.0
productVersion = Environment.Version.ToString();
}
if (productVersion == null)
{
try
{
// try to get product version from assembly path
Match match = Regex.Match(
RootAssembly.CodeBase,
@"/[^/]*microsoft\.netcore\.app/(\d+\.\d+\.\d+[^/]*)/",
RegexOptions.IgnoreCase);
if (match.Success && match.Groups.Count > 0 && match.Groups[1].Success)
{
productVersion = match.Groups[1].Value;
}
}
catch (Exception e)
{
Log.ErrorException("Error getting .NET Core version from assembly path", e);
}
}
if (productVersion == null)
{
// if we fail to extract version from assembly path,
// fall back to the [AssemblyInformationalVersion] or [AssemblyFileVersion]
productVersion = GetVersionFromAssemblyAttributes();
}
if (productVersion == null)
{
// at this point, everything else has failed (this is probably the same as [AssemblyFileVersion] above)
productVersion = Environment.Version.ToString();
}
return productVersion;
}
private static string GetVersionFromAssemblyAttributes()
{
string productVersion = null;
try
{
// if we fail to extract version from assembly path, fall back to the [AssemblyInformationalVersion],
var informationalVersionAttribute = (AssemblyInformationalVersionAttribute)RootAssembly.GetCustomAttribute(typeof(AssemblyInformationalVersionAttribute));
// split remove the commit hash from pre-release versions
productVersion = informationalVersionAttribute?.InformationalVersion?.Split('+')[0];
}
catch (Exception e)
{
Log.ErrorException("Error getting framework version from [AssemblyInformationalVersion]", e);
}
if (productVersion == null)
{
try
{
// and if that fails, try [AssemblyFileVersion]
var fileVersionAttribute = (AssemblyFileVersionAttribute)RootAssembly.GetCustomAttribute(typeof(AssemblyFileVersionAttribute));
productVersion = fileVersionAttribute?.Version;
}
catch (Exception e)
{
Log.ErrorException("Error getting framework version from [AssemblyFileVersion]", e);
}
}
return productVersion;
}
}
}