diff --git a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs index 382b5cc4d340e4..d6474dcc8c83d3 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs @@ -711,5 +711,13 @@ public static T[] AllocateArray(int length, bool pinned = false) // T[] rathe return Unsafe.As(AllocateNewArray(typeof(T[]).TypeHandle.Value, length, flags)); } + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern long _GetTotalPauseDuration(); + + public static TimeSpan GetTotalPauseDuration() + { + return new TimeSpan(_GetTotalPauseDuration()); + } } } diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index d291743679238a..e52fc373983e25 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -46323,6 +46323,11 @@ void GCHeap::GetMemoryInfo(uint64_t* highMemLoadThresholdBytes, #endif //_DEBUG } +int64_t GCHeap::GetTotalPauseDuration() +{ + return (int64_t)(gc_heap::total_suspended_time * 10); +} + uint32_t GCHeap::GetMemoryLoad() { uint32_t memory_load = 0; diff --git a/src/coreclr/gc/gcimpl.h b/src/coreclr/gc/gcimpl.h index 5f631642e11c76..1d470a0c8ca124 100644 --- a/src/coreclr/gc/gcimpl.h +++ b/src/coreclr/gc/gcimpl.h @@ -180,7 +180,9 @@ class GCHeap : public IGCHeapInternal bool* isConcurrent, uint64_t* genInfoRaw, uint64_t* pauseInfoRaw, - int kind);; + int kind); + + int64_t GetTotalPauseDuration(); uint32_t GetMemoryLoad(); diff --git a/src/coreclr/gc/gcinterface.h b/src/coreclr/gc/gcinterface.h index 8b6bf765aaad8b..a1edb39e59fcb3 100644 --- a/src/coreclr/gc/gcinterface.h +++ b/src/coreclr/gc/gcinterface.h @@ -925,6 +925,9 @@ class IGCHeap { IGCHeap() {} virtual ~IGCHeap() {} + + // Get the total paused duration + virtual int64_t GetTotalPauseDuration() = 0; }; #ifdef WRITE_BARRIER_CHECK diff --git a/src/coreclr/nativeaot/Runtime/GCHelpers.cpp b/src/coreclr/nativeaot/Runtime/GCHelpers.cpp index 9457e46d6f7269..5819776ca10534 100644 --- a/src/coreclr/nativeaot/Runtime/GCHelpers.cpp +++ b/src/coreclr/nativeaot/Runtime/GCHelpers.cpp @@ -339,3 +339,8 @@ EXTERN_C REDHAWK_API void RhAllocateNewObject(MethodTable* pEEType, uint32_t fla pThread->EnablePreemptiveMode(); } + +COOP_PINVOKE_HELPER(int64_t, RhGetTotalPauseDuration, ()) +{ + return GCHeapUtilities::GetGCHeap()->GetTotalPauseDuration(); +} diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs index dbf195b409daa2..b4d59af8f3655b 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs @@ -759,5 +759,10 @@ public static unsafe T[] AllocateArray(int length, bool pinned = false) return array; } + + public static TimeSpan GetTotalPauseDuration() + { + return new TimeSpan(RuntimeImports.RhGetTotalPauseDuration()); + } } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs index b1666d1d7d3d97..6ab44352da6c93 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs @@ -201,6 +201,10 @@ internal static void RhWaitForPendingFinalizers(bool allowReentrantWait) [LibraryImport(RuntimeLibrary)] internal static unsafe partial void RhAllocateNewObject(IntPtr pEEType, uint flags, void* pResult); + [MethodImpl(MethodImplOptions.InternalCall)] + [RuntimeImport(RuntimeLibrary, "RhGetTotalPauseDuration")] + internal static extern long RhGetTotalPauseDuration(); + [MethodImpl(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhCompareObjectContentsAndPadding")] internal static extern bool RhCompareObjectContentsAndPadding(object obj1, object obj2); diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index 732c6042ce8cb8..7f92d23b8cce6f 100644 --- a/src/coreclr/vm/comutilnative.cpp +++ b/src/coreclr/vm/comutilnative.cpp @@ -674,6 +674,16 @@ UINT64 GCInterface::m_remPressure[MEM_PRESSURE_COUNT] = {0, 0, 0, 0}; // his // (m_iteration % MEM_PRESSURE_COUNT) is used as an index into m_addPressure and m_remPressure UINT GCInterface::m_iteration = 0; +FCIMPL0(INT64, GCInterface::GetTotalPauseDuration) +{ + FCALL_CONTRACT; + + FC_GC_POLL_NOT_NEEDED(); + + return GCHeapUtilities::GetGCHeap()->GetTotalPauseDuration(); +} +FCIMPLEND + FCIMPL2(void, GCInterface::GetMemoryInfo, Object* objUNSAFE, int kind) { FCALL_CONTRACT; diff --git a/src/coreclr/vm/comutilnative.h b/src/coreclr/vm/comutilnative.h index 22bfc527184ad2..42ec3b0663b915 100644 --- a/src/coreclr/vm/comutilnative.h +++ b/src/coreclr/vm/comutilnative.h @@ -134,6 +134,7 @@ class GCInterface { static FORCEINLINE UINT64 InterlockedAdd(UINT64 *pAugend, UINT64 addend); static FORCEINLINE UINT64 InterlockedSub(UINT64 *pMinuend, UINT64 subtrahend); + static FCDECL0(INT64, GetTotalPauseDuration); static FCDECL2(void, GetMemoryInfo, Object* objUNSAFE, int kind); static FCDECL0(UINT32, GetMemoryLoad); static FCDECL0(int, GetGcLatencyMode); diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index ccb31d35cd8f78..05e9982a43fde4 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -475,6 +475,7 @@ FCFuncStart(gGCInterfaceFuncs) FCFuncElement("_WaitForFullGCComplete", GCInterface::WaitForFullGCComplete) FCFuncElement("_CollectionCount", GCInterface::CollectionCount) FCFuncElement("GetMemoryInfo", GCInterface::GetMemoryInfo) + FCFuncElement("_GetTotalPauseDuration", GCInterface::GetTotalPauseDuration) FCFuncElement("GetMemoryLoad", GCInterface::GetMemoryLoad) FCFuncElement("GetSegmentSize", GCInterface::GetSegmentSize) FCFuncElement("GetLastGCPercentTimeInGC", GCInterface::GetLastGCPercentTimeInGC) diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index feb44b077a5971..a32f7b36509a36 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -2519,6 +2519,12 @@ public static void SuppressFinalize(object obj) { } public static System.GCNotificationStatus WaitForFullGCComplete(int millisecondsTimeout) { throw null; } public static System.GCNotificationStatus WaitForFullGCComplete(System.TimeSpan timeout) { throw null; } public static void WaitForPendingFinalizers() { } + + /// + /// Gets the total amount of time paused in GC since the beginning of the process. + /// + /// The total amount of time paused in GC since the beginning of the process. + public static TimeSpan GetTotalPauseDuration() { return TimeSpan.Zero; } } public enum GCCollectionMode { diff --git a/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs b/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs index c08eec30fbebea..b5bc27542b5fef 100644 --- a/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs @@ -310,5 +310,10 @@ internal static int GetLastGCPercentTimeInGC() { return (int)EventPipeInternal.GetRuntimeCounterValue(EventPipeInternal.RuntimeCounters.GC_LAST_PERCENT_TIME_IN_GC); } + + public static TimeSpan GetTotalPauseDuration() + { + return TimeSpan.Zero; + } } } diff --git a/src/tests/GC/API/GC/GetTotalPauseDuration.cs b/src/tests/GC/API/GC/GetTotalPauseDuration.cs new file mode 100644 index 00000000000000..1435c3493aba92 --- /dev/null +++ b/src/tests/GC/API/GC/GetTotalPauseDuration.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Tests GC.GetTotalPauseDuration() + +using System; +using System.Diagnostics; + +public class Test_Collect +{ + public static int Main() + { + Stopwatch sw = Stopwatch.StartNew(); + GC.Collect(); + sw.Stop(); + TimeSpan elapsed = sw.Elapsed; + TimeSpan totalPauseDuration = GC.GetTotalPauseDuration(); + GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo(); + TimeSpan lastGcDuration = memoryInfo.PauseDurations[0]; + + // These conditions assume the only GC in the process + // is the one we just triggered. This makes the test incompatible + // with any changes that might introduce extra GCs. + + if (TimeSpan.Zero < totalPauseDuration && + totalPauseDuration <= elapsed && + lastGcDuration == totalPauseDuration) + { + return 100; + } + else + { + return 101; + } + } +} diff --git a/src/tests/GC/API/GC/GetTotalPauseDuration.csproj b/src/tests/GC/API/GC/GetTotalPauseDuration.csproj new file mode 100644 index 00000000000000..358a5cea38bb5e --- /dev/null +++ b/src/tests/GC/API/GC/GetTotalPauseDuration.csproj @@ -0,0 +1,16 @@ + + + Exe + + true + + 0 + + + + PdbOnly + + + + +