diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index b42232633a7773..1075a708b8bbb3 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -214,6 +214,7 @@ + diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 3d243e2021cc31..558e2553f99575 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -349,12 +349,6 @@ private static unsafe void DispatchTailCalls( } } } - - [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 index 00000000000000..f1dafd58a819f9 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/JitInfo.CoreCLR.cs @@ -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 + { + /// + /// Get the number of bytes of IL that have been compiled. If is true, + /// then this value is scoped to the current thread, otherwise, this is a global value. + /// + /// Whether the returned value should be specific to the current thread. Default: false + /// The number of bytes of IL the JIT has compiled. + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern long GetCompiledILBytes(bool currentThread = false); + + /// + /// Get the number of methods that have been compiled. If is true, + /// then this value is scoped to the current thread, otherwise, this is a global value. + /// + /// Whether the returned value should be specific to the current thread. Default: false + /// The number of methods the JIT has compiled. + [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 diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index ea3f65d72917d2..738ac9a3399771 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -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 diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 882e2c29cef040..b19fa365323746 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -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 g_cbILJitted = 0; +Volatile g_cMethodsJitted = 0; +Volatile 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; diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index e071d0717d1796..a84a948cda1c60 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -1149,8 +1149,17 @@ CORJIT_FLAGS GetDebuggerCompileFlags(Module* pModule, CORJIT_FLAGS flags); bool __stdcall TrackAllocationsEnabled(); -FCDECL0(INT64, GetJittedBytes); -FCDECL0(INT32, GetJittedMethodsCount); + +extern Volatile g_cbILJitted; +extern Volatile g_cMethodsJitted; +extern Volatile 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 diff --git a/src/coreclr/vm/util.cpp b/src/coreclr/vm/util.cpp index 64130d699ea936..fe51593c0efa7d 100644 --- a/src/coreclr/vm/util.cpp +++ b/src/coreclr/vm/util.cpp @@ -2272,4 +2272,6 @@ HRESULT GetFileVersion( // S_OK or error } #endif // !TARGET_UNIX +Volatile NormalizedTimer::s_frequency = -1.0; + #endif // !DACCESS_COMPILE diff --git a/src/coreclr/vm/util.hpp b/src/coreclr/vm/util.hpp index 19f7932f178290..4da40a3ead16dc 100644 --- a/src/coreclr/vm/util.hpp +++ b/src/coreclr/vm/util.hpp @@ -918,6 +918,83 @@ class COMCharacter { 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 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(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((stopTimestamp.QuadPart - startTimestamp.QuadPart) / s_frequency); + } +}; + #ifdef _DEBUG #define FORCEINLINE_NONDEBUG #else diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index 90f21d02f5c49b..c1bb1854ff1ba1 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -27,6 +27,7 @@ public static partial class PlatformDetection 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")); diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 6c1182acbd19ff..ac992cdc3b6e8b 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -867,6 +867,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSource.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSource.cs index fe47aae73fde21..6d57c82ef6cc40 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSource.cs @@ -39,6 +39,7 @@ internal sealed partial class RuntimeEventSource : EventSource private PollingCounter? _assemblyCounter; private PollingCounter? _ilBytesJittedCounter; private PollingCounter? _methodsJittedCounter; + private IncrementingPollingCounter? _jitTimeCounter; public static void Initialize() { @@ -83,8 +84,9 @@ protected override void OnEventCommand(EventCommandEventArgs command) _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 index 00000000000000..9176bdd84eb5a2 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/JitInfo.cs @@ -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 +{ + /// + /// A static class for getting information about the Just In Time compiler. + /// + public static partial class JitInfo + { + /// + /// Get the amount of time the JIT Compiler has spent compiling methods. If is true, + /// then this value is scoped to the current thread, otherwise, this is a global value. + /// + /// Whether the returned value should be specific to the current thread. Default: false + /// The amount of time the JIT Compiler has spent compiling methods. + public static TimeSpan GetCompilationTime(bool currentThread = false) + { + // TimeSpan.FromTicks() takes 100ns ticks + return TimeSpan.FromTicks(GetCompilationTimeInTicks(currentThread)); + } + } +} \ No newline at end of file diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 76bbc5dd649585..b1baaa1a95dba7 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -12472,6 +12472,12 @@ public static partial class GCSettings 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) { } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index 3ada2d0cd26217..6d888e344bc80e 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -223,6 +223,7 @@ + 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 index 00000000000000..f0a7eca83ee67b --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/Runtime/JitInfoTests.cs @@ -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 invokeSquareIt = + (Func) + squareIt.CreateDelegate(typeof(Func)); + + 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})"); + } + } +} diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index ae9cf03ab132ea..8488d916e6df03 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -242,6 +242,7 @@ + diff --git a/src/mono/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipe.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipe.Mono.cs index 7f3db11f9e5e9f..8d3ed1968d5f46 100644 --- a/src/mono/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipe.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipe.Mono.cs @@ -76,7 +76,8 @@ internal enum RuntimeCounters 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 diff --git a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs index cbaba0038eb504..ca14c4a823f9ec 100644 --- a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs @@ -154,15 +154,6 @@ public static object GetUninitializedObject( 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 index 00000000000000..c22549cdf85856 --- /dev/null +++ b/src/mono/System.Private.CoreLib/src/System/Runtime/JitInfo.Mono.cs @@ -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 + { + /// + /// Get the number of bytes of IL that have been compiled. If is true, + /// then this value is scoped to the current thread, otherwise, this is a global value. + /// + /// Whether the returned value should be specific to the current thread. Default: false + /// The number of bytes of IL the JIT has compiled. + public static long GetCompiledILBytes(bool currentThread = false) + { + return currentThread ? 0 : (long)EventPipeInternal.GetRuntimeCounterValue(EventPipeInternal.RuntimeCounters.JIT_IL_BYTES_JITTED); + } + + /// + /// Get the number of methods that have been compiled. If is true, + /// then this value is scoped to the current thread, otherwise, this is a global value. + /// + /// Whether the returned value should be specific to the current thread. Default: false + /// The number of methods the JIT has compiled. + 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 diff --git a/src/mono/mono/metadata/icall-eventpipe.c b/src/mono/mono/metadata/icall-eventpipe.c index 904b328ce87d50..7165efd3805890 100644 --- a/src/mono/mono/metadata/icall-eventpipe.c +++ b/src/mono/mono/metadata/icall-eventpipe.c @@ -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; } diff --git a/src/mono/mono/metadata/object-internals.h b/src/mono/mono/metadata/object-internals.h index 53112ed0ea37ff..828b367f79739d 100644 --- a/src/mono/mono/metadata/object-internals.h +++ b/src/mono/mono/metadata/object-internals.h @@ -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); diff --git a/src/mono/mono/mini/mini.h b/src/mono/mono/mini/mini.h index aefa9a270c86fc..e454d89ef41b81 100644 --- a/src/mono/mono/mini/mini.h +++ b/src/mono/mono/mini/mini.h @@ -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 diff --git a/src/tests/tracing/eventcounter/regression-25709.cs b/src/tests/tracing/eventcounter/regression-25709.cs index 9498ca4752350e..16447debe895b7 100644 --- a/src/tests/tracing/eventcounter/regression-25709.cs +++ b/src/tests/tracing/eventcounter/regression-25709.cs @@ -19,7 +19,7 @@ public class SimpleEventListener : EventListener { 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 @@ protected override void OnEventSourceCreated(EventSource source) 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 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) isExceptionCounter = true; if (payload.Key.Equals("Increment")) { - increment = Int32.Parse(payload.Value.ToString()); + increment = double.Parse(payload.Value.ToString()); } } if (isExceptionCounter) diff --git a/src/tests/tracing/eventcounter/runtimecounters.cs b/src/tests/tracing/eventcounter/runtimecounters.cs index eaa7c87d6d8ab1..bdab454300ade0 100644 --- a/src/tests/tracing/eventcounter/runtimecounters.cs +++ b/src/tests/tracing/eventcounter/runtimecounters.cs @@ -42,7 +42,8 @@ public RuntimeCounterListener() { "poh-size", false }, { "assembly-count", false }, { "il-bytes-jitted", false }, - { "methods-jitted-count", false } + { "methods-jitted-count", false }, + { "time-in-jit", false } }; } private Dictionary observedRuntimeCounters;