diff --git a/.gitignore b/.gitignore index 7e56f9616..5ccf3a317 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ # Test results *-testResults.xml +# Multicore JIT Optimization profiles +**/optimizationdata/dotnet + # NuGet keeps dropping Library/ diff --git a/src/dotnet/MulticoreJitActivator.cs b/src/dotnet/MulticoreJitActivator.cs new file mode 100644 index 000000000..0663b697b --- /dev/null +++ b/src/dotnet/MulticoreJitActivator.cs @@ -0,0 +1,46 @@ +// 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.Runtime.Loader; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools.Common; +using Microsoft.Extensions.PlatformAbstractions; +using NuGet.Frameworks; + +namespace Microsoft.DotNet.Cli +{ + public class MulticoreJitActivator + { + public bool TryActivateMulticoreJit() + { + var disableMulticoreJit = IsMulticoreJitDisabled(); + + if (disableMulticoreJit) + { + return false; + } + + StartCliProfileOptimization(); + + return true; + } + + private bool IsMulticoreJitDisabled() + { + return Environment.GetEnvironmentVariable("DOTNET_DISABLE_MULTICOREJIT") == "1"; + } + + private void StartCliProfileOptimization() + { + var profileOptimizationRootPath = new MulticoreJitProfilePathCalculator().MulticoreJitProfilePath; + + PathUtility.EnsureDirectory(profileOptimizationRootPath); + + AssemblyLoadContext.Default.SetProfileOptimizationRoot(profileOptimizationRootPath); + + AssemblyLoadContext.Default.StartProfileOptimization("dotnet"); + } + } +} \ No newline at end of file diff --git a/src/dotnet/Program.cs b/src/dotnet/Program.cs index 55a16a3d8..fd8fcbb31 100644 --- a/src/dotnet/Program.cs +++ b/src/dotnet/Program.cs @@ -42,11 +42,9 @@ namespace Microsoft.DotNet.Cli public static int Main(string[] args) { DebugHelper.HandleDebugSwitch(ref args); - - AssemblyLoadContext.Default.SetProfileOptimizationRoot( - new MulticoreJitProfilePathCalculator().MulticoreJitProfilePath); - AssemblyLoadContext.Default.StartProfileOptimization("dotnet"); - + + new MulticoreJitActivator().TryActivateMulticoreJit(); + if (Env.GetEnvironmentVariableAsBool("DOTNET_CLI_CAPTURE_TIMING", false)) { PerfTrace.Enabled = true; diff --git a/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs b/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs index 7b181f944..97d80a577 100644 --- a/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs +++ b/test/dotnet.Tests/GivenThatIWantToManageMulticoreJIT.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information.  using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.TestFramework; using Microsoft.DotNet.Tools.Test.Utilities; using Microsoft.Extensions.PlatformAbstractions; using System; @@ -16,52 +17,101 @@ namespace Microsoft.DotNet.Tests public class GivenThatIWantToManageMulticoreJIT : TestBase { ITestOutputHelper _output; - private const string OptimizationProfileFileName = "dotnet."; - private readonly string _optimizationProfileFilePath; + private const string OptimizationProfileFileName = "dotnet"; public GivenThatIWantToManageMulticoreJIT(ITestOutputHelper output) { _output = output; - _optimizationProfileFilePath = GetOptimizationProfileFilePath(); } [Fact] - public void When_invoked_it_writes_optimization_data_to_the_profile_root() + public void When_invoked_then_dotnet_writes_optimization_data_to_the_profile_root() { - var testStartTime = DateTime.UtcNow; - + var testDirectory = TestAssetsManager.CreateTestDirectory(); + var testStartTime = GetTruncatedDateTime(); + new TestCommand("dotnet") - .Execute("--version"); + .WithUserProfileRoot(testDirectory.Path) + .ExecuteWithCapturedOutput("--help"); + - File.Exists(_optimizationProfileFilePath) + var optimizationProfileFilePath = GetOptimizationProfileFilePath(testDirectory.Path); + + File.Exists(optimizationProfileFilePath) .Should().BeTrue("Because dotnet CLI creates it after each run"); - File.GetLastWriteTimeUtc(_optimizationProfileFilePath) - .Should().BeOnOrAfter(testStartTime, "Because dotnet CLI was executed after that time."); + File.GetLastWriteTimeUtc(optimizationProfileFilePath) + .Should().BeOnOrAfter(testStartTime, "Because dotnet CLI was executed after that time"); } - private static string GetOptimizationProfileFilePath() + [Fact] + public void When_invoked_with_MulticoreJit_disabled_then_dotnet_does_not_writes_optimization_data_to_the_profile_root() { - Console.WriteLine(GetOptimizationRootPath(GetDotnetVersion())); - return Path.Combine(GetOptimizationRootPath(GetDotnetVersion()), + var testDirectory = TestAssetsManager.CreateTestDirectory(); + var testStartTime = GetTruncatedDateTime(); + + new TestCommand("dotnet") + .WithUserProfileRoot(testDirectory.Path) + .WithEnvironmentVariable("DOTNET_DISABLE_MULTICOREJIT", "1") + .ExecuteWithCapturedOutput("--help"); + + + var optimizationProfileFilePath = GetOptimizationProfileFilePath(testDirectory.Path); + + File.Exists(optimizationProfileFilePath) + .Should().BeFalse("Because multicore JIT is disabled"); + } + + [Fact] + public void When_the_profile_root_is_undefined_then_dotnet_does_not_crash() + { + var testDirectory = TestAssetsManager.CreateTestDirectory(); + var testStartTime = GetTruncatedDateTime(); + + var optimizationProfileFilePath = GetOptimizationProfileFilePath(testDirectory.Path); + + new TestCommand("dotnet") + .WithUserProfileRoot("") + .ExecuteWithCapturedOutput("--help") + .Should().Pass(); + } + + [Fact] + public void When_cli_repo_builds_then_dotnet_writes_optimization_data_to_the_default_profile_root() + { + var optimizationProfileFilePath = GetOptimizationProfileFilePath(); + + File.Exists(optimizationProfileFilePath) + .Should().BeTrue("Because the dotnet building dotnet writes to the default root"); + } + + private static string GetOptimizationProfileFilePath(string userHomePath = null) + { + return Path.Combine( + GetUserProfileRoot(userHomePath), + GetOptimizationRootPath(GetDotnetVersion()), OptimizationProfileFileName); } + + private static string GetUserProfileRoot(string overrideUserProfileRoot = null) + { + if (overrideUserProfileRoot != null) + { + return overrideUserProfileRoot; + } + + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Environment.GetEnvironmentVariable("LocalAppData") + : Environment.GetEnvironmentVariable("HOME"); + } private static string GetOptimizationRootPath(string version) { + var rid = PlatformServices.Default.Runtime.GetRuntimeIdentifier(); + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? GetWindowsProfileRoot(version) - : GetNonWindowsProfileRoot(version); - } - - private static string GetWindowsProfileRoot(string version) - { - return $@"{(Environment.GetEnvironmentVariable("LocalAppData"))}\Microsoft\dotnet\sdk\{version}\optimizationdata"; - } - - private static string GetNonWindowsProfileRoot(string version) - { - return $"{(Environment.GetEnvironmentVariable("HOME"))}/.dotnet/sdk/{version}/optimizationdata"; + ? $@"Microsoft\dotnet\sdk\{version}{rid}\optimizationdata" + : $@".dotnet/sdk/{version}/{rid}/optimizationdata"; } private static string GetDotnetVersion() @@ -72,5 +122,12 @@ namespace Microsoft.DotNet.Tests .StdOut .Trim(); } + + private static DateTime GetTruncatedDateTime() + { + var dt = DateTime.UtcNow; + + return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, 0, dt.Kind); + } } } diff --git a/test/dotnet.Tests/TestCommandExtensions.cs b/test/dotnet.Tests/TestCommandExtensions.cs new file mode 100644 index 000000000..5cd99dab1 --- /dev/null +++ b/test/dotnet.Tests/TestCommandExtensions.cs @@ -0,0 +1,31 @@ +// 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 Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools.Test.Utilities; +using Microsoft.Extensions.PlatformAbstractions; +using System; +using System.IO; +using System.Runtime.InteropServices; +using Xunit; +using Xunit.Abstractions; +using FluentAssertions; + +namespace Microsoft.DotNet.Tests +{ + public static class TestCommandExtensions + { + public static TestCommand WithUserProfileRoot(this TestCommand testCommand, string path) + { + var userProfileEnvironmentVariableName = GetUserProfileEnvironmentVariableName(); + return testCommand.WithEnvironmentVariable(userProfileEnvironmentVariableName, path); + } + + private static string GetUserProfileEnvironmentVariableName() + { + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? "LocalAppData" + : "HOME"; + } + } +}