Implement JitInfo API (#55046)
authorJohn Salem <josalem@microsoft.com>
Wed, 14 Jul 2021 17:47:10 +0000 (10:47 -0700)
committerGitHub <noreply@github.com>
Wed, 14 Jul 2021 17:47:10 +0000 (10:47 -0700)
24 files changed:
src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs
src/coreclr/System.Private.CoreLib/src/System/Runtime/JitInfo.CoreCLR.cs [new file with mode: 0644]
src/coreclr/vm/ecalllist.h
src/coreclr/vm/jitinterface.cpp
src/coreclr/vm/jitinterface.h
src/coreclr/vm/util.cpp
src/coreclr/vm/util.hpp
src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSource.cs
src/libraries/System.Private.CoreLib/src/System/Runtime/JitInfo.cs [new file with mode: 0644]
src/libraries/System.Runtime/ref/System.Runtime.cs
src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj
src/libraries/System.Runtime/tests/System/Runtime/JitInfoTests.cs [new file with mode: 0644]
src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj
src/mono/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipe.Mono.cs
src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs
src/mono/System.Private.CoreLib/src/System/Runtime/JitInfo.Mono.cs [new file with mode: 0644]
src/mono/mono/metadata/icall-eventpipe.c
src/mono/mono/metadata/object-internals.h
src/mono/mono/mini/mini.h
src/tests/tracing/eventcounter/regression-25709.cs
src/tests/tracing/eventcounter/runtimecounters.cs

index b422326..1075a70 100644 (file)
     <Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\TypeDependencyAttribute.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Runtime\DependentHandle.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Runtime\GCSettings.CoreCLR.cs" />
+    <Compile Include="$(BclSourcesRoot)\System\Runtime\JitInfo.CoreCLR.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ComTypes\IEnumerable.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ComTypes\IEnumerator.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\DynamicInterfaceCastableHelpers.cs" />
index 3d243e2..558e255 100644 (file)
@@ -349,12 +349,6 @@ namespace System.Runtime.CompilerServices
                 }
             }
         }
-
-        [MethodImpl(MethodImplOptions.InternalCall)]
-        internal static extern long GetILBytesJitted();
-
-        [MethodImpl(MethodImplOptions.InternalCall)]
-        internal static extern int GetMethodsJittedCount();
     }
     // Helper class to assist with unsafe pinning of arbitrary objects.
     // It's used by VM code.
diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/JitInfo.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/JitInfo.CoreCLR.cs
new file mode 100644 (file)
index 0000000..f1dafd5
--- /dev/null
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Internal.Runtime.CompilerServices;
+
+namespace System.Runtime
+{
+    public static partial class JitInfo
+    {
+        /// <summary>
+        /// Get the number of bytes of IL that have been compiled. If <paramref name="currentThread"/> is true,
+        /// then this value is scoped to the current thread, otherwise, this is a global value.
+        /// </summary>
+        /// <param name="currentThread">Whether the returned value should be specific to the current thread. Default: false</param>
+        /// <returns>The number of bytes of IL the JIT has compiled.</returns>
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        public static extern long GetCompiledILBytes(bool currentThread = false);
+
+        /// <summary>
+        /// Get the number of methods that have been compiled. If <paramref name="currentThread"/> is true,
+        /// then this value is scoped to the current thread, otherwise, this is a global value.
+        /// </summary>
+        /// <param name="currentThread">Whether the returned value should be specific to the current thread. Default: false</param>
+        /// <returns>The number of methods the JIT has compiled.</returns>
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        public static extern long GetCompiledMethodCount(bool currentThread = false);
+
+        // Normalized to 100ns ticks on vm side
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern long GetCompilationTimeInTicks(bool currentThread = false);
+    }
+}
\ No newline at end of file
index ea3f65d..738ac9a 100644 (file)
@@ -834,6 +834,12 @@ FCFuncStart(gInterlockedFuncs)
     QCFuncElement("_MemoryBarrierProcessWide", COMInterlocked::MemoryBarrierProcessWide)
 FCFuncEnd()
 
+FCFuncStart(gJitInfoFuncs)
+    FCFuncElement("GetCompiledILBytes", GetCompiledILBytes)
+    FCFuncElement("GetCompiledMethodCount", GetCompiledMethodCount)
+    FCFuncElement("GetCompilationTimeInTicks", GetCompilationTimeInTicks)
+FCFuncEnd()
+
 FCFuncStart(gVarArgFuncs)
     FCFuncElementSig(COR_CTOR_METHOD_NAME, &gsig_IM_IntPtr_PtrVoid_RetVoid, VarArgsNative::Init2)
     FCFuncElementSig(COR_CTOR_METHOD_NAME, &gsig_IM_IntPtr_RetVoid, VarArgsNative::Init)
@@ -879,8 +885,6 @@ FCFuncStart(gRuntimeHelpers)
     QCFuncElement("AllocateTypeAssociatedMemory", RuntimeTypeHandle::AllocateTypeAssociatedMemory)
     FCFuncElement("AllocTailCallArgBuffer", TailCallHelp::AllocTailCallArgBuffer)
     FCFuncElement("GetTailCallInfo", TailCallHelp::GetTailCallInfo)
-    FCFuncElement("GetILBytesJitted", GetJittedBytes)
-    FCFuncElement("GetMethodsJittedCount", GetJittedMethodsCount)
 FCFuncEnd()
 
 FCFuncStart(gMngdFixedArrayMarshalerFuncs)
