Implement APIs for some threading metrics (CoreCLR) (#24113)
authorKoundinya Veluri <kouvel@users.noreply.github.com>
Sat, 20 Apr 2019 02:22:53 +0000 (19:22 -0700)
committerGitHub <noreply@github.com>
Sat, 20 Apr 2019 02:22:53 +0000 (19:22 -0700)
Implement APIs for some threading metrics (CoreCLR)

API review: https://github.com/dotnet/corefx/issues/35500

14 files changed:
src/System.Private.CoreLib/shared/System/Threading/ThreadPool.cs
src/System.Private.CoreLib/src/System/Threading/Monitor.cs
src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs
src/classlibnative/bcltype/objectnative.cpp
src/classlibnative/bcltype/objectnative.h
src/vm/comthreadpool.cpp
src/vm/comthreadpool.h
src/vm/ecalllist.h
src/vm/syncblk.cpp
src/vm/threadpoolrequest.h
src/vm/threads.cpp
src/vm/threads.h
src/vm/win32threadpool.cpp
src/vm/win32threadpool.h

index 19c043d..fc53fc1 100644 (file)
@@ -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;
+
         /// <summary>
         /// Dispatches work items to this thread.
         /// </summary>
@@ -1241,5 +1276,23 @@ namespace System.Threading
 
         internal static object[] GetLocallyQueuedWorkItemsForDebugger() =>
             ToObjectArray(GetLocallyQueuedWorkItems());
+
+        /// <summary>
+        /// Gets the number of work items that are currently queued to be processed.
+        /// </summary>
+        /// <remarks>
+        /// 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.
+        /// </remarks>
+        public static long PendingWorkItemCount
+        {
+            get
+            {
+                ThreadPoolWorkQueue workQueue = ThreadPoolGlobals.workQueue;
+                return workQueue.LocalCount + workQueue.GlobalCount + PendingUnmanagedWorkItemCount;
+            }
+        }
     }
 }
index 2701b3b..cb3a0f6 100644 (file)
@@ -233,5 +233,14 @@ namespace System.Threading
 
             ObjPulseAll(obj);
         }
+
+        /// <summary>
+        /// Gets the number of times there was contention upon trying to take a <see cref="Monitor"/>'s lock so far.
+        /// </summary>
+        public static extern long LockContentionCount
+        {
+            [MethodImpl(MethodImplOptions.InternalCall)]
+            get;
+        }
     }
 }
index 8903078..76eb2db 100644 (file)
@@ -231,6 +231,36 @@ namespace System.Threading
             GetAvailableThreadsNative(out workerThreads, out completionPortThreads);
         }
 
+        /// <summary>
+        /// Gets the number of thread pool threads that currently exist.
+        /// </summary>
+        /// <remarks>
+        /// For a thread pool implementation that may have different types of threads, the count includes all types.
+        /// </remarks>
+        public static extern int ThreadCount
+        {
+            [MethodImpl(MethodImplOptions.InternalCall)]
+            get;
+        }
+
+        /// <summary>
+        /// Gets the number of work items that have been processed so far.
+        /// </summary>
+        /// <remarks>
+        /// For a thread pool implementation that may have different types of work items, the count includes all types.
+        /// </remarks>
+        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,
index 5304e01..a5619e1 100644 (file)
@@ -355,3 +355,9 @@ FCIMPL1(FC_BOOL_RET, ObjectNative::IsLockHeld, Object* pThisUNSAFE)
 }
 FCIMPLEND
 
+FCIMPL0(INT64, ObjectNative::GetMonitorLockContentionCount)
+{
+    FCALL_CONTRACT;
+    return (INT64)Thread::GetTotalMonitorLockContentionCount();
+}
+FCIMPLEND
index 573b04e..3d008d9 100644 (file)
@@ -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_
index 18db995..f5dc423 100644 (file)
@@ -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)
 {
index 6b0f4ec..d830c9e 100644 (file)
@@ -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);
index 50071ed..4d9de5b 100644 (file)
@@ -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)
index ce31ac9..9aea18f 100644 (file)
@@ -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();
 
index eaba823..3b2da28 100644 (file)
@@ -221,6 +221,12 @@ public:
        _ASSERT(FALSE); 
     }   
 
+    inline ULONG GetNumRequests()
+    {
+        LIMITED_METHOD_CONTRACT;
+        return VolatileLoad(&m_NumRequests);
+    }
+
 private:
     SpinLock m_lock;
     ULONG m_NumRequests;
index 70c2611..37d30c3 100644 (file)
@@ -83,7 +83,9 @@ PTR_ThreadLocalModule ThreadLocalBlock::GetTLMIfExists(MethodTable* pMT)
 
 BOOL Thread::s_fCleanFinalizedThread = FALSE;
 
-Volatile<LONG> 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 {
index e5307d9..ee3e69f 100644 (file)
@@ -3871,21 +3871,113 @@ private:
 #endif // defined(FEATURE_PROFAPI_ATTACH_DETACH) || defined(DATA_PROFAPI_ATTACH_DETACH)
 
 private:
-    Volatile<LONG> m_threadPoolCompletionCount;
-    static Volatile<LONG> 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:
 
index f340542..da44beb 100644 (file)
@@ -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();
                 }
index 55f321c..ff47ea2 100644 (file)
@@ -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();
     }