From 0410c3e4fe54590eea34aead28f550539d814b98 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 19 Apr 2019 19:22:53 -0700 Subject: [PATCH] Implement APIs for some threading metrics (CoreCLR) (#24113) Implement APIs for some threading metrics (CoreCLR) API review: https://github.com/dotnet/corefx/issues/35500 --- .../shared/System/Threading/ThreadPool.cs | 53 ++++++++++ .../src/System/Threading/Monitor.cs | 9 ++ .../src/System/Threading/ThreadPool.CoreCLR.cs | 30 ++++++ src/classlibnative/bcltype/objectnative.cpp | 6 ++ src/classlibnative/bcltype/objectnative.h | 1 + src/vm/comthreadpool.cpp | 24 +++++ src/vm/comthreadpool.h | 3 + src/vm/ecalllist.h | 4 + src/vm/syncblk.cpp | 1 + src/vm/threadpoolrequest.h | 6 ++ src/vm/threads.cpp | 82 +++++++++++---- src/vm/threads.h | 112 +++++++++++++++++++-- src/vm/win32threadpool.cpp | 24 ++++- src/vm/win32threadpool.h | 4 +- 14 files changed, 325 insertions(+), 34 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Threading/ThreadPool.cs b/src/System.Private.CoreLib/shared/System/Threading/ThreadPool.cs index 19c043d..fc53fc1 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/ThreadPool.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/ThreadPool.cs @@ -381,6 +381,26 @@ namespace System.Threading return null; } } + + public int Count + { + get + { + bool lockTaken = false; + try + { + m_foreignLock.Enter(ref lockTaken); + return Math.Max(0, m_tailIndex - m_headIndex); + } + finally + { + if (lockTaken) + { + m_foreignLock.Exit(useMemoryBarrier: false); + } + } + } + } } internal bool loggingEnabled; @@ -512,6 +532,21 @@ namespace System.Threading return callback; } + public long LocalCount + { + get + { + long count = 0; + foreach (WorkStealingQueue workStealingQueue in WorkStealingQueueList.Queues) + { + count += workStealingQueue.Count; + } + return count; + } + } + + public long GlobalCount => workItems.Count; + /// /// Dispatches work items to this thread. /// @@ -1241,5 +1276,23 @@ namespace System.Threading internal static object[] GetLocallyQueuedWorkItemsForDebugger() => ToObjectArray(GetLocallyQueuedWorkItems()); + + /// + /// Gets the number of work items that are currently queued to be processed. + /// + /// + /// For a thread pool implementation that may have different types of work items, the count includes all types that can + /// be tracked, which may only be the user work items including tasks. Some implementations may also include queued + /// timer and wait callbacks in the count. On Windows, the count is unlikely to include the number of pending IO + /// completions, as they get posted directly to an IO completion port. + /// + public static long PendingWorkItemCount + { + get + { + ThreadPoolWorkQueue workQueue = ThreadPoolGlobals.workQueue; + return workQueue.LocalCount + workQueue.GlobalCount + PendingUnmanagedWorkItemCount; + } + } } } diff --git a/src/System.Private.CoreLib/src/System/Threading/Monitor.cs b/src/System.Private.CoreLib/src/System/Threading/Monitor.cs index 2701b3b..cb3a0f6 100644 --- a/src/System.Private.CoreLib/src/System/Threading/Monitor.cs +++ b/src/System.Private.CoreLib/src/System/Threading/Monitor.cs @@ -233,5 +233,14 @@ namespace System.Threading ObjPulseAll(obj); } + + /// + /// Gets the number of times there was contention upon trying to take a 's lock so far. + /// + public static extern long LockContentionCount + { + [MethodImpl(MethodImplOptions.InternalCall)] + get; + } } } diff --git a/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index 8903078..76eb2db 100644 --- a/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -231,6 +231,36 @@ namespace System.Threading GetAvailableThreadsNative(out workerThreads, out completionPortThreads); } + /// + /// Gets the number of thread pool threads that currently exist. + /// + /// + /// For a thread pool implementation that may have different types of threads, the count includes all types. + /// + public static extern int ThreadCount + { + [MethodImpl(MethodImplOptions.InternalCall)] + get; + } + + /// + /// Gets the number of work items that have been processed so far. + /// + /// + /// For a thread pool implementation that may have different types of work items, the count includes all types. + /// + public static extern long CompletedWorkItemCount + { + [MethodImpl(MethodImplOptions.InternalCall)] + get; + } + + private static extern long PendingUnmanagedWorkItemCount + { + [MethodImpl(MethodImplOptions.InternalCall)] + get; + } + private static RegisteredWaitHandle RegisterWaitForSingleObject( // throws RegisterWaitException WaitHandle waitObject, WaitOrTimerCallback callBack, diff --git a/src/classlibnative/bcltype/objectnative.cpp b/src/classlibnative/bcltype/objectnative.cpp index 5304e01..a5619e1 100644 --- a/src/classlibnative/bcltype/objectnative.cpp +++ b/src/classlibnative/bcltype/objectnative.cpp @@ -355,3 +355,9 @@ FCIMPL1(FC_BOOL_RET, ObjectNative::IsLockHeld, Object* pThisUNSAFE) } FCIMPLEND +FCIMPL0(INT64, ObjectNative::GetMonitorLockContentionCount) +{ + FCALL_CONTRACT; + return (INT64)Thread::GetTotalMonitorLockContentionCount(); +} +FCIMPLEND diff --git a/src/classlibnative/bcltype/objectnative.h b/src/classlibnative/bcltype/objectnative.h index 573b04e..3d008d9 100644 --- a/src/classlibnative/bcltype/objectnative.h +++ b/src/classlibnative/bcltype/objectnative.h @@ -38,6 +38,7 @@ public: static FCDECL1(void, Pulse, Object* pThisUNSAFE); static FCDECL1(void, PulseAll, Object* pThisUNSAFE); static FCDECL1(FC_BOOL_RET, IsLockHeld, Object* pThisUNSAFE); + static FCDECL0(INT64, GetMonitorLockContentionCount); }; #endif // _OBJECTNATIVE_H_ diff --git a/src/vm/comthreadpool.cpp b/src/vm/comthreadpool.cpp index 18db995..f5dc423 100644 --- a/src/vm/comthreadpool.cpp +++ b/src/vm/comthreadpool.cpp @@ -182,6 +182,30 @@ FCIMPL2(VOID, ThreadPoolNative::CorGetAvailableThreads,DWORD* workerThreads, DWO FCIMPLEND /*****************************************************************************************************/ +FCIMPL0(INT32, ThreadPoolNative::GetThreadCount) +{ + FCALL_CONTRACT; + return ThreadpoolMgr::GetThreadCount(); +} +FCIMPLEND + +/*****************************************************************************************************/ +FCIMPL0(INT64, ThreadPoolNative::GetCompletedWorkItemCount) +{ + FCALL_CONTRACT; + return (INT64)Thread::GetTotalThreadPoolCompletionCount(); +} +FCIMPLEND + +/*****************************************************************************************************/ +FCIMPL0(INT64, ThreadPoolNative::GetPendingUnmanagedWorkItemCount) +{ + FCALL_CONTRACT; + return PerAppDomainTPCountList::GetUnmanagedTPCount()->GetNumRequests(); +} +FCIMPLEND + +/*****************************************************************************************************/ FCIMPL0(VOID, ThreadPoolNative::NotifyRequestProgress) { diff --git a/src/vm/comthreadpool.h b/src/vm/comthreadpool.h index 6b0f4ec..d830c9e 100644 --- a/src/vm/comthreadpool.h +++ b/src/vm/comthreadpool.h @@ -28,6 +28,9 @@ public: static FCDECL2(FC_BOOL_RET, CorSetMinThreads, DWORD workerThreads, DWORD completionPortThreads); static FCDECL2(VOID, CorGetMinThreads, DWORD* workerThreads, DWORD* completionPortThreads); static FCDECL2(VOID, CorGetAvailableThreads, DWORD* workerThreads, DWORD* completionPortThreads); + static FCDECL0(INT32, GetThreadCount); + static FCDECL0(INT64, GetCompletedWorkItemCount); + static FCDECL0(INT64, GetPendingUnmanagedWorkItemCount); static FCDECL0(VOID, NotifyRequestProgress); static FCDECL0(FC_BOOL_RET, NotifyRequestComplete); diff --git a/src/vm/ecalllist.h b/src/vm/ecalllist.h index 50071ed..4d9de5b 100644 --- a/src/vm/ecalllist.h +++ b/src/vm/ecalllist.h @@ -678,6 +678,9 @@ FCFuncStart(gThreadPoolFuncs) FCFuncElement("GetAvailableThreadsNative", ThreadPoolNative::CorGetAvailableThreads) FCFuncElement("SetMinThreadsNative", ThreadPoolNative::CorSetMinThreads) FCFuncElement("GetMinThreadsNative", ThreadPoolNative::CorGetMinThreads) + FCFuncElement("get_ThreadCount", ThreadPoolNative::GetThreadCount) + FCFuncElement("get_CompletedWorkItemCount", ThreadPoolNative::GetCompletedWorkItemCount) + FCFuncElement("get_PendingUnmanagedWorkItemCount", ThreadPoolNative::GetPendingUnmanagedWorkItemCount) FCFuncElement("RegisterWaitForSingleObjectNative", ThreadPoolNative::CorRegisterWaitForSingleObject) FCFuncElement("BindIOCompletionCallbackNative", ThreadPoolNative::CorBindIoCompletionCallback) FCFuncElement("SetMaxThreadsNative", ThreadPoolNative::CorSetMaxThreads) @@ -915,6 +918,7 @@ FCFuncStart(gMonitorFuncs) FCFuncElement("ObjPulse", ObjectNative::Pulse) FCFuncElement("ObjPulseAll", ObjectNative::PulseAll) FCFuncElement("IsEnteredNative", ObjectNative::IsLockHeld) + FCFuncElement("get_LockContentionCount", ObjectNative::GetMonitorLockContentionCount) FCFuncEnd() FCFuncStart(gOverlappedFuncs) diff --git a/src/vm/syncblk.cpp b/src/vm/syncblk.cpp index ce31ac9..9aea18f 100644 --- a/src/vm/syncblk.cpp +++ b/src/vm/syncblk.cpp @@ -2575,6 +2575,7 @@ BOOL AwareLock::EnterEpilogHelper(Thread* pCurThread, INT32 timeOut) FireEtwContentionStart_V1(ETW::ContentionLog::ContentionStructs::ManagedContention, GetClrInstanceId()); LogContention(); + Thread::IncrementMonitorLockContentionCount(pCurThread); OBJECTREF obj = GetOwningObject(); diff --git a/src/vm/threadpoolrequest.h b/src/vm/threadpoolrequest.h index eaba823..3b2da28 100644 --- a/src/vm/threadpoolrequest.h +++ b/src/vm/threadpoolrequest.h @@ -221,6 +221,12 @@ public: _ASSERT(FALSE); } + inline ULONG GetNumRequests() + { + LIMITED_METHOD_CONTRACT; + return VolatileLoad(&m_NumRequests); + } + private: SpinLock m_lock; ULONG m_NumRequests; diff --git a/src/vm/threads.cpp b/src/vm/threads.cpp index 70c2611..37d30c3 100644 --- a/src/vm/threads.cpp +++ b/src/vm/threads.cpp @@ -83,7 +83,9 @@ PTR_ThreadLocalModule ThreadLocalBlock::GetTLMIfExists(MethodTable* pMT) BOOL Thread::s_fCleanFinalizedThread = FALSE; -Volatile Thread::s_threadPoolCompletionCountOverflow = 0; +UINT64 Thread::s_workerThreadPoolCompletionCountOverflow = 0; +UINT64 Thread::s_ioThreadPoolCompletionCountOverflow = 0; +UINT64 Thread::s_monitorLockContentionCountOverflow = 0; CrstStatic g_DeadlockAwareCrst; @@ -1528,7 +1530,9 @@ Thread::Thread() m_sfEstablisherOfActualHandlerFrame.Clear(); #endif // WIN64EXCEPTIONS - m_threadPoolCompletionCount = 0; + m_workerThreadPoolCompletionCount = 0; + m_ioThreadPoolCompletionCount = 0; + m_monitorLockContentionCount = 0; Thread *pThread = GetThread(); InitContext(); @@ -5350,9 +5354,15 @@ BOOL ThreadStore::RemoveThread(Thread *target) if (target->IsBackground()) s_pThreadStore->m_BackgroundThreadCount--; - FastInterlockExchangeAdd( - &Thread::s_threadPoolCompletionCountOverflow, - target->m_threadPoolCompletionCount); + FastInterlockExchangeAddLong( + (LONGLONG *)&Thread::s_workerThreadPoolCompletionCountOverflow, + target->m_workerThreadPoolCompletionCount); + FastInterlockExchangeAddLong( + (LONGLONG *)&Thread::s_ioThreadPoolCompletionCountOverflow, + target->m_ioThreadPoolCompletionCount); + FastInterlockExchangeAddLong( + (LONGLONG *)&Thread::s_monitorLockContentionCountOverflow, + target->m_monitorLockContentionCount); _ASSERTE(s_pThreadStore->m_ThreadCount >= 0); _ASSERTE(s_pThreadStore->m_BackgroundThreadCount >= 0); @@ -8002,7 +8012,23 @@ BOOL ThreadStore::HoldingThreadStore(Thread *pThread) } } -LONG Thread::GetTotalThreadPoolCompletionCount() +NOINLINE void Thread::OnIncrementCountOverflow(UINT32 *threadLocalCount, UINT64 *overflowCount) +{ + WRAPPER_NO_CONTRACT; + _ASSERTE(threadLocalCount != nullptr); + _ASSERTE(overflowCount != nullptr); + + // Increment overflow, accumulate the count for this increment into the overflow count and reset the thread-local count + + // The thread store lock, in coordination with other places that read these values, ensures that both changes + // below become visible together + ThreadStoreLockHolder tsl; + + *threadLocalCount = 0; + InterlockedExchangeAdd64((LONGLONG *)overflowCount, (LONGLONG)UINT32_MAX + 1); +} + +UINT64 Thread::GetTotalCount(SIZE_T threadLocalCountOffset, UINT64 *overflowCount) { CONTRACTL { @@ -8011,32 +8037,44 @@ LONG Thread::GetTotalThreadPoolCompletionCount() } CONTRACTL_END; - LONG total; - if (g_fEEStarted) //make sure we actually have a thread store - { - // make sure up-to-date thread-local counts are visible to us - ::FlushProcessWriteBuffers(); + // enumerate all threads, summing their local counts. + ThreadStoreLockHolder tsl; - // enumerate all threads, summing their local counts. - ThreadStoreLockHolder tsl; + UINT64 total = GetOverflowCount(overflowCount); - total = s_threadPoolCompletionCountOverflow.Load(); + Thread *pThread = NULL; + while ((pThread = ThreadStore::GetAllThreadList(pThread, 0, 0)) != NULL) + { + total += *GetThreadLocalCountRef(pThread, threadLocalCountOffset); + } - Thread *pThread = NULL; - while ((pThread = ThreadStore::GetAllThreadList(pThread, 0, 0)) != NULL) - { - total += pThread->m_threadPoolCompletionCount; - } + return total; +} + +UINT64 Thread::GetTotalThreadPoolCompletionCount() +{ + CONTRACTL + { + NOTHROW; + MODE_ANY; } - else + CONTRACTL_END; + + // enumerate all threads, summing their local counts. + ThreadStoreLockHolder tsl; + + UINT64 total = GetWorkerThreadPoolCompletionCountOverflow() + GetIOThreadPoolCompletionCountOverflow(); + + Thread *pThread = NULL; + while ((pThread = ThreadStore::GetAllThreadList(pThread, 0, 0)) != NULL) { - total = s_threadPoolCompletionCountOverflow.Load(); + total += pThread->m_workerThreadPoolCompletionCount; + total += pThread->m_ioThreadPoolCompletionCount; } return total; } - INT32 Thread::ResetManagedThreadObject(INT32 nPriority) { CONTRACTL { diff --git a/src/vm/threads.h b/src/vm/threads.h index e5307d9..ee3e69f 100644 --- a/src/vm/threads.h +++ b/src/vm/threads.h @@ -3871,21 +3871,113 @@ private: #endif // defined(FEATURE_PROFAPI_ATTACH_DETACH) || defined(DATA_PROFAPI_ATTACH_DETACH) private: - Volatile m_threadPoolCompletionCount; - static Volatile s_threadPoolCompletionCountOverflow; //counts completions for threads that have been destroyed. + UINT32 m_workerThreadPoolCompletionCount; + static UINT64 s_workerThreadPoolCompletionCountOverflow; + UINT32 m_ioThreadPoolCompletionCount; + static UINT64 s_ioThreadPoolCompletionCountOverflow; + UINT32 m_monitorLockContentionCount; + static UINT64 s_monitorLockContentionCountOverflow; -public: - static void IncrementThreadPoolCompletionCount() +#ifndef DACCESS_COMPILE +private: + static UINT32 *GetThreadLocalCountRef(Thread *pThread, SIZE_T threadLocalCountOffset) { - LIMITED_METHOD_CONTRACT; - Thread* pThread = GetThread(); - if (pThread) - pThread->m_threadPoolCompletionCount++; + WRAPPER_NO_CONTRACT; + _ASSERTE(threadLocalCountOffset <= sizeof(Thread) - sizeof(UINT32)); + + return (UINT32 *)((SIZE_T)pThread + threadLocalCountOffset); + } + + static void IncrementCount(Thread *pThread, SIZE_T threadLocalCountOffset, UINT64 *overflowCount) + { + WRAPPER_NO_CONTRACT; + _ASSERTE(overflowCount != nullptr); + + if (pThread != nullptr) + { + UINT32 *threadLocalCount = GetThreadLocalCountRef(pThread, threadLocalCountOffset); + UINT32 newCount = *threadLocalCount + 1; + if (newCount != 0) + { + VolatileStoreWithoutBarrier(threadLocalCount, newCount); + } + else + { + OnIncrementCountOverflow(threadLocalCount, overflowCount); + } + } else - FastInterlockIncrement(&s_threadPoolCompletionCountOverflow); + { + InterlockedIncrement64((LONGLONG *)overflowCount); + } + } + + static void OnIncrementCountOverflow(UINT32 *threadLocalCount, UINT64 *overflowCount); + + static UINT64 GetOverflowCount(UINT64 *overflowCount) + { + WRAPPER_NO_CONTRACT; + + if (sizeof(void *) >= sizeof(*overflowCount)) + { + return VolatileLoad(overflowCount); + } + return InterlockedCompareExchange64((LONGLONG *)overflowCount, 0, 0); // prevent tearing + } + + static UINT64 GetTotalCount(SIZE_T threadLocalCountOffset, UINT64 *overflowCount); + +public: + static void IncrementWorkerThreadPoolCompletionCount(Thread *pThread) + { + WRAPPER_NO_CONTRACT; + IncrementCount(pThread, offsetof(Thread, m_workerThreadPoolCompletionCount), &s_workerThreadPoolCompletionCountOverflow); + } + + static UINT64 GetWorkerThreadPoolCompletionCountOverflow() + { + WRAPPER_NO_CONTRACT; + return GetOverflowCount(&s_workerThreadPoolCompletionCountOverflow); + } + + static UINT64 GetTotalWorkerThreadPoolCompletionCount() + { + WRAPPER_NO_CONTRACT; + return GetTotalCount(offsetof(Thread, m_workerThreadPoolCompletionCount), &s_workerThreadPoolCompletionCountOverflow); + } + + static void IncrementIOThreadPoolCompletionCount(Thread *pThread) + { + WRAPPER_NO_CONTRACT; + IncrementCount(pThread, offsetof(Thread, m_ioThreadPoolCompletionCount), &s_ioThreadPoolCompletionCountOverflow); + } + + static UINT64 GetIOThreadPoolCompletionCountOverflow() + { + WRAPPER_NO_CONTRACT; + return GetOverflowCount(&s_ioThreadPoolCompletionCountOverflow); + } + + static UINT64 GetTotalThreadPoolCompletionCount(); + + static void IncrementMonitorLockContentionCount(Thread *pThread) + { + WRAPPER_NO_CONTRACT; + IncrementCount(pThread, offsetof(Thread, m_monitorLockContentionCount), &s_monitorLockContentionCountOverflow); } - static LONG GetTotalThreadPoolCompletionCount(); + static UINT64 GetMonitorLockContentionCountOverflow() + { + WRAPPER_NO_CONTRACT; + return GetOverflowCount(&s_monitorLockContentionCountOverflow); + } + + static UINT64 GetTotalMonitorLockContentionCount() + { + WRAPPER_NO_CONTRACT; + return GetTotalCount(offsetof(Thread, m_monitorLockContentionCount), &s_monitorLockContentionCountOverflow); + } +#endif // !DACCESS_COMPILE private: diff --git a/src/vm/win32threadpool.cpp b/src/vm/win32threadpool.cpp index f340542..da44beb 100644 --- a/src/vm/win32threadpool.cpp +++ b/src/vm/win32threadpool.cpp @@ -693,6 +693,18 @@ BOOL ThreadpoolMgr::GetAvailableThreads(DWORD* AvailableWorkerThreads, return TRUE; } +INT32 ThreadpoolMgr::GetThreadCount() +{ + WRAPPER_NO_CONTRACT; + + if (!IsInitialized()) + { + return 0; + } + + return WorkerCounter.DangerousGetDirtyCounts().NumActive + CPThreadCounter.DangerousGetDirtyCounts().NumActive; +} + void QueueUserWorkItemHelp(LPTHREAD_START_ROUTINE Function, PVOID Context) { STATIC_CONTRACT_THROWS; @@ -911,7 +923,7 @@ void ThreadpoolMgr::AdjustMaxWorkersActive() _ASSERTE(ThreadAdjustmentLock.IsHeld()); DWORD currentTicks = GetTickCount(); - LONG totalNumCompletions = Thread::GetTotalThreadPoolCompletionCount(); + LONG totalNumCompletions = (LONG)Thread::GetTotalWorkerThreadPoolCompletionCount(); LONG numCompletions = totalNumCompletions - VolatileLoad(&PriorCompletedWorkRequests); LARGE_INTEGER startTime = CurrentSampleStartTime; @@ -2864,6 +2876,10 @@ DWORD WINAPI ThreadpoolMgr::AsyncCallbackCompletion(PVOID pArgs) ((WAITORTIMERCALLBACKFUNC) waitInfo->Callback) ( waitInfo->Context, asyncCallback->waitTimedOut != FALSE); + +#ifndef FEATURE_PAL + Thread::IncrementIOThreadPoolCompletionCount(pThread); +#endif } return ERROR_SUCCESS; @@ -3604,6 +3620,12 @@ Top: ((LPOVERLAPPED_COMPLETION_ROUTINE) key)(errorCode, numBytes, pOverlapped); } + if ((void *)key != CallbackForInitiateDrainageOfCompletionPortQueue && + (void *)key != CallbackForContinueDrainageOfCompletionPortQueue) + { + Thread::IncrementIOThreadPoolCompletionCount(pThread); + } + if (pThread == NULL) { pThread = GetThread(); } diff --git a/src/vm/win32threadpool.h b/src/vm/win32threadpool.h index 55f321c..ff47ea2 100644 --- a/src/vm/win32threadpool.h +++ b/src/vm/win32threadpool.h @@ -244,6 +244,8 @@ public: static BOOL GetAvailableThreads(DWORD* AvailableWorkerThreads, DWORD* AvailableIOCompletionThreads); + static INT32 GetThreadCount(); + static BOOL QueueUserWorkItem(LPTHREAD_START_ROUTINE Function, PVOID Context, ULONG Flags, @@ -841,7 +843,7 @@ public: static void NotifyWorkItemCompleted() { WRAPPER_NO_CONTRACT; - Thread::IncrementThreadPoolCompletionCount(); + Thread::IncrementWorkerThreadPoolCompletionCount(GetThread()); UpdateLastDequeueTime(); } -- 2.7.4