@@ -1158,6 +1162,7 @@ FCClassElement("IReflect", "System.Reflection", gStdMngIReflectFuncs)
 FCClassElement("InterfaceMarshaler", "System.StubHelpers", gInterfaceMarshalerFuncs)
 #endif
 FCClassElement("Interlocked", "System.Threading", gInterlockedFuncs)
+FCClassElement("JitInfo", "System.Runtime", gJitInfoFuncs)
 #if TARGET_UNIX
 FCClassElement("Kernel32", "", gPalKernel32Funcs)
 #endif
index 882e2c2..b19fa36 100644 (file)
@@ -102,23 +102,47 @@ GARY_IMPL(VMHELPDEF, hlpDynamicFuncTable, DYNAMIC_CORINFO_HELP_COUNT);
 
 #else // DACCESS_COMPILE
 
-uint64_t g_cbILJitted = 0;
-uint32_t g_cMethodsJitted = 0;
+Volatile<int64_t> g_cbILJitted = 0;
+Volatile<int64_t> g_cMethodsJitted = 0;
+Volatile<int64_t> g_c100nsTicksInJit = 0;
+thread_local int64_t t_cbILJittedForThread = 0;
+thread_local int64_t t_cMethodsJittedForThread = 0;
+thread_local int64_t t_c100nsTicksInJitForThread = 0;
+
+// This prevents tearing of 64 bit values on 32 bit systems
+static inline
+int64_t AtomicLoad64WithoutTearing(int64_t volatile *valueRef)
+{
+    WRAPPER_NO_CONTRACT;
+#if TARGET_64BIT
+    return VolatileLoad(valueRef);
+#else
+    return InterlockedCompareExchangeT((LONG64 volatile *)valueRef, (LONG64)0, (LONG64)0);
+#endif // TARGET_64BIT
+}
 
 #ifndef CROSSGEN_COMPILE
-FCIMPL0(INT64, GetJittedBytes)
+FCIMPL1(INT64, GetCompiledILBytes, CLR_BOOL currentThread)
 {
     FCALL_CONTRACT;
 
-    return g_cbILJitted;
+    return currentThread ? t_cbILJittedForThread : AtomicLoad64WithoutTearing(&g_cbILJitted);
 }
 FCIMPLEND
 
-FCIMPL0(INT32, GetJittedMethodsCount)
+FCIMPL1(INT64, GetCompiledMethodCount, CLR_BOOL currentThread)
 {
     FCALL_CONTRACT;
 
-    return g_cMethodsJitted;
+    return currentThread ? t_cMethodsJittedForThread : AtomicLoad64WithoutTearing(&g_cMethodsJitted);
+}
+FCIMPLEND
+
+FCIMPL1(INT64, GetCompilationTimeInTicks, CLR_BOOL currentThread)
+{
+    FCALL_CONTRACT;
+
+    return currentThread ? t_c100nsTicksInJitForThread : AtomicLoad64WithoutTearing(&g_c100nsTicksInJit);
 }
 FCIMPLEND
 #endif
@@ -13030,9 +13054,13 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config,
     MethodDesc* ftn = nativeCodeVersion.GetMethodDesc();
 
     PCODE ret = NULL;
+    NormalizedTimer timer;
+    int64_t c100nsTicksInJit = 0;
 
     COOPERATIVE_TRANSITION_BEGIN();
 
+    timer.Start();
+
 #ifdef FEATURE_PREJIT
 
     if (g_pConfig->RequireZaps() == EEConfig::REQUIRE_ZAPS_ALL &&
@@ -13394,8 +13422,17 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config,
         printf(".");
 #endif // _DEBUG
 
-    FastInterlockExchangeAddLong((LONG64*)&g_cbILJitted, methodInfo.ILCodeSize);
-    FastInterlockIncrement((LONG*)&g_cMethodsJitted);
+    timer.Stop();
+    c100nsTicksInJit = timer.Elapsed100nsTicks();
+
+    InterlockedExchangeAdd64((LONG64*)&g_c100nsTicksInJit, c100nsTicksInJit);
+    t_c100nsTicksInJitForThread += c100nsTicksInJit;
+
+    InterlockedExchangeAdd64((LONG64*)&g_cbILJitted, methodInfo.ILCodeSize);
+    t_cbILJittedForThread += methodInfo.ILCodeSize;
+
+    InterlockedIncrement64((LONG64*)&g_cMethodsJitted);
+    t_cMethodsJittedForThread++;
 
     COOPERATIVE_TRANSITION_END();
     return ret;
index e071d07..a84a948 100644 (file)
@@ -1149,8 +1149,17 @@ CORJIT_FLAGS GetDebuggerCompileFlags(Module* pModule, CORJIT_FLAGS flags);
 
 bool __stdcall TrackAllocationsEnabled();
 
-FCDECL0(INT64, GetJittedBytes);
-FCDECL0(INT32, GetJittedMethodsCount);
+
+extern Volatile<int64_t> g_cbILJitted;
+extern Volatile<int64_t> g_cMethodsJitted;
+extern Volatile<int64_t> g_c100nsTicksInJit;
+extern thread_local int64_t t_cbILJittedForThread;
+extern thread_local int64_t t_cMethodsJittedForThread;
+extern thread_local int64_t t_c100nsTicksInJitForThread;
+
+FCDECL1(INT64, GetCompiledILBytes, CLR_BOOL currentThread);
+FCDECL1(INT64, GetCompiledMethodCount, CLR_BOOL currentThread);
+FCDECL1(INT64, GetCompilationTimeInTicks, CLR_BOOL currentThread);
 
 #endif // JITINTERFACE_H
 
index 64130d6..fe51593 100644 (file)
@@ -2272,4 +2272,6 @@ HRESULT GetFileVersion(                     // S_OK or error
 }
 #endif // !TARGET_UNIX
 
+Volatile<double> NormalizedTimer::s_frequency = -1.0;
+
 #endif // !DACCESS_COMPILE
index 19f7932..4da40a3 100644 (file)
@@ -918,6 +918,83 @@ public:
     static BOOL nativeIsDigit(WCHAR c);
 };
 
