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;