diff --git a/src/coreclr/System.Private.CoreLib/src/System/GC.cs b/src/coreclr/System.Private.CoreLib/src/System/GC.cs index bcbf042fd1b24d..33594cca7170ae 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/GC.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/GC.cs @@ -719,5 +719,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 7400a2eae94882..df9a96b8dc0099 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -45045,6 +45045,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 d7c08c44d9adce..03c56d49dbb531 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/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index fec65f96e5928f..6b5bdde020abf6 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 f59bbe7f66c118..666ed6aefcfc25 100644 --- a/src/coreclr/vm/comutilnative.h +++ b/src/coreclr/vm/comutilnative.h @@ -143,6 +143,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 363ae6f6cfa7cc..8317386a22f722 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -706,6 +706,7 @@ FCFuncStart(gGCInterfaceFuncs) FCFuncElement("_WaitForFullGCComplete", GCInterface::WaitForFullGCComplete) FCFuncElement("_CollectionCount", GCInterface::CollectionCount) FCFuncElement("GetMemoryInfo", GCInterface::GetMemoryInfo) + FCFuncElement("_GetTotalPauseDuration", GCInterface::GetTotalPauseDuration) FCFuncElement("GetMemoryLoad", GCInterface::GetMemoryLoad) QCFuncElement("_StartNoGCRegion", GCInterface::StartNoGCRegion) QCFuncElement("_EndNoGCRegion", GCInterface::EndNoGCRegion) diff --git a/src/libraries/System.Runtime/src/MatchingRefApiCompatBaseline.txt b/src/libraries/System.Runtime/src/MatchingRefApiCompatBaseline.txt index cbe8cabdbb2956..542d29445b645c 100644 --- a/src/libraries/System.Runtime/src/MatchingRefApiCompatBaseline.txt +++ b/src/libraries/System.Runtime/src/MatchingRefApiCompatBaseline.txt @@ -24,4 +24,5 @@ CannotMakeMemberAbstract : Member 'public System.Boolean System.IO.FileSystemInf CannotMakeMemberAbstract : Member 'public System.String System.IO.FileSystemInfo.Name.get()' is abstract in the reference but is not abstract in the implementation. # C# generates backing fields for fixed buffers as public. TypesMustExist : Type 'System.IO.Enumeration.FileSystemEntry.<_fileNameBuffer>e__FixedBuffer' does not exist in the reference but it does exist in the implementation. - +# This API only exists on servicing builds after the commit that introduce it +MembersMustExist : Member 'public System.TimeSpan System.GC.GetTotalPauseDuration()' does not exist in the reference but it does exist in the implementation. \ No newline at end of file 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 a92ebab49920d7..4a383c9efb7172 100644 --- a/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs @@ -313,5 +313,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..96ac42640c804b --- /dev/null +++ b/src/tests/GC/API/GC/GetTotalPauseDuration.cs @@ -0,0 +1,36 @@ +// 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; +using System.Reflection; + +public class Test_Collect +{ + public static int Main() + { + Stopwatch sw = Stopwatch.StartNew(); + GC.Collect(); + sw.Stop(); + TimeSpan elapsed = sw.Elapsed; + TimeSpan totalPauseDuration = (TimeSpan)typeof(GC).GetMethod("GetTotalPauseDuration", BindingFlags.Public | BindingFlags.Static).Invoke(null, null); + 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 + + + + +