+// ======================================================================================
+// Simple, reusable 100ns timer for normalizing ticks. For use in Q/FCalls to avoid discrepency with
+// tick frequency between native and managed.
+class NormalizedTimer
+{
+private:
+    static const int64_t NormalizedTicksPerSecond = 10000000 /* 100ns ticks per second (1e7) */;
+    static Volatile<double> s_frequency;
+
+    LARGE_INTEGER startTimestamp;
+    LARGE_INTEGER stopTimestamp;
+
+#if _DEBUG
+    bool isRunning = false;
+#endif // _DEBUG
+
+public:
+    NormalizedTimer()
+    {
+        LIMITED_METHOD_CONTRACT;
+        if (s_frequency.Load() == -1)
+        {
+            double frequency;
+            LARGE_INTEGER qpfValue;
+            QueryPerformanceFrequency(&qpfValue);
+            frequency = static_cast<double>(qpfValue.QuadPart);
+            frequency /= NormalizedTicksPerSecond;
+            s_frequency.Store(frequency);
+        }
+
+        startTimestamp.QuadPart = 0;
+        startTimestamp.QuadPart = 0;
+    }
+
+    // ======================================================================================
+    // Start the timer
+    inline
+    void Start()
+    {
+        LIMITED_METHOD_CONTRACT;
+        _ASSERTE(!isRunning);
+        QueryPerformanceCounter(&startTimestamp);
+
+#if _DEBUG
+        isRunning = true;
+#endif // _DEBUG
+    }
+
+    // ======================================================================================
+    // stop the timer. If called before starting, sets the start time to the same as the stop
+    inline
+    void Stop()
+    {
+        LIMITED_METHOD_CONTRACT;
+        _ASSERTE(isRunning);
+        QueryPerformanceCounter(&stopTimestamp);
+
+#if _DEBUG
+        isRunning = false;
+#endif // _DEBUG
+    }
+
+    // ======================================================================================
+    // Return elapsed ticks. This will stop a running timer.
+    // Will return 0 if called out of order.
+    // Only recalculated this value if it has been stopped/started since previous calculation.
+    inline
+    int64_t Elapsed100nsTicks()
+    {
+        LIMITED_METHOD_CONTRACT;
+        _ASSERTE(!isRunning);
+        _ASSERTE(startTimestamp.QuadPart > 0);
+        _ASSERTE(stopTimestamp.QuadPart > 0);
+        return static_cast<int64_t>((stopTimestamp.QuadPart - startTimestamp.QuadPart) / s_frequency);
+    }
+};
+
 #ifdef _DEBUG
 #define FORCEINLINE_NONDEBUG
 #else
index 90f21d0..c1bb185 100644 (file)
@@ -27,6 +27,7 @@ namespace System
         public static bool IsNotMonoRuntime => !IsMonoRuntime;
         public static bool IsMonoInterpreter => GetIsRunningOnMonoInterpreter();
         public static bool IsMonoAOT => Environment.GetEnvironmentVariable("MONO_AOT_MODE") == "aot";
+        public static bool IsNotMonoAOT => Environment.GetEnvironmentVariable("MONO_AOT_MODE") != "aot";
         public static bool IsFreeBSD => RuntimeInformation.IsOSPlatform(OSPlatform.Create("FREEBSD"));
         public static bool IsNetBSD => RuntimeInformation.IsOSPlatform(OSPlatform.Create("NETBSD"));
         public static bool IsAndroid => RuntimeInformation.IsOSPlatform(OSPlatform.Create("ANDROID"));
index 6c1182a..ac992cd 100644 (file)
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Intrinsics\Vector64_1.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Intrinsics\Vector64DebugView_1.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Intrinsics\X86\Enums.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\JitInfo.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Loader\AssemblyLoadContext.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Loader\LibraryNameVariation.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\MemoryFailPoint.cs" />
index fe47aae..6d57c82 100644 (file)
@@ -39,6 +39,7 @@ namespace System.Diagnostics.Tracing
         private PollingCounter? _assemblyCounter;
         private PollingCounter? _ilBytesJittedCounter;
         private PollingCounter? _methodsJittedCounter;
+        private IncrementingPollingCounter? _jitTimeCounter;
 
         public static void Initialize()
         {
@@ -83,8 +84,9 @@ namespace System.Diagnostics.Tracing
                 _lohSizeCounter ??= new PollingCounter("loh-size", this, () => GC.GetGenerationSize(3)) { DisplayName = "LOH Size", DisplayUnits = "B" };
                 _pohSizeCounter ??= new PollingCounter("poh-size", this, () => GC.GetGenerationSize(4)) { DisplayName = "POH (Pinned Object Heap) Size", DisplayUnits = "B" };
                 _assemblyCounter ??= new PollingCounter("assembly-count", this, () => System.Reflection.Assembly.GetAssemblyCount()) { DisplayName = "Number of Assemblies Loaded" };
-                _ilBytesJittedCounter ??= new PollingCounter("il-bytes-jitted", this, () => System.Runtime.CompilerServices.RuntimeHelpers.GetILBytesJitted()) { DisplayName = "IL Bytes Jitted", DisplayUnits = "B" };
-                _methodsJittedCounter ??= new PollingCounter("methods-jitted-count", this, () => System.Runtime.CompilerServices.RuntimeHelpers.GetMethodsJittedCount()) { DisplayName = "Number of Methods Jitted" };
+                _ilBytesJittedCounter ??= new PollingCounter("il-bytes-jitted", this, () => System.Runtime.JitInfo.GetCompiledILBytes()) { DisplayName = "IL Bytes Jitted", DisplayUnits = "B" };
+                _methodsJittedCounter ??= new PollingCounter("methods-jitted-count", this, () => System.Runtime.JitInfo.GetCompiledMethodCount()) { DisplayName = "Number of Methods Jitted" };
+                _jitTimeCounter ??= new IncrementingPollingCounter("time-in-jit", this, () => System.Runtime.JitInfo.GetCompilationTime().TotalMilliseconds) { DisplayName = "Time spent in JIT", DisplayUnits = "ms", DisplayRateTimeScale = new TimeSpan(0, 0, 1) };
             }
 
         }
diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/JitInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/JitInfo.cs
new file mode 100644 (file)
index 0000000..9176bdd
--- /dev/null
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Runtime
+{
+    /// <summary>
+    /// A static class for getting information about the Just In Time compiler.
+    /// </summary>
+    public static partial class JitInfo
+    {
+        /// <summary>
+        /// Get the amount of time the JIT Compiler has spent compiling methods. If <paramref name="currentThread"/> is true,
+        /// then this value is scoped to the current thread, otherwise, this is a global value.
+        /// </summary>
+        /// <param name="currentThread">Whether the returned value should be specific to the current thread. Default: false</param>
+        /// <returns>The amount of time the JIT Compiler has spent compiling methods.</returns>
+        public static TimeSpan GetCompilationTime(bool currentThread = false)
+        {
+            // TimeSpan.FromTicks() takes 100ns ticks
+            return TimeSpan.FromTicks(GetCompilationTimeInTicks(currentThread));
+        }
+    }
+}
\ No newline at end of file
index 76bbc5d..b1baaa1 100644 (file)
@@ -12472,6 +12472,12 @@ namespace System.Runtime
         public static System.Runtime.GCLargeObjectHeapCompactionMode LargeObjectHeapCompactionMode { get { throw null; } set { } }
         public static System.Runtime.GCLatencyMode LatencyMode { get { throw null; } set { } }
     }
+    public static partial class JitInfo
+    {
+        public static long GetCompiledILBytes(bool currentThread=false) { throw null; }
+        public static long GetCompiledMethodCount(bool currentThread=false) { throw null; }
+        public static TimeSpan GetCompilationTime(bool currentThread=false) { throw null; }
+    }
     public sealed partial class MemoryFailPoint : System.Runtime.ConstrainedExecution.CriticalFinalizerObject, System.IDisposable
     {
         public MemoryFailPoint(int sizeInMegabytes) { }
index 3ada2d0..6d888e3 100644 (file)
     <Compile Include="System\Reflection\TypeTests.Get.CornerCases.cs" />
     <Compile Include="System\Reflection\TypeTests.GetMember.cs" />
     <Compile Include="System\Runtime\DependentHandleTests.cs" />
+    <Compile Include="System\Runtime\JitInfoTests.cs" />
     <Compile Include="System\Runtime\MemoryFailPointTests.cs" />
     <Compile Include="System\Runtime\NgenServicingAttributesTests.cs" />
     <Compile Include="System\Runtime\CompilerServices\AttributesTests.cs" />
diff --git a/src/libraries/System.Runtime/tests/System/Runtime/JitInfoTests.cs b/src/libraries/System.Runtime/tests/System/Runtime/JitInfoTests.cs
new file mode 100644 (file)
index 0000000..f0a7eca
--- /dev/null
@@ -0,0 +1,176 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.DotNet.XUnitExtensions;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Threading;
+using Xunit;
+
+namespace System.Runtime.Tests
+{
+    public class JitInfoTests
+    {
+        private long MakeAndInvokeDynamicSquareMethod(int input)
+        {
+            // example ref emit dynamic method from https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/how-to-define-and-execute-dynamic-methods
+            Type[] methodArgs = {typeof(int)};
+
+            DynamicMethod squareIt = new DynamicMethod(
+                "SquareIt",
+                typeof(long),
+                methodArgs,
+                typeof(JitInfoTests).Module);
+
+            ILGenerator il = squareIt.GetILGenerator();
+            il.Emit(OpCodes.Ldarg_0);
+            il.Emit(OpCodes.Conv_I8);
+            il.Emit(OpCodes.Dup);
+            il.Emit(OpCodes.Mul);
+            il.Emit(OpCodes.Ret);
+
+            Func<int, long> invokeSquareIt =
+                (Func<int, long>)
+                squareIt.CreateDelegate(typeof(Func<int, long>));
+
+            return invokeSquareIt(input);
+
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoAOT))] // JitInfo metrics will be 0 in AOT scenarios
+        public void JitInfoIsPopulated()
+        {
+            TimeSpan beforeCompilationTime = System.Runtime.JitInfo.GetCompilationTime();
+            long beforeCompiledILBytes = System.Runtime.JitInfo.GetCompiledILBytes();
+            long beforeCompiledMethodCount = System.Runtime.JitInfo.GetCompiledMethodCount();
+
+            long square = MakeAndInvokeDynamicSquareMethod(100);
+            Assert.True(square == 10000);
+
+            TimeSpan afterCompilationTime = System.Runtime.JitInfo.GetCompilationTime();
+            long afterCompiledILBytes = System.Runtime.JitInfo.GetCompiledILBytes();
+            long afterCompiledMethodCount = System.Runtime.JitInfo.GetCompiledMethodCount();
+
+            if (PlatformDetection.IsMonoInterpreter)
+            {
+                // special case the Mono interpreter where compilation time may be >0 but before and after will most likely be the same
+                Assert.True(beforeCompilationTime >= TimeSpan.Zero, $"Compilation time not greater than 0! ({beforeCompilationTime})");
+                Assert.True(beforeCompiledILBytes >= 0, $"Compiled IL bytes not greater than 0! ({beforeCompiledILBytes})");
+                Assert.True(beforeCompiledMethodCount >= 0, $"Compiled method count not greater than 0! ({beforeCompiledMethodCount})");
+
+                Assert.True(afterCompilationTime >= beforeCompilationTime, $"CompilationTime: after not greater than before! (after: {afterCompilationTime}, before: {beforeCompilationTime})");
+                Assert.True(afterCompiledILBytes >= beforeCompiledILBytes, $"Compiled IL bytes: after not greater than before! (after: {afterCompiledILBytes}, before: {beforeCompiledILBytes})");
+                Assert.True(afterCompiledMethodCount >= beforeCompiledMethodCount, $"Compiled method count: after not greater than before! (after: {afterCompiledMethodCount}, before: {beforeCompiledMethodCount})");
+            }
+            else
+            {
+                Assert.True(beforeCompilationTime > TimeSpan.Zero, $"Compilation time not greater than 0! ({beforeCompilationTime})");
+                Assert.True(beforeCompiledILBytes > 0, $"Compiled IL bytes not greater than 0! ({beforeCompiledILBytes})");
+                Assert.True(beforeCompiledMethodCount > 0, $"Compiled method count not greater than 0! ({beforeCompiledMethodCount})");
+
+                Assert.True(afterCompilationTime > beforeCompilationTime, $"CompilationTime: after not greater than before! (after: {afterCompilationTime}, before: {beforeCompilationTime})");
+                Assert.True(afterCompiledILBytes > beforeCompiledILBytes, $"Compiled IL bytes: after not greater than before! (after: {afterCompiledILBytes}, before: {beforeCompiledILBytes})");
+                Assert.True(afterCompiledMethodCount > beforeCompiledMethodCount, $"Compiled method count: after not greater than before! (after: {afterCompiledMethodCount}, before: {beforeCompiledMethodCount})");
+            }
+        }
+
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsMonoAOT))] // JitInfo metrics will be 0 in AOT scenarios
+        public void JitInfoIsNotPopulated()
+        {
+            TimeSpan beforeCompilationTime = System.Runtime.JitInfo.GetCompilationTime();
+            long beforeCompiledILBytes = System.Runtime.JitInfo.GetCompiledILBytes();
+            long beforeCompiledMethodCount = System.Runtime.JitInfo.GetCompiledMethodCount();
+
+            long square = MakeAndInvokeDynamicSquareMethod(100);
+            Assert.True(square == 10000);
+
+            TimeSpan afterCompilationTime = System.Runtime.JitInfo.GetCompilationTime();
+            long afterCompiledILBytes = System.Runtime.JitInfo.GetCompiledILBytes();
+            long afterCompiledMethodCount = System.Runtime.JitInfo.GetCompiledMethodCount();
+
+            Assert.True(beforeCompilationTime == TimeSpan.Zero, $"Before Compilation time not eqeual to 0! ({beforeCompilationTime})");
+            Assert.True(beforeCompiledILBytes == 0, $"Before Compiled IL bytes not eqeual to 0! ({beforeCompiledILBytes})");
+            Assert.True(beforeCompiledMethodCount == 0, $"Before Compiled method count not eqeual to 0! ({beforeCompiledMethodCount})");
+
+            Assert.True(afterCompilationTime == TimeSpan.Zero, $"After Compilation time not eqeual to 0! ({afterCompilationTime})");
+            Assert.True(afterCompiledILBytes == 0, $"After Compiled IL bytes not eqeual to 0! ({afterCompiledILBytes})");
+            Assert.True(afterCompiledMethodCount == 0, $"After Compiled method count not eqeual to 0! ({afterCompiledMethodCount})");
+        }
+
+        [Fact]
+        [SkipOnMono("Mono does not track thread specific JIT information")]
+        public void JitInfoCurrentThreadIsPopulated()
+        {
+            TimeSpan t1_beforeCompilationTime = TimeSpan.Zero;
+            long t1_beforeCompiledILBytes = 0;
+            long t1_beforeCompiledMethodCount = 0;
+
+            TimeSpan t1_afterCompilationTime = TimeSpan.Zero;
+            long t1_afterCompiledILBytes = 0;
+            long t1_afterCompiledMethodCount = 0;
+
+            TimeSpan t2_beforeCompilationTime = System.Runtime.JitInfo.GetCompilationTime(currentThread: true);
+            long t2_beforeCompiledILBytes = System.Runtime.JitInfo.GetCompiledILBytes(currentThread: true);
+            long t2_beforeCompiledMethodCount = System.Runtime.JitInfo.GetCompiledMethodCount(currentThread: true);
+
+            var t1 = new Thread(() => {
+                t1_beforeCompilationTime = System.Runtime.JitInfo.GetCompilationTime(currentThread: true);
+                t1_beforeCompiledILBytes = System.Runtime.JitInfo.GetCompiledILBytes(currentThread: true);
+                t1_beforeCompiledMethodCount = System.Runtime.JitInfo.GetCompiledMethodCount(currentThread: true);
+                long square = MakeAndInvokeDynamicSquareMethod(100);
+                Assert.True(square == 10000);
+                t1_afterCompilationTime = System.Runtime.JitInfo.GetCompilationTime(currentThread: true);
+                t1_afterCompiledILBytes = System.Runtime.JitInfo.GetCompiledILBytes(currentThread: true);
+                t1_afterCompiledMethodCount = System.Runtime.JitInfo.GetCompiledMethodCount(currentThread: true);
+            });
+
+            t1.Start();
+            t1.Join();
+
+            long square = MakeAndInvokeDynamicSquareMethod(100);
+            Assert.True(square == 10000);
+
+            TimeSpan t2_afterCompilationTime = System.Runtime.JitInfo.GetCompilationTime(currentThread: true);
+            long t2_afterCompiledILBytes = System.Runtime.JitInfo.GetCompiledILBytes(currentThread: true);
+            long t2_afterCompiledMethodCount = System.Runtime.JitInfo.GetCompiledMethodCount(currentThread: true);
+
+            Assert.True(t2_beforeCompilationTime > TimeSpan.Zero, $"Thread 2 Compilation time not greater than 0! ({t2_beforeCompilationTime})");
+            Assert.True(t2_beforeCompiledILBytes > 0, $"Thread 2 Compiled IL bytes not greater than 0! ({t2_beforeCompiledILBytes})");
+            Assert.True(t2_beforeCompiledMethodCount > 0, $"Thread 2 Compiled method count not greater than 0! ({t2_beforeCompiledMethodCount})");
+
+            Assert.True(t2_afterCompilationTime > t2_beforeCompilationTime, $"CompilationTime: after not greater than before! (after: {t2_afterCompilationTime}, before: {t2_beforeCompilationTime})");
+            Assert.True(t2_afterCompiledILBytes > t2_beforeCompiledILBytes, $"Compiled IL bytes: after not greater than before! (after: {t2_afterCompiledILBytes}, before: {t2_beforeCompiledILBytes})");
+            Assert.True(t2_afterCompiledMethodCount > t2_beforeCompiledMethodCount, $"Compiled method count: after not greater than before! (after: {t2_afterCompiledMethodCount}, before: {t2_beforeCompiledMethodCount})");
+
+            Assert.True(t1_beforeCompilationTime > TimeSpan.Zero, $"Thread 1 before compilation time not greater than 0! ({t1_beforeCompilationTime})");
+            Assert.True(t1_beforeCompiledILBytes > 0, $"Thread 1 before compiled IL bytes not greater than 0! ({t1_beforeCompiledILBytes})");
+            Assert.True(t1_beforeCompiledMethodCount > 0, $"Thread 1 before compiled method count not greater than 0! ({t1_beforeCompiledMethodCount})");
+
+            Assert.True(t1_afterCompilationTime > t1_beforeCompilationTime, $"Thread 1 compilation time: after not greater than before! (after: {t1_afterCompilationTime}, before: {t1_beforeCompilationTime})");
+            Assert.True(t1_afterCompiledILBytes > t1_beforeCompiledILBytes, $"Thread 1 compiled IL bytes: after not greater than before! (after: {t1_afterCompiledILBytes}, before: {t1_beforeCompiledILBytes})");
+            Assert.True(t1_afterCompiledMethodCount > t1_beforeCompiledMethodCount, $"Thread 1 compiled method count: after not greater than before! (after: {t1_afterCompiledMethodCount}, before: {t1_beforeCompiledMethodCount})");
+
+            Assert.True(t1_afterCompilationTime != t2_afterCompilationTime, $"Thread 1 compilation time: equal to other thread! (t1: {t1_afterCompilationTime}, t2: {t2_beforeCompilationTime})");
+            Assert.True(t1_afterCompiledILBytes != t2_afterCompiledILBytes, $"Thread 1 compiled IL bytes: equal to other thread! (t1: {t1_afterCompiledILBytes}, t2: {t2_beforeCompiledILBytes})");
+            Assert.True(t1_afterCompiledMethodCount != t2_afterCompiledMethodCount, $"Thread 1 compiled method count: equal to other thread! (t1: {t1_afterCompiledMethodCount}, t2: {t2_beforeCompiledMethodCount})");
+        }
+
+        [Fact]
+        [SkipOnCoreClr("CoreCLR does track thread specific JIT information")]
+        public void JitInfoCurrentThreadIsNotPopulated()
+        {
+            TimeSpan compilationTime = TimeSpan.Zero;
+            long compiledILBytes = 0;
+            long compiledMethodCount = 0;
+
+            compilationTime = System.Runtime.JitInfo.GetCompilationTime(currentThread: true);
+            compiledILBytes = System.Runtime.JitInfo.GetCompiledILBytes(currentThread: true);
+            compiledMethodCount = System.Runtime.JitInfo.GetCompiledMethodCount(currentThread: true);
+
+            Assert.True(compilationTime == TimeSpan.Zero, $"compilation time not equal to 0! ({compilationTime})");
+            Assert.True(compiledILBytes == 0, $"compiled IL bytes not equal to 0! ({compiledILBytes})");
+            Assert.True(compiledMethodCount == 0, $"compiled method count not equal to 0! ({compiledMethodCount})");
+        }
+    }
+}
index ae9cf03..8488d91 100644 (file)
       <Compile Include="$(BclSourcesRoot)\System\Resources\ManifestBasedResourceGroveler.Mono.cs" />
       <Compile Include="$(BclSourcesRoot)\System\Runtime\DependentHandle.cs" />
       <Compile Include="$(BclSourcesRoot)\System\Runtime\GCSettings.Mono.cs" />
+      <Compile Include="$(BclSourcesRoot)\System\Runtime\JitInfo.Mono.cs" />
       <Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\JitHelpers.cs" />
       <Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\RuntimeHelpers.Mono.cs" />
       <Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\RuntimeFeature.Mono.cs" />
index 7f3db11..8d3ed19 100644 (file)
@@ -76,7 +76,8 @@ namespace System.Diagnostics.Tracing
             GC_LARGE_OBJECT_SIZE_BYTES,
             GC_LAST_PERCENT_TIME_IN_GC,
             JIT_IL_BYTES_JITTED,
-            JIT_METHODS_JITTED
+            JIT_METHODS_JITTED,
+            JIT_TICKS_IN_JIT
         }
 
 #if FEATURE_PERFTRACING
index cbaba00..ca14c4a 100644 (file)
@@ -154,15 +154,6 @@ namespace System.Runtime.CompilerServices
             return GetUninitializedObjectInternal(new RuntimeTypeHandle(rt).Value);
         }
 
-        internal static long GetILBytesJitted()
-        {
-            return (long)EventPipeInternal.GetRuntimeCounterValue(EventPipeInternal.RuntimeCounters.JIT_IL_BYTES_JITTED);
-        }
-
-        internal static int GetMethodsJittedCount()
-        {
-            return (int)EventPipeInternal.GetRuntimeCounterValue(EventPipeInternal.RuntimeCounters.JIT_METHODS_JITTED);
-        }
 
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
         private static extern unsafe void PrepareMethod(IntPtr method, IntPtr* instantiations, int ninst);
diff --git a/src/mono/System.Private.CoreLib/src/System/Runtime/JitInfo.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Runtime/JitInfo.Mono.cs
new file mode 100644 (file)
index 0000000..c22549c
--- /dev/null
@@ -0,0 +1,38 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.Tracing;
+
+namespace System.Runtime
+{
+    public static partial class JitInfo
+    {
+        /// <summary>
+        /// Get the number of bytes of IL that have been compiled. If <paramref name="currentThread"/> is true,
+        /// then this value is scoped to the current thread, otherwise, this is a global value.
+        /// </summary>
+        /// <param name="currentThread">Whether the returned value should be specific to the current thread. Default: false</param>
+        /// <returns>The number of bytes of IL the JIT has compiled.</returns>
+        public static long GetCompiledILBytes(bool currentThread = false)
+        {
+            return currentThread ? 0 : (long)EventPipeInternal.GetRuntimeCounterValue(EventPipeInternal.RuntimeCounters.JIT_IL_BYTES_JITTED);
+        }
+
+        /// <summary>
+        /// Get the number of methods that have been compiled. If <paramref name="currentThread"/> is true,
+        /// then this value is scoped to the current thread, otherwise, this is a global value.
+        /// </summary>
+        /// <param name="currentThread">Whether the returned value should be specific to the current thread. Default: false</param>
+        /// <returns>The number of methods the JIT has compiled.</returns>
+        public static long GetCompiledMethodCount(bool currentThread = false)
+        {
+            return currentThread ? 0 : (long)EventPipeInternal.GetRuntimeCounterValue(EventPipeInternal.RuntimeCounters.JIT_METHODS_JITTED);
+        }
+
+        // normalized to 100ns ticks on vm side
+        private static long GetCompilationTimeInTicks(bool currentThread = false)
+        {
+            return currentThread ? 0 : (long)EventPipeInternal.GetRuntimeCounterValue(EventPipeInternal.RuntimeCounters.JIT_TICKS_IN_JIT);
+        }
+    }
+}
\ No newline at end of file
index 904b328..7165efd 100644 (file)
@@ -237,7 +237,8 @@ typedef enum {
        EP_RT_COUNTERS_GC_LARGE_OBJECT_SIZE_BYTES,
        EP_RT_COUNTERS_GC_LAST_PERCENT_TIME_IN_GC,
        EP_RT_COUNTERS_JIT_IL_BYTES_JITTED,
-       EP_RT_COUNTERS_JIT_METOHODS_JITTED
+       EP_RT_COUNTERS_JIT_METHODS_JITTED,
+       EP_RT_COUNTERS_JIT_TICKS_IN_JIT
 } EventPipeRuntimeCounters;
 
 static
@@ -265,24 +266,26 @@ get_il_bytes_jitted (void)
        gint64 methods_compiled = 0;
        gint64 cil_code_size_bytes = 0;
        gint64 native_code_size_bytes = 0;
+       gint64 jit_time = 0;
 
        if (mono_get_runtime_callbacks ()->get_jit_stats)
-               mono_get_runtime_callbacks ()->get_jit_stats (&methods_compiled, &cil_code_size_bytes, &native_code_size_bytes);
+               mono_get_runtime_callbacks ()->get_jit_stats (&methods_compiled, &cil_code_size_bytes, &native_code_size_bytes, &jit_time);
        return cil_code_size_bytes;
 }
 
 static
 inline
-gint32
+gint64
 get_methods_jitted (void)
 {
        gint64 methods_compiled = 0;
        gint64 cil_code_size_bytes = 0;
        gint64 native_code_size_bytes = 0;
+       gint64 jit_time = 0;
 
        if (mono_get_runtime_callbacks ()->get_jit_stats)
-               mono_get_runtime_callbacks ()->get_jit_stats (&methods_compiled, &cil_code_size_bytes, &native_code_size_bytes);
-       return (gint32)methods_compiled;
+               mono_get_runtime_callbacks ()->get_jit_stats (&methods_compiled, &cil_code_size_bytes, &native_code_size_bytes, &jit_time);
+       return methods_compiled;
 }
 
 static
@@ -296,6 +299,21 @@ get_exception_count (void)
        return excepion_count;
 }
 
+static
+inline
+gint64
+get_ticks_in_jit (void)
+{
+       gint64 methods_compiled = 0;
+       gint64 cil_code_size_bytes = 0;
+       gint64 native_code_size_bytes = 0;
+       gint64 jit_time = 0;
+
+       if (mono_get_runtime_callbacks ()->get_jit_stats)
+               mono_get_runtime_callbacks ()->get_jit_stats (&methods_compiled, &cil_code_size_bytes, &native_code_size_bytes, &jit_time);
+       return jit_time;
+}
+
 guint64 ves_icall_System_Diagnostics_Tracing_EventPipeInternal_GetRuntimeCounterValue (gint32 id)
 {
        EventPipeRuntimeCounters counterID = (EventPipeRuntimeCounters)id;
@@ -314,8 +332,10 @@ guint64 ves_icall_System_Diagnostics_Tracing_EventPipeInternal_GetRuntimeCounter
                return (guint64)gc_last_percent_time_in_gc ();
        case EP_RT_COUNTERS_JIT_IL_BYTES_JITTED :
                return (guint64)get_il_bytes_jitted ();
-       case EP_RT_COUNTERS_JIT_METOHODS_JITTED :
+       case EP_RT_COUNTERS_JIT_METHODS_JITTED :
                return (guint64)get_methods_jitted ();
+       case EP_RT_COUNTERS_JIT_TICKS_IN_JIT :
+               return (gint64)get_ticks_in_jit ();
        default:
                return 0;
        }
index 53112ed..828b367 100644 (file)
@@ -642,7 +642,7 @@ typedef struct {
        void (*init_mem_manager)(MonoMemoryManager*);
        void (*free_mem_manager)(MonoMemoryManager*);
        void     (*metadata_update_published) (MonoAssemblyLoadContext *alc, uint32_t generation);
-       void (*get_jit_stats)(gint64 *methods_compiled, gint64 *cil_code_size_bytes, gint64 *native_code_size_bytes);
+       void (*get_jit_stats)(gint64 *methods_compiled, gint64 *cil_code_size_bytes, gint64 *native_code_size_bytes, gint64 *jit_time);
        void (*get_exception_stats)(guint32 *exception_count);
        // Same as compile_method, but returns a MonoFtnDesc in llvmonly mode
        gpointer (*get_ftnptr)(MonoMethod *method, MonoError *error);
index aefa9a2..e454d89 100644 (file)
@@ -1744,11 +1744,12 @@ typedef struct {
 extern MonoJitStats mono_jit_stats;
 
 static inline void
-get_jit_stats (gint64 *methods_compiled, gint64 *cil_code_size_bytes, gint64 *native_code_size_bytes)
+get_jit_stats (gint64 *methods_compiled, gint64 *cil_code_size_bytes, gint64 *native_code_size_bytes, gint64 *jit_time)
 {
        *methods_compiled = mono_jit_stats.methods_compiled;
        *cil_code_size_bytes = mono_jit_stats.cil_code_size;
        *native_code_size_bytes = mono_jit_stats.native_code_size;
+       *jit_time = mono_jit_stats.jit_time;
 }
 
 guint32
index 9498ca4..16447de 100644 (file)
@@ -19,7 +19,7 @@ namespace EventCounterRegressionTests
     {        
         private readonly EventLevel _level = EventLevel.Verbose;
 
-        public int MaxIncrement { get; private set; } = 0;
+        public double MaxIncrement { get; private set; } = 0;
 
         public SimpleEventListener()
         {
@@ -38,7 +38,7 @@ namespace EventCounterRegressionTests
 
         protected override void OnEventWritten(EventWrittenEventArgs eventData)
         {
-            int increment = 0;
+            double increment = 0;
             bool isExceptionCounter = false;
 
             for (int i = 0; i < eventData.Payload.Count; i++)
@@ -52,7 +52,7 @@ namespace EventCounterRegressionTests
                             isExceptionCounter = true;
                         if (payload.Key.Equals("Increment"))
                         {
-                            increment = Int32.Parse(payload.Value.ToString());
+                            increment = double.Parse(payload.Value.ToString());
                         }
                     }
                     if (isExceptionCounter)
index eaa7c87..bdab454 100644 (file)
@@ -42,7 +42,8 @@ namespace RuntimeEventCounterTests
                 { "poh-size", false },
                 { "assembly-count", false },
                 { "il-bytes-jitted", false },
-                { "methods-jitted-count", false }
+                { "methods-jitted-count", false },
+                { "time-in-jit", false }
             };
         }
         private Dictionary<string, bool> observedRuntimeCounters;