Implement !threadpool in C# (#3824)
authorLee Culver <leculver@microsoft.com>
Thu, 13 Apr 2023 15:18:29 +0000 (08:18 -0700)
committerGitHub <noreply@github.com>
Thu, 13 Apr 2023 15:18:29 +0000 (08:18 -0700)
* Add initial ThreadPoolCommand

Only implemented dumping work items.

* Remove -live

* Fix issue with Marshal.SizeOf

Marshal.SizeOf does not allow enums.  Unsafe.SizeOf is what we want here.

* Implement the remainder of ThreadPool

* Use C# version of !threadpool

* Remove ObjectIterator and GCHeap

* Bump clrmd version

* Fix issue with usingPortableCompletionPorts

threadPool.Portable means that we found a PortableThreadPool in the BCL.  We need to check that we found that portable threadpool and also that the PortableIOField is true (or missing), instead of if the portable threadpool exists at all.

* Move cancellation to inside the loop

eng/Version.Details.xml
eng/Versions.props
src/Microsoft.Diagnostics.DebugServices.Implementation/DataReader.cs
src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolCommand.cs [new file with mode: 0644]
src/SOS/SOS.Hosting/Commands/SOSCommand.cs
src/SOS/Strike/sos.cpp
src/SOS/Strike/sos.h
src/SOS/Strike/strike.cpp
src/SOS/Strike/util.cpp
src/SOS/Strike/util.h
src/SOS/lldbplugin/soscommand.cpp

index 6dfbbd8a911f67fd903218385674c5046a19bbbe..e6f3099c90ab298d4e29fe243c15975df4691f93 100644 (file)
@@ -4,13 +4,13 @@
       <Uri>https://github.com/dotnet/symstore</Uri>
       <Sha>e09f81a0b38786cb20f66b589a8b88b6997a62da</Sha>
     </Dependency>
-    <Dependency Name="Microsoft.Diagnostics.Runtime" Version="3.0.0-beta.23210.1">
+    <Dependency Name="Microsoft.Diagnostics.Runtime" Version="3.0.0-beta.23212.1">
       <Uri>https://github.com/microsoft/clrmd</Uri>
-      <Sha>677f1b9c6fa27b1d2988eac9d51c8a5ea8698fdd</Sha>
+      <Sha>d68eb893272971726b08473935d2c7d088d1db5c</Sha>
     </Dependency>
-    <Dependency Name="Microsoft.Diagnostics.Runtime.Utilities" Version="3.0.0-beta.23210.1">
+    <Dependency Name="Microsoft.Diagnostics.Runtime.Utilities" Version="3.0.0-beta.23212.1">
       <Uri>https://github.com/microsoft/clrmd</Uri>
-      <Sha>677f1b9c6fa27b1d2988eac9d51c8a5ea8698fdd</Sha>
+      <Sha>d68eb893272971726b08473935d2c7d088d1db5c</Sha>
     </Dependency>
   </ProductDependencies>
   <ToolsetDependencies>
index cf0b959d034c191e4fa2612b4efd729a4bc4c9c2..cecc4b1342fc8192941c4bde418a64d820e9d68a 100644 (file)
@@ -45,7 +45,7 @@
     <SystemReflectionMetadataVersion>5.0.0</SystemReflectionMetadataVersion>
     <!-- Other libs -->
     <MicrosoftBclAsyncInterfacesVersion>6.0.0</MicrosoftBclAsyncInterfacesVersion>
-    <MicrosoftDiagnosticsRuntimeVersion>3.0.0-beta.23210.1</MicrosoftDiagnosticsRuntimeVersion>
+    <MicrosoftDiagnosticsRuntimeVersion>3.0.0-beta.23212.1</MicrosoftDiagnosticsRuntimeVersion>
     <MicrosoftDiaSymReaderNativePackageVersion>16.9.0-beta1.21055.5</MicrosoftDiaSymReaderNativePackageVersion>
     <MicrosoftDiagnosticsTracingTraceEventVersion>3.0.7</MicrosoftDiagnosticsTracingTraceEventVersion>
     <MicrosoftExtensionsLoggingVersion>6.0.0</MicrosoftExtensionsLoggingVersion>
index b32038d5406903a68bd9d0209eba7b5b453534d3..89716bf326f210a494311041f8a0fb645c6435da 100644 (file)
@@ -83,7 +83,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
 
         bool IMemoryReader.Read<T>(ulong address, out T value)
         {
-            Span<byte> buffer = stackalloc byte[Marshal.SizeOf<T>()];
+            Span<byte> buffer = stackalloc byte[Unsafe.SizeOf<T>()];
             if (((IMemoryReader)this).Read(address, buffer) == buffer.Length)
             {
                 value = Unsafe.As<byte, T>(ref MemoryMarshal.GetReference(buffer));
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolCommand.cs
new file mode 100644 (file)
index 0000000..074bb53
--- /dev/null
@@ -0,0 +1,242 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.Runtime;
+using static Microsoft.Diagnostics.ExtensionCommands.TableOutput;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+    [Command(Name = "threadpool", Help = "Displays info about the runtime thread pool.")]
+    public sealed class ThreadPoolCommand : CommandBase
+    {
+        [ServiceImport]
+        public ClrRuntime Runtime { get; set; }
+
+        [Option(Name = "-ti", Help = "Print the hill climbing log.", Aliases = new string[] { "-hc" })]
+        public bool PrintHillClimbingLog { get; set; }
+
+        [Option(Name = "-wi", Help = "Print all work items that are queued.")]
+        public bool PrintWorkItems { get; set; }
+
+        public override void Invoke()
+        {
+            // Runtime.ThreadPool shouldn't be null unless there was a problem with the dump.
+            ClrThreadPool threadPool = Runtime.ThreadPool;
+            if (threadPool is null)
+            {
+                Console.WriteLineError("Failed to obtain ThreadPool data.");
+            }
+            else
+            {
+                TableOutput output = new(Console, (17, ""));
+                output.WriteRow("CPU utilization:", $"{threadPool.CpuUtilization}%");
+                output.WriteRow("Workers Total:", threadPool.ActiveWorkerThreads + threadPool.IdleWorkerThreads + threadPool.RetiredWorkerThreads);
+                output.WriteRow("Workers Running:", threadPool.ActiveWorkerThreads);
+                output.WriteRow("Workers Idle:", threadPool.IdleWorkerThreads);
+                output.WriteRow("Worker Min Limit:", threadPool.MinThreads);
+                output.WriteRow("Worker Max Limit:", threadPool.MaxThreads);
+                Console.WriteLine();
+
+                ClrType threadPoolType = Runtime.BaseClassLibrary.GetTypeByName("System.Threading.ThreadPool");
+                ClrStaticField usePortableIOField = threadPoolType?.GetStaticFieldByName("UsePortableThreadPoolForIO");
+
+                // Desktop CLR work items.
+                if (PrintWorkItems)
+                {
+                    LegacyThreadPoolWorkRequest[] requests = threadPool.EnumerateLegacyWorkRequests().ToArray();
+                    if (requests.Length > 0)
+                    {
+                        Console.WriteLine($"Work Request in Queue: {requests.Length:n0}");
+                        foreach (LegacyThreadPoolWorkRequest request in requests)
+                        {
+                            Console.CancellationToken.ThrowIfCancellationRequested();
+
+                            if (request.IsAsyncTimerCallback)
+                            {
+                                Console.WriteLine($"    AsyncTimerCallbackCompletion TimerInfo@{request.Context:x}");
+                            }
+                            else
+                            {
+                                Console.WriteLine($"    Unknown Function: {request.Function:x}  Context: {request.Context:x}");
+                            }
+                        }
+                    }
+                }
+
+                // We will assume that if UsePortableThreadPoolForIO field is deleted from ThreadPool then we are always
+                // using C# version.
+                bool usingPortableCompletionPorts = threadPool.Portable && (usePortableIOField is null || usePortableIOField.Read<bool>(usePortableIOField.Type.Module.AppDomain));
+                if (!usingPortableCompletionPorts)
+                {
+                    output = new(Console, (17, ""));
+                    output.WriteRow("Completion Total:", threadPool.TotalCompletionPorts);
+                    output.WriteRow("Completion Free:", threadPool.FreeCompletionPorts);
+                    output.WriteRow("Completion MaxFree:", threadPool.MaxFreeCompletionPorts);
+                    output.WriteRow("Completion Current Limit:", threadPool.CompletionPortCurrentLimit);
+                    output.WriteRow("Completion Min Limit:", threadPool.MinCompletionPorts);
+                    output.WriteRow("Completion Max Limit:", threadPool.MaxCompletionPorts);
+                    Console.WriteLine();
+                }
+
+                if (PrintHillClimbingLog)
+                {
+                    HillClimbingLogEntry[] hcl = threadPool.EnumerateHillClimbingLog().ToArray();
+                    if (hcl.Length > 0)
+                    {
+                        output = new(Console, (10, ""), (19, ""), (12, "n0"), (12, "n0"));
+
+                        Console.WriteLine("Hill Climbing Log:");
+                        output.WriteRow("Time", "Transition", "#New Threads", "#Samples", "Throughput");
+
+                        int end = hcl.Last().TickCount;
+                        foreach (HillClimbingLogEntry entry in hcl)
+                        {
+                            Console.CancellationToken.ThrowIfCancellationRequested();
+                            output.WriteRow($"{(entry.TickCount - end)/1000.0:0.00}", entry.StateOrTransition, entry.NewControlSetting, entry.LastHistoryCount, $"{entry.LastHistoryMean:0.00}");
+                        }
+
+                        Console.WriteLine();
+                    }
+                }
+            }
+
+            // We can print managed work items even if we failed to request the ThreadPool.
+            if (PrintWorkItems && (threadPool is null || threadPool.Portable))
+            {
+                DumpWorkItems();
+            }
+        }
+
+        private void DumpWorkItems()
+        {
+            TableOutput output = null;
+
+            ClrType workQueueType = Runtime.BaseClassLibrary.GetTypeByName("System.Threading.ThreadPoolWorkQueue");
+            ClrType workStealingQueueType = Runtime.BaseClassLibrary.GetTypeByName("System.Threading.ThreadPoolWorkQueue+WorkStealingQueue");
+
+            foreach (ClrObject obj in Runtime.Heap.EnumerateObjects())
+            {
+                Console.CancellationToken.ThrowIfCancellationRequested();
+
+                if (obj.Type == workQueueType)
+                {
+                    if (obj.TryReadObjectField("highPriorityWorkItems", out ClrObject workItems))
+                    {
+                        foreach (ClrObject entry in EnumerateConcurrentQueue(workItems))
+                        {
+                            WriteEntry(ref output, entry, isHighPri: true);
+                        }
+                    }
+
+                    if (obj.TryReadObjectField("workItems", out workItems))
+                    {
+                        foreach (ClrObject entry in EnumerateConcurrentQueue(workItems))
+                        {
+                            WriteEntry(ref output, entry, isHighPri: false);
+                        }
+                    }
+
+                    if (obj.Type.Fields.Any(r => r.Name == "_assignableWorkItems"))
+                    {
+                        if (obj.TryReadObjectField("_assignableWorkItems", out workItems))
+                        {
+                            foreach (ClrObject entry in EnumerateConcurrentQueue(workItems))
+                            {
+                                WriteEntry(ref output, entry, isHighPri: false);
+                            }
+                        }
+                    }
+                }
+                else if (obj.Type == workStealingQueueType)
+                {
+                    if (obj.TryReadObjectField("m_array", out ClrObject m_array) && m_array.IsValid && !m_array.IsNull)
+                    {
+                        ClrArray arrayView = m_array.AsArray();
+                        int len = Math.Min(8192, arrayView.Length); // ensure a sensible max in case we have heap corruption
+
+                        nuint[] buffer = arrayView.ReadValues<nuint>(0, len);
+                        if (buffer != null)
+                        {
+                            for (int i = 0; i < len; i++)
+                            {
+                                if (buffer[i] != 0)
+                                {
+                                    ClrObject entry = Runtime.Heap.GetObject(buffer[i]);
+                                    if (entry.IsValid && !entry.IsNull)
+                                    {
+                                        WriteEntry(ref output, entry, isHighPri: false);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        private void WriteEntry(ref TableOutput output, ClrObject entry, bool isHighPri)
+        {
+            if (output is null)
+            {
+                output = new(Console, (17, ""), (16, "x12"))
+                {
+                    AlignLeft = true,
+                };
+
+                output.WriteRow("Queue", "Object", "Type");
+            }
+
+            output.WriteRow(isHighPri ? "[Global high-pri]" : "[Global]", new DmlDumpObj(entry), entry.Type?.Name);
+            if (entry.IsDelegate)
+            {
+                ClrDelegate del = entry.AsDelegate();
+                ClrDelegateTarget target = del.GetDelegateTarget();
+                if (target is not null)
+                {
+                    Console.WriteLine($"    => {target.TargetObject.Address:x} {target.Method.Name}");
+                }
+            }
+        }
+
+        private IEnumerable<ClrObject> EnumerateConcurrentQueue(ClrObject concurrentQueue)
+        {
+            if (!concurrentQueue.IsValid || concurrentQueue.IsNull)
+            {
+                yield break;
+            }
+
+            if (concurrentQueue.TryReadObjectField("_head", out ClrObject curr))
+            {
+                while (curr.IsValid && !curr.IsNull)
+                {
+                    Console.CancellationToken.ThrowIfCancellationRequested();
+
+                    if (curr.TryReadObjectField("_slots", out ClrObject slots) && slots.IsValid && slots.IsArray)
+                    {
+                        ClrArray slotsArray = slots.AsArray();
+                        for (int i = 0; i < slotsArray.Length; i++)
+                        {
+                            Console.CancellationToken.ThrowIfCancellationRequested();
+
+                            ClrObject item = slotsArray.GetStructValue(i).ReadObjectField("Item");
+                            if (item.IsValid && !item.IsNull)
+                            {
+                                yield return item;
+                            }
+                        }
+                    }
+
+                    if (!curr.TryReadObjectField("_nextSegment", out curr))
+                    {
+                        Console.WriteLineError($"Error:  Type '{slots.Type?.Name}' does not contain a '_nextSegment' field.");
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}
index 7f740a181de9c7823c8ed3686da7c89141a80856..d398b194598629143fbd744d68b6c07880f3c70d 100644 (file)
@@ -47,7 +47,6 @@ namespace SOS.Hosting
     [Command(Name = "printexception",    DefaultOptions = "PrintException",      Aliases = new string[] { "pe" }, Help = "Displays and formats fields of any object derived from the Exception class at the specified address.")]
     [Command(Name = "soshelp",           DefaultOptions = "Help",                Help = "Displays help for a specific SOS command.")]
     [Command(Name = "syncblk",           DefaultOptions = "SyncBlk",             Help = "Displays the SyncBlock holder info.")]
-    [Command(Name = "threadpool",        DefaultOptions = "ThreadPool",          Help = "Lists basic information about the thread pool.")]
     [Command(Name = "threadstate",       DefaultOptions = "ThreadState",         Help = "Pretty prints the meaning of a threads state.")]
     [Command(Name = "comstate",          DefaultOptions = "COMState",            Flags = CommandFlags.Windows, Help = "Lists the COM apartment model for each thread.")]
     [Command(Name = "dumprcw",           DefaultOptions = "DumpRCW",             Flags = CommandFlags.Windows, Help = "Displays information about a Runtime Callable Wrapper.")]
index d637289253d363214c25535b79fed0978840bff7..3a9e528eecfdc2178c6415fa5dd8226e4bff1bab 100644 (file)
@@ -376,322 +376,6 @@ namespace sos
         return (size_t)stInfo.m_StringLength;
     }
 
-    const TADDR GCHeap::HeapStart = 0;
-    const TADDR GCHeap::HeapEnd = ~0;
-
-    ObjectIterator::ObjectIterator(const GCHeapDetails *heap, int numHeaps, TADDR start, TADDR stop)
-    : bLarge(false), bPinned(false), mCurrObj(0), mLastObj(0), mStart(start), mEnd(stop), mSegmentEnd(0), mHeaps(heap),
-      mNumHeaps(numHeaps), mCurrHeap(0), mCurrRegionGen(0)
-    {
-        mAllocInfo.Init();
-        SOS_Assert(numHeaps > 0);
-
-        TADDR segStart;
-        if (heap->has_regions)
-        {
-            // with regions, we have a null terminated list for each generation
-            segStart = TO_TADDR(mHeaps[0].generation_table[mCurrRegionGen].start_segment);
-        }
-        else
-        {
-            segStart = TO_TADDR(mHeaps[0].generation_table[GetMaxGeneration()].start_segment);
-        }
-        if (FAILED(mSegment.Request(g_sos, segStart, mHeaps[0].original_heap_details)))
-        {
-            sos::Throw<DataRead>("Could not request segment data at %p.", segStart);
-        }
-
-        mCurrObj = mStart < TO_TADDR(mSegment.mem) ? TO_TADDR(mSegment.mem) : mStart;
-        mSegmentEnd = TO_TADDR(mSegment.highAllocMark);
-
-        TryAlignToObjectInRange();
-    }
-
-    bool ObjectIterator::TryMoveNextSegment()
-    {
-        CheckInterrupt();
-
-        if (mCurrHeap >= mNumHeaps)
-        {
-            return false;
-        }
-
-        TADDR next = TO_TADDR(mSegment.next);
-        if (next == NULL)
-        {
-            if (mHeaps[mCurrHeap].has_regions)
-            {
-                mCurrRegionGen++;
-                if ((mCurrRegionGen > GetMaxGeneration() + 2) ||
-                    (mCurrRegionGen > GetMaxGeneration() + 1 && !mHeaps[mCurrHeap].has_poh))
-                {
-                    mCurrHeap++;
-                    if (mCurrHeap == mNumHeaps)
-                    {
-                        return false;
-                    }
-                    mCurrRegionGen = 0;
-                }
-                next = TO_TADDR(mHeaps[mCurrHeap].generation_table[mCurrRegionGen].start_segment);
-            }
-            else if (bPinned || (bLarge && !mHeaps[mCurrHeap].has_poh))
-            {
-                mCurrHeap++;
-                if (mCurrHeap == mNumHeaps)
-                {
-                    return false;
-                }
-
-                bPinned = false;
-                bLarge = false;
-                next = TO_TADDR(mHeaps[mCurrHeap].generation_table[GetMaxGeneration()].start_segment);
-            }
-            else if (bLarge)
-            {
-                bLarge = false;
-                bPinned = true;
-                next = TO_TADDR(mHeaps[mCurrHeap].generation_table[GetMaxGeneration() + 2].start_segment);
-            }
-            else
-            {
-                bLarge = true;
-                next = TO_TADDR(mHeaps[mCurrHeap].generation_table[GetMaxGeneration() + 1].start_segment);
-            }
-        }
-
-        SOS_Assert(next != NULL);
-        if (FAILED(mSegment.Request(g_sos, next, mHeaps[mCurrHeap].original_heap_details)))
-        {
-            sos::Throw<DataRead>("Failed to request segment data at %p.", next);
-        }
-
-        mLastObj = 0;
-        mCurrObj = mStart < TO_TADDR(mSegment.mem) ? TO_TADDR(mSegment.mem) : mStart;
-        mSegmentEnd = TO_TADDR(mSegment.highAllocMark);
-        return true;
-    }
-
-    bool ObjectIterator::TryMoveToObjectInNextSegmentInRange()
-    {
-        if (TryMoveNextSegment())
-        {
-            return TryAlignToObjectInRange();
-        }
-
-        return false;
-    }
-
-    bool ObjectIterator::TryAlignToObjectInRange()
-    {
-        CheckInterrupt();
-        while (!MemOverlap(mStart, mEnd, TO_TADDR(mSegment.mem), mSegmentEnd))
-        {
-            CheckInterrupt();
-            if (!TryMoveNextSegment())
-            {
-                return false;
-            }
-        }
-
-        // At this point we know that the current segment contains objects in
-        // the correct range.  However, there's no telling if the user gave us
-        // a starting address that corresponds to an object.  If mStart is a
-        // valid object, then we'll just start there.  If it's not we'll need
-        // to walk the segment from the beginning to find the first aligned
-        // object on or after mStart.
-        if (mCurrObj == mStart && !Object::IsValid(mStart))
-        {
-            // It's possible mCurrObj will equal mStart after this.  That's fine.
-            // It means that the starting object is corrupt (and we'll figure
-            // that when the user calls GetNext), or IsValid was wrong.
-            mLastObj = 0;
-            mCurrObj = TO_TADDR(mSegment.mem);
-            while (mCurrObj < mStart)
-                MoveToNextObject();
-        }
-
-        return true;
-    }
-
-
-
-    const Object &ObjectIterator::operator*() const
-    {
-        AssertSanity();
-        return mCurrObj;
-    }
-
-
-    const Object *ObjectIterator::operator->() const
-    {
-        AssertSanity();
-        return &mCurrObj;
-    }
-
-    //Object ObjectIterator::GetNext()
-    const ObjectIterator &ObjectIterator::operator++()
-    {
-        CheckInterrupt();
-
-        // Assert we aren't done walking the heap.
-        SOS_Assert(*this);
-        AssertSanity();
-
-        MoveToNextObject();
-        return *this;
-    }
-
-    void ObjectIterator::MoveToNextObjectCarefully()
-    {
-        CheckInterrupt();
-
-        SOS_Assert(*this);
-        AssertSanity();
-
-        // Move to NextObject won't generally throw unless it fails to request the
-        // MethodTable of the object.  At which point we won't know how large the
-        // current object is, nor how to move past it.  In this case we'll simply
-        // move to the next segment if possible to continue iterating from there.
-        try
-        {
-            MoveToNextObject();
-        }
-        catch(const sos::Exception &)
-        {
-            TryMoveToObjectInNextSegmentInRange();
-        }
-    }
-
-    void ObjectIterator::AssertSanity() const
-    {
-        // Assert that we are in a sane state. Function which call this assume two things:
-        //   1. That the current object is within the segment bounds.
-        //   2. That the current object is within the requested memory range.
-        SOS_Assert(mCurrObj >= TO_TADDR(mSegment.mem));
-        SOS_Assert(mCurrObj <= TO_TADDR(mSegmentEnd - Align(min_obj_size)));
-
-        SOS_Assert(mCurrObj >= mStart);
-        SOS_Assert(mCurrObj <= mEnd);
-    }
-
-    void ObjectIterator::MoveToNextObject()
-    {
-        CheckInterrupt();
-
-        // Object::GetSize can be unaligned, so we must align it ourselves.
-        size_t size = (bLarge || bPinned) ? AlignLarge(mCurrObj.GetSize()) : Align(mCurrObj.GetSize());
-
-        mLastObj = mCurrObj;
-        mCurrObj = mCurrObj.GetAddress() + size;
-
-        if (!bLarge)
-        {
-            // Is this the end of an allocation context? We need to know this because there can be
-            // allocated memory at the end of an allocation context that doesn't yet contain any objects.
-            // This happens because we actually allocate a minimum amount of memory (the allocation quantum)
-            // whenever we need to get more memory. Typically, a single allocation request won't fill this
-            // block, so we'll fulfill subsequent requests out of the remainder of the block until it's
-            // depleted.
-            int i;
-            for (i = 0; i < mAllocInfo.num; i ++)
-            {
-                if (mCurrObj == TO_TADDR(mAllocInfo.array[i].alloc_ptr)) // end of objects in this context
-                {
-                    // Set mCurrObj to point after the context (alloc_limit is the end of the allocation context).
-                    mCurrObj = TO_TADDR(mAllocInfo.array[i].alloc_limit) + Align(min_obj_size);
-                    break;
-                }
-            }
-
-            // We also need to look at the gen0 alloc context.
-            if (mCurrObj == TO_TADDR(mHeaps[mCurrHeap].generation_table[0].allocContextPtr))
-                mCurrObj = TO_TADDR(mHeaps[mCurrHeap].generation_table[0].allocContextLimit) + Align(min_obj_size);
-        }
-
-        if (mCurrObj > mEnd || mCurrObj >= mSegmentEnd)
-        {
-            TryMoveToObjectInNextSegmentInRange();
-        }
-    }
-
-    SyncBlkIterator::SyncBlkIterator()
-    : mCurr(1), mTotal(0)
-    {
-        // If DacpSyncBlockData::Request fails with the call "1", then it means
-        // there are no SyncBlocks in the process.
-        DacpSyncBlockData syncBlockData;
-        if (SUCCEEDED(syncBlockData.Request(g_sos, 1)))
-        {
-            mTotal = syncBlockData.SyncBlockCount;
-            mSyncBlk = mCurr;
-        }
-    }
-
-    GCHeap::GCHeap()
-    {
-        if (FAILED(mHeapData.Request(g_sos)))
-        {
-            sos::Throw<DataRead>("Failed to request GC heap data.");
-        }
-
-        if (mHeapData.bServerMode)
-        {
-            mNumHeaps = mHeapData.HeapCount;
-            DWORD dwAllocSize = 0;
-            if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), mNumHeaps, dwAllocSize))
-            {
-                sos::Throw<Exception>("Failed to get GCHeaps: Integer overflow.");
-            }
-
-            CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize);
-            if (FAILED(g_sos->GetGCHeapList(mNumHeaps, heapAddrs, NULL)))
-            {
-                sos::Throw<DataRead>("Failed to get GCHeaps.");
-            }
-
-            mHeaps = new GCHeapDetails[mNumHeaps];
-
-            for (int i = 0; i < mNumHeaps; i++)
-            {
-                DacpGcHeapDetails dacHeapDetails;
-                if (FAILED(dacHeapDetails.Request(g_sos, heapAddrs[i])))
-                {
-                    sos::Throw<DataRead>("Failed to get GC heap details at %p.", heapAddrs[i]);
-                }
-
-                mHeaps[i].Set(dacHeapDetails, heapAddrs[i]);
-            }
-        }
-        else
-        {
-            mHeaps = new GCHeapDetails[1];
-            mNumHeaps = 1;
-
-            DacpGcHeapDetails dacGCDetails;
-            if (FAILED(dacGCDetails.Request(g_sos)))
-            {
-                sos::Throw<DataRead>("Failed to request GC details data.");
-            }
-
-            mHeaps[0].Set(dacGCDetails);
-        }
-    }
-
-    GCHeap::~GCHeap()
-    {
-        delete [] mHeaps;
-    }
-
-    ObjectIterator GCHeap::WalkHeap(TADDR start, TADDR stop) const
-    {
-        return ObjectIterator(mHeaps, mNumHeaps, start, stop);
-    }
-
-    bool GCHeap::AreGCStructuresValid() const
-    {
-        return mHeapData.bGcStructuresValid != FALSE;
-    }
-
     // SyncBlk class
     SyncBlk::SyncBlk()
         : mIndex(0)
index c0fed3a3b2f3f7858df0a9ae1498975eb5ee7c07..170722378a4fb587a674c0d5d6018ff91b6d1100 100644 (file)
@@ -32,8 +32,6 @@ class CGCDescSeries;
 
 namespace sos
 {
-    class GCHeap;
-
     /* The base SOS Exception.  Note that most commands should not attempt to be
      * resilient to exceptions thrown by most functions here.  Instead a top level
      * try/catch at the beginning of the command which prints out the exception's
@@ -452,121 +450,6 @@ namespace sos
         mutable WCHAR *mTypeName;
     };
 
-    /* The Iterator used to walk the managed objects on the GC heap.
-     * The general usage pattern for this class is:
-     *   for (ObjectIterator itr = gcheap.WalkHeap(); itr; ++itr)
-     *     itr->SomeObjectMethod();
-     */
-    class ObjectIterator
-    {
-        friend class GCHeap;
-    public:
-
-        /* Returns the next object in the GCHeap.  Note that you must ensure
-         * that there are more objects to walk before calling this function by
-         * checking "if (iterator)".  If this function throws an exception,
-         * the the iterator is invalid, and should no longer be used to walk
-         * the heap.  This should generally only happen if we cannot read the
-         * MethodTable of the object to move to the next object.
-         * Throws:
-         *   DataRead
-         */
-        const ObjectIterator &operator++();
-
-        /* Dereference operator.  This allows you to take a reference to the
-         * current object.  Note the lifetime of this reference is valid for
-         * either the lifetime of the iterator or until you call operator++,
-         * whichever is shorter.  For example.
-         *   void Foo(const Object &param);
-         *   void Bar(const ObjectIterator &itr)
-         *   {
-         *      Foo(*itr);
-         *   }
-         */
-        const Object &operator*() const;
-
-        /* Returns a pointer to the current Object to call members on it.
-         * The usage pattern for the iterator is to simply use operator->
-         * to call methods on the Object it points to without taking a
-         * direct reference to the underlying Object if at all possible.
-         */
-        const Object *operator->() const;
-
-        /* Returns false when the iterator has reached the end of the managed
-         * heap.
-         */
-        inline operator void *() const
-        {
-            return (void*)(SIZE_T)(mCurrHeap == mNumHeaps ? 0 : 1);
-        }
-
-        /* Do not use.
-         * TODO: Replace this functionality with int Object::GetGeneration().
-         */
-        bool IsCurrObjectOnLOH() const
-        {
-            SOS_Assert(*this);
-            return bLarge;
-        }
-
-        /* Attempts to move to the next object (similar to ObjectIterator++), but
-         * attempts to recover from any heap corruption by skipping to the next
-         * segment.  If Verify returns false, meaning it detected heap corruption
-         * at the current object, you can use MoveToNextObjectCarefully instead of
-         * ObjectIterator++ to attempt to keep reading from the heap.  If possible,
-         * this function attempts to move to the next object in the same segment,
-         * but if that's not possible then it skips to the next segment and
-         * continues from there.
-         * Note:
-         *   This function can throw, and if it does then the iterator is no longer
-         *   in a valid state.  No further attempts to move to the next object will
-         *   be possible.
-         * Throws:
-         *   DataRead - if the heap is corrupted and it's not possible to continue
-         *              walking the heap
-         */
-        void MoveToNextObjectCarefully();
-
-    private:
-        ObjectIterator(const GCHeapDetails *heap, int numHeaps, TADDR start, TADDR stop);
-
-        void AssertSanity() const;
-
-        /*
-            This function moves to the next segment/region without checking any restrictions
-            on the range. Returns true if it was able to move to a new segment/region.
-        */
-        bool TryMoveNextSegment();
-
-        /*
-            Aligns the iterator to the object that falls in the requested range, moving to 
-            the next segment/region as necessary. The iterator state doesn't change if the
-            current object already lies in the requested range. Returns true if aligning
-            to such an object was possible.
-        */
-        bool TryAlignToObjectInRange();
-
-        /*
-            Moves to the next segment/region that contains an object in the requested
-            range and align it to such object. This operation always moves the iterator.
-            Returns false if no such move was possible.
-        */
-        bool TryMoveToObjectInNextSegmentInRange();
-        void MoveToNextObject();
-
-    private:
-        DacpHeapSegmentData mSegment;
-        bool bLarge;
-        bool bPinned;
-        Object mCurrObj;
-        TADDR mLastObj, mStart, mEnd, mSegmentEnd;
-        AllocInfo mAllocInfo;
-        const GCHeapDetails *mHeaps;
-        int mNumHeaps;
-        int mCurrHeap;
-        unsigned mCurrRegionGen;
-    };
-
     /* Reprensents an entry in the sync block table.
      */
     class SyncBlk
@@ -671,53 +554,6 @@ namespace sos
         SyncBlk mSyncBlk;
     };
 
-    /* An class which contains information about the GCHeap.
-     */
-    class GCHeap
-    {
-    public:
-        static const TADDR HeapStart;  // A constant signifying the start of the GC heap.
-        static const TADDR HeapEnd;    // A constant signifying the end of the GC heap.
-
-    public:
-        /* Constructor.
-         * Throws:
-         *   DataRead
-         */
-        GCHeap();
-
-        ~GCHeap();
-
-        /* Returns an ObjectIterator which allows you to walk the objects on the managed heap.
-         * This ObjectIterator is valid for the duration of the GCHeap's lifetime.  Note that
-         * if you specify an address at which you wish to start walking the heap it need
-         * not point directly to a managed object.  However, if it does not, WalkHeap
-         * will need to walk the segment that address resides in to find the first object
-         * after that address, and if it encounters any heap corruption along the way,
-         * it may be impossible to walk the heap from the address specified.
-         *
-         * Params:
-         *   start - The starting address at which you want to start walking the heap.
-         *           This need not point directly to an object on the heap.
-         *   end - The ending address at which you want to stop walking the heap.  This
-         *         need not point directly to an object on the heap.
-         *   validate - Whether or not you wish to validate the GC heap as you walk it.
-         * Throws:
-         *   DataRead
-         */
-        ObjectIterator WalkHeap(TADDR start = HeapStart, TADDR stop = HeapEnd) const;
-
-        /* Returns true if the GC Heap structures are in a valid state for traversal.
-         * Returns false if not (e.g. if we are in the middle of a relocation).
-         */
-        bool AreGCStructuresValid() const;
-
-    private:
-        GCHeapDetails *mHeaps;
-        DacpGcHeapData mHeapData;
-        int mNumHeaps;
-    };
-
     // convenience functions
     /* A temporary wrapper function for Object::IsValid.  There are too many locations
      * in SOS which need to use IsObject but have a wide variety of internal
index 410a8ea409a785e5e5ee405ee1a16e311862ae97..f0efe93e8c37b4a5cf49a94be462bde14bfd6562 100644 (file)
@@ -6597,595 +6597,10 @@ DECLARE_API(bpmd)
 \**********************************************************************/
 DECLARE_API(ThreadPool)
 {
-    INIT_API();
+    INIT_API_EXT();
     MINIDUMP_NOT_SUPPORTED();
 
-    BOOL doHCDump = FALSE, doWorkItemDump = FALSE, dml = FALSE;
-    BOOL mustBePortableThreadPool = FALSE;
-
-    CMDOption option[] =
-    {   // name, vptr, type, hasValue
-        {"-ti", &doHCDump, COBOOL, FALSE},
-        {"-wi", &doWorkItemDump, COBOOL, FALSE},
-        {"/d", &dml, COBOOL, FALSE},
-    };
-
-    if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL))
-    {
-        return E_FAIL;
-    }
-
-    EnableDMLHolder dmlHolder(dml);
-
-    DacpThreadpoolData threadpool;
-    Status = threadpool.Request(g_sos);
-    if (Status == E_NOTIMPL)
-    {
-        mustBePortableThreadPool = TRUE;
-    }
-    else if (Status != S_OK)
-    {
-        ExtOut("    %s\n", "Failed to request ThreadpoolMgr information");
-        return FAILED(Status) ? Status : E_FAIL;
-    }
-
-    DWORD_PTR corelibModule;
-    {
-        int numModule;
-        ArrayHolder<DWORD_PTR> moduleList = ModuleFromName(const_cast<LPSTR>("System.Private.CoreLib.dll"), &numModule);
-        if (moduleList == NULL || numModule != 1)
-        {
-            ExtOut("    %s\n", "Failed to find System.Private.CoreLib.dll");
-            return E_FAIL;
-        }
-        corelibModule = moduleList[0];
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-    // Check whether the portable thread pool is being used and fill in the thread pool data
-
-    UINT64 ui64Value = 0;
-    DacpObjectData vPortableTpHcLogArray;
-    int portableTpHcLogEntry_tickCountOffset = 0;
-    int portableTpHcLogEntry_stateOrTransitionOffset = 0;
-    int portableTpHcLogEntry_newControlSettingOffset = 0;
-    int portableTpHcLogEntry_lastHistoryCountOffset = 0;
-    int portableTpHcLogEntry_lastHistoryMeanOffset = 0;
-    do // while (false)
-    {
-        if (!mustBePortableThreadPool)
-        {
-            // Determine if the portable thread pool is enabled
-            if (FAILED(
-                GetNonSharedStaticFieldValueFromName(
-                    &ui64Value,
-                    corelibModule,
-                    "System.Threading.ThreadPool",
-                    W("UsePortableThreadPool"),
-                    ELEMENT_TYPE_BOOLEAN)) ||
-                ui64Value == 0)
-            {
-                // The type was not loaded yet, or the static field was not found, etc. For now assume that the portable thread pool
-                // is not being used.
-                break;
-            }
-        }
-
-        // Get the thread pool instance
-        if (FAILED(
-                GetNonSharedStaticFieldValueFromName(
-                    &ui64Value,
-                    corelibModule,
-                    "System.Threading.PortableThreadPool",
-                    W("ThreadPoolInstance"),
-                    ELEMENT_TYPE_CLASS)) ||
-            ui64Value == 0)
-        {
-            // The type was not loaded yet, or the static field was not found, etc. For now assume that the portable thread pool
-            // is not being used.
-            break;
-        }
-        CLRDATA_ADDRESS cdaTpInstance = TO_CDADDR(ui64Value);
-
-        // Get the thread pool method table
-        CLRDATA_ADDRESS cdaTpMethodTable;
-        {
-            TADDR tpMethodTableAddr = NULL;
-            if (FAILED(GetMTOfObject(TO_TADDR(cdaTpInstance), &tpMethodTableAddr)))
-            {
-                break;
-            }
-            cdaTpMethodTable = TO_CDADDR(tpMethodTableAddr);
-        }
-
-        DWORD_PTR ptrValue = 0;
-        INT32 i32Value = 0;
-        INT16 i16Value = 0;
-        int offset = 0;
-
-        // Populate fields of the thread pool with simple types
-        {
-            offset = GetObjFieldOffset(cdaTpInstance, cdaTpMethodTable, W("_cpuUtilization"));
-            if (offset <= 0 || FAILED(MOVE(i32Value, cdaTpInstance + offset)))
-            {
-                ExtOut("    %s\n", "Failed to read PortableThreadPool._cpuUtilization");
-                break;
-            }
-            threadpool.cpuUtilization = i32Value;
-
-            offset = GetObjFieldOffset(cdaTpInstance, cdaTpMethodTable, W("_minThreads"));
-            if (offset <= 0 || FAILED(MOVE(i16Value, cdaTpInstance + offset)))
-            {
-                ExtOut("    %s\n", "Failed to read PortableThreadPool._minThreads");
-                break;
-            }
-            threadpool.MinLimitTotalWorkerThreads = i16Value;
-
-            offset = GetObjFieldOffset(cdaTpInstance, cdaTpMethodTable, W("_maxThreads"));
-            if (offset <= 0 || FAILED(MOVE(i16Value, cdaTpInstance + offset)))
-            {
-                ExtOut("    %s\n", "Failed to read PortableThreadPool._maxThreads");
-                break;
-            }
-            threadpool.MaxLimitTotalWorkerThreads = i16Value;
-        }
-
-        // Populate thread counts
-        {
-            DacpFieldDescData vSeparatedField;
-            offset = GetObjFieldOffset(cdaTpInstance, cdaTpMethodTable, W("_separated"), TRUE, &vSeparatedField);
-            if (offset <= 0)
-            {
-                ExtOut("    %s\n", "Failed to read PortableThreadPool._separated");
-                break;
-            }
-            int accumulatedOffset = offset;
-
-            DacpFieldDescData vCountsField;
-            offset = GetValueFieldOffset(vSeparatedField.MTOfType, W("counts"), &vCountsField);
-            if (offset < 0)
-            {
-                ExtOut("    %s\n", "Failed to read PortableThreadPool._separated.counts");
-                break;
-            }
-            accumulatedOffset += offset;
-
-            offset = GetValueFieldOffset(vCountsField.MTOfType, W("_data"));
-            if (offset < 0 || FAILED(MOVE(ui64Value, cdaTpInstance + accumulatedOffset + offset)))
-            {
-                ExtOut("    %s\n", "Failed to read PortableThreadPool._separated.counts._data");
-                break;
-            }
-            UINT64 data = ui64Value;
-
-            const UINT8 NumProcessingWorkShift = 0;
-            const UINT8 NumExistingThreadsShift = 16;
-
-            INT16 numProcessingWork = (INT16)(data >> NumProcessingWorkShift);
-            INT16 numExistingThreads = (INT16)(data >> NumExistingThreadsShift);
-
-            threadpool.NumIdleWorkerThreads = numExistingThreads - numProcessingWork;
-            threadpool.NumWorkingWorkerThreads = numProcessingWork;
-            threadpool.NumRetiredWorkerThreads = 0;
-        }
-
-        // Populate hill climbing log info
-        {
-            threadpool.HillClimbingLog = 0; // this indicates that the portable thread pool's hill climbing data should be used
-            threadpool.HillClimbingLogFirstIndex = 0;
-            threadpool.HillClimbingLogSize = 0;
-
-            // Get the hill climbing instance
-            if (FAILED(
-                    GetNonSharedStaticFieldValueFromName(
-                        &ui64Value,
-                        corelibModule,
-                        "System.Threading.PortableThreadPool+HillClimbing",
-                        W("ThreadPoolHillClimber"),
-                        ELEMENT_TYPE_CLASS)) ||
-                ui64Value == 0)
-            {
-                // The type was not loaded yet, or the static field was not found, etc. For now assume that the hill climber has
-                // not been used yet.
-                break;
-            }
-            CLRDATA_ADDRESS cdaTpHcInstance = TO_CDADDR(ui64Value);
-
-            // Get the thread pool method table
-            CLRDATA_ADDRESS cdaTpHcMethodTable;
-            {
-                TADDR tpHcMethodTableAddr = NULL;
-                if (FAILED(GetMTOfObject(TO_TADDR(cdaTpHcInstance), &tpHcMethodTableAddr)))
-                {
-                    ExtOut("    %s\n", "Failed to get method table for PortableThreadPool.HillClimbing");
-                    break;
-                }
-                cdaTpHcMethodTable = TO_CDADDR(tpHcMethodTableAddr);
-            }
-
-            offset = GetObjFieldOffset(cdaTpHcInstance, cdaTpHcMethodTable, W("_logStart"));
-            if (offset <= 0 || FAILED(MOVE(i32Value, cdaTpHcInstance + offset)))
-            {
-                ExtOut("    %s\n", "Failed to read PortableThreadPool.HillClimbing._logStart");
-                break;
-            }
-            int logStart = i32Value;
-
-            offset = GetObjFieldOffset(cdaTpHcInstance, cdaTpHcMethodTable, W("_logSize"));
-            if (offset <= 0 || FAILED(MOVE(i32Value, cdaTpHcInstance + offset)))
-            {
-                ExtOut("    %s\n", "Failed to read PortableThreadPool.HillClimbing._logSize");
-                break;
-            }
-            int logSize = i32Value;
-
-            offset = GetObjFieldOffset(cdaTpHcInstance, cdaTpHcMethodTable, W("_log"));
-            if (offset <= 0 || FAILED(MOVE(ptrValue, cdaTpHcInstance + offset)) || ptrValue == 0)
-            {
-                ExtOut("    %s\n", "Failed to read PortableThreadPool.HillClimbing._log");
-                break;
-            }
-            CLRDATA_ADDRESS cdaTpHcLog = TO_CDADDR(ptrValue);
-
-            // Validate the log array
-            if (!sos::IsObject(cdaTpHcLog, false) ||
-                vPortableTpHcLogArray.Request(g_sos, cdaTpHcLog) != S_OK ||
-                vPortableTpHcLogArray.ObjectType != OBJ_ARRAY ||
-                vPortableTpHcLogArray.ArrayDataPtr == 0 ||
-                vPortableTpHcLogArray.dwComponentSize != sizeof(HillClimbingLogEntry) ||
-                vPortableTpHcLogArray.ElementTypeHandle == 0)
-            {
-                ExtOut("    %s\n", "Failed to validate PortableThreadPool.HillClimbing._log");
-                break;
-            }
-
-            // Get the log entry field offsets
-            portableTpHcLogEntry_tickCountOffset =
-                GetValueFieldOffset(vPortableTpHcLogArray.ElementTypeHandle, W("tickCount"));
-            portableTpHcLogEntry_stateOrTransitionOffset =
-                GetValueFieldOffset(vPortableTpHcLogArray.ElementTypeHandle, W("stateOrTransition"));
-            portableTpHcLogEntry_newControlSettingOffset =
-                GetValueFieldOffset(vPortableTpHcLogArray.ElementTypeHandle, W("newControlSetting"));
-            portableTpHcLogEntry_lastHistoryCountOffset =
-                GetValueFieldOffset(vPortableTpHcLogArray.ElementTypeHandle, W("lastHistoryCount"));
-            portableTpHcLogEntry_lastHistoryMeanOffset =
-                GetValueFieldOffset(vPortableTpHcLogArray.ElementTypeHandle, W("lastHistoryMean"));
-            if (portableTpHcLogEntry_tickCountOffset < 0 ||
-                portableTpHcLogEntry_stateOrTransitionOffset < 0 ||
-                portableTpHcLogEntry_newControlSettingOffset < 0 ||
-                portableTpHcLogEntry_lastHistoryCountOffset < 0 ||
-                portableTpHcLogEntry_lastHistoryMeanOffset < 0)
-            {
-                ExtOut("    %s\n", "Failed to get a field offset in PortableThreadPool.HillClimbing.LogEntry");
-                break;
-            }
-
-            ExtOut("logStart: %d\n", logStart);
-            ExtOut("logSize: %d\n", logSize);
-            threadpool.HillClimbingLogFirstIndex = logStart;
-            threadpool.HillClimbingLogSize = logSize;
-        }
-    } while (false);
-
-    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-    ExtOut ("CPU utilization: %d %s\n", threadpool.cpuUtilization, "%");
-    ExtOut ("Worker Thread:");
-    ExtOut (" Total: %d", threadpool.NumWorkingWorkerThreads + threadpool.NumIdleWorkerThreads + threadpool.NumRetiredWorkerThreads);
-    ExtOut (" Running: %d", threadpool.NumWorkingWorkerThreads);
-    ExtOut (" Idle: %d", threadpool.NumIdleWorkerThreads);
-    ExtOut (" MaxLimit: %d", threadpool.MaxLimitTotalWorkerThreads);
-    ExtOut (" MinLimit: %d", threadpool.MinLimitTotalWorkerThreads);
-    ExtOut ("\n");
-
-    int numWorkRequests = 0;
-    CLRDATA_ADDRESS workRequestPtr = threadpool.FirstUnmanagedWorkRequest;
-    DacpWorkRequestData workRequestData;
-    while (workRequestPtr)
-    {
-        if ((Status = workRequestData.Request(g_sos,workRequestPtr))!=S_OK)
-        {
-            ExtOut("    Failed to examine a WorkRequest\n");
-            return Status;
-        }
-        numWorkRequests++;
-        workRequestPtr = workRequestData.NextWorkRequest;
-    }
-
-    ExtOut ("Work Request in Queue: %d\n", numWorkRequests);
-    workRequestPtr = threadpool.FirstUnmanagedWorkRequest;
-    while (workRequestPtr)
-    {
-        if ((Status = workRequestData.Request(g_sos,workRequestPtr))!=S_OK)
-        {
-            ExtOut("    Failed to examine a WorkRequest\n");
-            return Status;
-        }
-
-        if (workRequestData.Function == threadpool.AsyncTimerCallbackCompletionFPtr)
-            ExtOut ("    AsyncTimerCallbackCompletion TimerInfo@%p\n", SOS_PTR(workRequestData.Context));
-        else
-            ExtOut ("    Unknown Function: %p  Context: %p\n", SOS_PTR(workRequestData.Function),
-                SOS_PTR(workRequestData.Context));
-
-        workRequestPtr = workRequestData.NextWorkRequest;
-    }
-
-    if (doWorkItemDump && g_snapshot.Build())
-    {
-        // Display a message if the heap isn't verified.
-        sos::GCHeap gcheap;
-        if (!gcheap.AreGCStructuresValid())
-        {
-            DisplayInvalidStructuresMessage();
-        }
-
-        mdTypeDef threadPoolWorkQueueMd, threadPoolWorkStealingQueueMd;
-        GetInfoFromName(corelibModule, "System.Threading.ThreadPoolWorkQueue", &threadPoolWorkQueueMd);
-        GetInfoFromName(corelibModule, "System.Threading.ThreadPoolWorkQueue+WorkStealingQueue", &threadPoolWorkStealingQueueMd);
-
-        // Walk every heap item looking for the global queue and local queues.
-        ExtOut("\nQueued work items:\n%" THREAD_POOL_WORK_ITEM_TABLE_QUEUE_WIDTH "s %" POINTERSIZE "s %s\n", "Queue", "Address", "Work Item");
-        HeapStat stats;
-        for (sos::ObjectIterator itr = gcheap.WalkHeap(); !IsInterrupt() && itr != NULL; ++itr)
-        {
-            DacpMethodTableData mtdata;
-            if (mtdata.Request(g_sos, TO_TADDR(itr->GetMT())) != S_OK ||
-                mtdata.Module != corelibModule)
-            {
-                continue;
-            }
-
-            if (mtdata.cl == threadPoolWorkQueueMd)
-            {
-                // We found a ThreadPoolWorkQueue (there should be only one, given one AppDomain).
-
-                // Enumerate high-priority work items.
-                int offset = GetObjFieldOffset(itr->GetAddress(), itr->GetMT(), W("highPriorityWorkItems"));
-                if (offset > 0)
-                {
-                    DWORD_PTR workItemsConcurrentQueuePtr;
-                    MOVE(workItemsConcurrentQueuePtr, itr->GetAddress() + offset);
-                    if (sos::IsObject(workItemsConcurrentQueuePtr, false))
-                    {
-                        // We got the ConcurrentQueue.  Enumerate it.
-                        EnumerateThreadPoolGlobalWorkItemConcurrentQueue(workItemsConcurrentQueuePtr, "[Global high-pri]", &stats);
-                    }
-                }
-
-                // Enumerate assignable normal-priority work items.
-                offset = GetObjFieldOffset(itr->GetAddress(), itr->GetMT(), W("_assignableWorkItemQueues"));
-                if (offset > 0)
-                {
-                    DWORD_PTR workItemsConcurrentQueueArrayPtr;
-                    MOVE(workItemsConcurrentQueueArrayPtr, itr->GetAddress() + offset);
-                    DacpObjectData workItemsConcurrentQueueArray;
-                    if (workItemsConcurrentQueueArray.Request(g_sos, TO_CDADDR(workItemsConcurrentQueueArrayPtr)) == S_OK &&
-                        workItemsConcurrentQueueArray.ObjectType == OBJ_ARRAY)
-                    {
-                        for (int i = 0; i < workItemsConcurrentQueueArray.dwNumComponents; i++)
-                        {
-                            DWORD_PTR workItemsConcurrentQueuePtr;
-                            MOVE(workItemsConcurrentQueuePtr, workItemsConcurrentQueueArray.ArrayDataPtr + (i * workItemsConcurrentQueueArray.dwComponentSize));
-                            if (workItemsConcurrentQueuePtr != NULL && sos::IsObject(TO_CDADDR(workItemsConcurrentQueuePtr), false))
-                            {
-                                // We got the ConcurrentQueue.  Enumerate it.
-                                EnumerateThreadPoolGlobalWorkItemConcurrentQueue(workItemsConcurrentQueuePtr, "[Global]", &stats);
-                            }
-                        }
-                    }
-                }
-
-                // Enumerate normal-priority work items.
-                offset = GetObjFieldOffset(itr->GetAddress(), itr->GetMT(), W("workItems"));
-                if (offset > 0)
-                {
-                    DWORD_PTR workItemsConcurrentQueuePtr;
-                    MOVE(workItemsConcurrentQueuePtr, itr->GetAddress() + offset);
-                    if (sos::IsObject(workItemsConcurrentQueuePtr, false))
-                    {
-                        // We got the ConcurrentQueue.  Enumerate it.
-                        EnumerateThreadPoolGlobalWorkItemConcurrentQueue(workItemsConcurrentQueuePtr, "[Global]", &stats);
-                    }
-                }
-            }
-            else if (mtdata.cl == threadPoolWorkStealingQueueMd)
-            {
-                // We found a local queue.  Get its work items array.
-                int offset = GetObjFieldOffset(itr->GetAddress(), itr->GetMT(), W("m_array"));
-                if (offset > 0)
-                {
-                    // Walk every element in the array, outputting details on non-null work items.
-                    DWORD_PTR workItemArrayPtr;
-                    MOVE(workItemArrayPtr, itr->GetAddress() + offset);
-                    DacpObjectData workItemArray;
-                    if (workItemArray.Request(g_sos, TO_CDADDR(workItemArrayPtr)) == S_OK && workItemArray.ObjectType == OBJ_ARRAY)
-                    {
-                        for (int i = 0; i < workItemArray.dwNumComponents; i++)
-                        {
-                            DWORD_PTR workItemPtr;
-                            MOVE(workItemPtr, workItemArray.ArrayDataPtr + (i * workItemArray.dwComponentSize));
-                            if (workItemPtr != NULL && sos::IsObject(TO_CDADDR(workItemPtr), false))
-                            {
-                                sos::Object workItem = TO_TADDR(workItemPtr);
-                                stats.Add((DWORD_PTR)workItem.GetMT(), (DWORD)workItem.GetSize());
-                                DMLOut("%" THREAD_POOL_WORK_ITEM_TABLE_QUEUE_WIDTH "s %s %S", DMLObject(itr->GetAddress()), DMLObject(workItem.GetAddress()), workItem.GetTypeName());
-                                if ((offset = GetObjFieldOffset(workItem.GetAddress(), workItem.GetMT(), W("_callback"))) > 0 ||
-                                    (offset = GetObjFieldOffset(workItem.GetAddress(), workItem.GetMT(), W("m_action"))) > 0)
-                                {
-                                    DWORD_PTR delegatePtr;
-                                    MOVE(delegatePtr, workItem.GetAddress() + offset);
-                                    CLRDATA_ADDRESS md;
-                                    if (TryGetMethodDescriptorForDelegate(TO_CDADDR(delegatePtr), &md))
-                                    {
-                                        NameForMD_s((DWORD_PTR)md, g_mdName, mdNameLen);
-                                        ExtOut(" => %S", g_mdName);
-                                    }
-                                }
-                                ExtOut("\n");
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        // Output a summary.
-        stats.Sort();
-        stats.Print();
-        ExtOut("\n");
-    }
-
-    if (doHCDump)
-    {
-        ExtOut ("--------------------------------------\n");
-        ExtOut ("\nThread Injection History\n");
-        if (threadpool.HillClimbingLogSize > 0)
-        {
-            static char const * const TransitionNames[] =
-            {
-                "Warmup",
-                "Initializing",
-                "RandomMove",
-                "ClimbingMove",
-                "ChangePoint",
-                "Stabilizing",
-                "Starvation",
-                "ThreadTimedOut",
-                "CooperativeBlocking",
-                "Undefined"
-            };
-
-            bool usePortableThreadPoolHillClimbingData = threadpool.HillClimbingLog == 0;
-            int logCapacity =
-                usePortableThreadPoolHillClimbingData
-                    ? (int)vPortableTpHcLogArray.dwNumComponents
-                    : HillClimbingLogCapacity;
-
-            ExtOut("\n    Time Transition     New #Threads      #Samples   Throughput\n");
-            DacpHillClimbingLogEntry entry;
-
-            // Get the most recent entry first, so we can calculate time offsets
-            DWORD endTime;
-            int index = (threadpool.HillClimbingLogFirstIndex + threadpool.HillClimbingLogSize - 1) % logCapacity;
-            if (usePortableThreadPoolHillClimbingData)
-            {
-                CLRDATA_ADDRESS entryPtr =
-                    TO_CDADDR(vPortableTpHcLogArray.ArrayDataPtr + index * sizeof(HillClimbingLogEntry));
-                INT32 i32Value = 0;
-
-                if (FAILED(Status = MOVE(i32Value, entryPtr + portableTpHcLogEntry_tickCountOffset)))
-                {
-                    ExtOut("    Failed to examine a HillClimbing log entry\n");
-                    return Status;
-                }
-
-                endTime = i32Value;
-            }
-            else
-            {
-                CLRDATA_ADDRESS entryPtr = threadpool.HillClimbingLog + (index * sizeof(HillClimbingLogEntry));
-                if ((Status = entry.Request(g_sos, entryPtr)) != S_OK)
-                {
-                    ExtOut("    Failed to examine a HillClimbing log entry\n");
-                    return Status;
-                }
-
-                endTime = entry.TickCount;
-            }
-
-            for (int i = 0; i < threadpool.HillClimbingLogSize; i++)
-            {
-                index = (i + threadpool.HillClimbingLogFirstIndex) % logCapacity;
-                if (usePortableThreadPoolHillClimbingData)
-                {
-                    CLRDATA_ADDRESS entryPtr =
-                        TO_CDADDR(vPortableTpHcLogArray.ArrayDataPtr + (index * sizeof(HillClimbingLogEntry)));
-                    INT32 i32Value = 0;
-                    float f32Value = 0;
-
-                    if (FAILED(Status = MOVE(i32Value, entryPtr + portableTpHcLogEntry_tickCountOffset)))
-                    {
-                        ExtOut("    Failed to examine a HillClimbing log entry\n");
-                        return Status;
-                    }
-                    entry.TickCount = i32Value;
-
-                    if (FAILED(Status = MOVE(i32Value, entryPtr + portableTpHcLogEntry_stateOrTransitionOffset)))
-                    {
-                        ExtOut("    Failed to examine a HillClimbing log entry\n");
-                        return Status;
-                    }
-                    entry.Transition = i32Value;
-
-                    if (FAILED(Status = MOVE(i32Value, entryPtr + portableTpHcLogEntry_newControlSettingOffset)))
-                    {
-                        ExtOut("    Failed to examine a HillClimbing log entry\n");
-                        return Status;
-                    }
-                    entry.NewControlSetting = i32Value;
-
-                    if (FAILED(Status = MOVE(i32Value, entryPtr + portableTpHcLogEntry_lastHistoryCountOffset)))
-                    {
-                        ExtOut("    Failed to examine a HillClimbing log entry\n");
-                        return Status;
-                    }
-                    entry.LastHistoryCount = i32Value;
-
-                    if (FAILED(Status = MOVE(f32Value, entryPtr + portableTpHcLogEntry_lastHistoryMeanOffset)))
-                    {
-                        ExtOut("    Failed to examine a HillClimbing log entry\n");
-                        return Status;
-                    }
-                    entry.LastHistoryMean = f32Value;
-                }
-                else
-                {
-                    CLRDATA_ADDRESS entryPtr = threadpool.HillClimbingLog + (index * sizeof(HillClimbingLogEntry));
-
-                    if ((Status = entry.Request(g_sos, entryPtr)) != S_OK)
-                    {
-                        ExtOut("    Failed to examine a HillClimbing log entry\n");
-                        return Status;
-                    }
-                }
-
-                ExtOut("%8.2lf %-14s %12d  %12d  %11.2lf\n",
-                    (double)(int)(entry.TickCount - endTime) / 1000.0,
-                    TransitionNames[entry.Transition],
-                    entry.NewControlSetting,
-                    entry.LastHistoryCount,
-                    entry.LastHistoryMean);
-            }
-        }
-    }
-
-    ExtOut ("--------------------------------------\n");
-    ExtOut ("Number of Timers: %d\n", threadpool.NumTimers);
-    ExtOut ("--------------------------------------\n");
-
-    // Determine if the portable thread pool is being used for IO. The portable thread pool does not use a separate set of
-    // threads for processing IO completions.
-    if (FAILED(
-            GetNonSharedStaticFieldValueFromName(
-                &ui64Value,
-                corelibModule,
-                "System.Threading.ThreadPool",
-                W("UsePortableThreadPoolForIO"),
-                ELEMENT_TYPE_BOOLEAN)) ||
-        ui64Value == 0)
-    {
-        ExtOut ("Completion Port Thread:");
-        ExtOut ("Total: %d", threadpool.NumCPThreads);
-        ExtOut (" Free: %d", threadpool.NumFreeCPThreads);
-        ExtOut (" MaxFree: %d", threadpool.MaxFreeCPThreads);
-        ExtOut (" CurrentLimit: %d", threadpool.CurrentLimitTotalCPThreads);
-        ExtOut (" MaxLimit: %d", threadpool.MaxLimitTotalCPThreads);
-        ExtOut (" MinLimit: %d", threadpool.MinLimitTotalCPThreads);
-        ExtOut ("\n");
-    }
-
-    return S_OK;
+    return ExecuteCommand("threadpool", args);
 }
 
 DECLARE_API(FindAppDomain)
index db556a16ed54aa6e907502e41df3c4c89640b46a..768c919ca099e72eeda1ab9f45511f2577323841 100644 (file)
@@ -1128,125 +1128,6 @@ void DisplayFields(CLRDATA_ADDRESS cdaMT, DacpMethodTableData *pMTD, DacpMethodT
     return;
 }
 
-HRESULT GetNonSharedStaticFieldValueFromName(
-    UINT64* pValue,
-    DWORD_PTR moduleAddr,
-    const char *typeName,
-    __in_z LPCWSTR wszFieldName,
-    CorElementType fieldType)
-{
-    HRESULT hr = S_OK;
-
-    mdTypeDef mdType = 0;
-    GetInfoFromName(moduleAddr, typeName, &mdType);
-    if (mdType == 0)
-    {
-        return E_FAIL; // Failed to find type token
-    }
-
-    CLRDATA_ADDRESS cdaMethodTable = 0;
-    if (FAILED(hr = g_sos->GetMethodDescFromToken(moduleAddr, mdType, &cdaMethodTable)) ||
-        !IsValidToken(moduleAddr, mdType) ||
-        cdaMethodTable == 0)
-    {
-        return FAILED(hr) ? hr : E_FAIL; // Invalid type token or type is not loaded yet
-    }
-
-    DacpMethodTableData vMethodTable;
-    if ((hr = vMethodTable.Request(g_sos, cdaMethodTable)) != S_OK)
-    {
-        return FAILED(hr) ? hr : E_FAIL; // Failed to get method table data
-    }
-    if (vMethodTable.bIsShared)
-    {
-        ExtOut("    %s: %s\n", "Method table is shared (not implemented)", typeName);
-        return E_NOTIMPL;
-    }
-
-    DacpMethodTableFieldData vMethodTableFields;
-    if (FAILED(hr = vMethodTableFields.Request(g_sos, cdaMethodTable)))
-    {
-        return hr; // Failed to get field data
-    }
-
-    DacpModuleData vModule;
-    if ((hr = vModule.Request(g_sos, vMethodTable.Module)) != S_OK)
-    {
-        return FAILED(hr) ? hr : E_FAIL; // Failed to get module data
-    }
-
-    DacpDomainLocalModuleData vDomainLocalModule;
-    if ((hr = g_sos->GetDomainLocalModuleDataFromModule(vMethodTable.Module, &vDomainLocalModule)) != S_OK)
-    {
-        return FAILED(hr) ? hr : E_FAIL; // Failed to get domain local module data
-    }
-
-    ToRelease<IMetaDataImport> pImport = MDImportForModule(&vModule);
-    CLRDATA_ADDRESS cdaField = vMethodTableFields.FirstField;
-    DacpFieldDescData vFieldDesc;
-    bool found = false;
-    for (DWORD staticFieldIndex = 0; staticFieldIndex < vMethodTableFields.wNumStaticFields; )
-    {
-        if ((hr = vFieldDesc.Request(g_sos, cdaField)) != S_OK || vFieldDesc.Type >= ELEMENT_TYPE_MAX)
-        {
-            return FAILED(hr) ? hr : E_FAIL; // Failed to get member field desc
-        }
-        cdaField = vFieldDesc.NextField;
-
-        if (!vFieldDesc.bIsStatic)
-        {
-            continue;
-        }
-
-        ++staticFieldIndex;
-
-        if (vFieldDesc.Type != fieldType)
-        {
-            continue;
-        }
-
-        if (FAILED(hr = NameForToken_s(TokenFromRid(vFieldDesc.mb, mdtFieldDef), pImport, g_mdName, mdNameLen, false)))
-        {
-            return hr; // Failed to get member field name
-        }
-
-        if (_wcscmp(g_mdName, wszFieldName) != 0)
-        {
-            continue;
-        }
-
-        if (vFieldDesc.bIsThreadLocal || vFieldDesc.bIsContextLocal)
-        {
-            ExtOut("    %s: %s.%S\n", "Static field is thread-local or context-local (not implemented)", typeName, wszFieldName);
-            return E_NOTIMPL;
-        }
-
-        found = true;
-        break;
-    }
-
-    if (!found)
-    {
-        return E_FAIL; // Static field not found
-    }
-
-    DWORD_PTR pValueAddr = 0;
-    GetStaticFieldPTR(&pValueAddr, &vDomainLocalModule, &vMethodTable, &vFieldDesc);
-    if (pValueAddr == 0)
-    {
-        return E_FAIL; // Failed to get static field address
-    }
-
-    UINT64 value = 0;
-    if (FAILED(MOVEBLOCK(value, pValueAddr, gElementTypeInfo[fieldType])))
-    {
-        return E_FAIL; // Failed to read static field
-    }
-
-    *pValue = value;
-    return S_OK;
-}
-
 // Return value: -1 = error,
 //                0 = field not found,
 //              > 0 = offset to field from objAddr
@@ -1330,65 +1211,6 @@ int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, CLRDATA_ADDRESS cdaMT, __in_z LPCW
 }
 
 
-// Return value: -1 = error
-//               -2 = not found
-//             >= 0 = offset to field from cdaValue
-int GetValueFieldOffset(CLRDATA_ADDRESS cdaMT, __in_z LPCWSTR wszFieldName, DacpFieldDescData* pDacpFieldDescData)
-{
-#define EXITPOINT(EXPR) do { if(!(EXPR)) { return -1; } } while (0)
-
-    const int NOT_FOUND = -2;
-    DacpMethodTableData dmtd;
-    DacpMethodTableFieldData vMethodTableFields;
-    DacpFieldDescData vFieldDesc;
-    DacpModuleData module;
-    static DWORD numInstanceFields = 0; // Static due to recursion visiting parents
-    numInstanceFields = 0;
-
-    EXITPOINT(vMethodTableFields.Request(g_sos, cdaMT) == S_OK);
-
-    EXITPOINT(dmtd.Request(g_sos, cdaMT) == S_OK);
-    EXITPOINT(module.Request(g_sos, dmtd.Module) == S_OK);
-    if (dmtd.ParentMethodTable)
-    {
-        DWORD retVal = GetValueFieldOffset(dmtd.ParentMethodTable, wszFieldName, pDacpFieldDescData);
-        if (retVal != (DWORD)NOT_FOUND)
-        {
-            // Return in case of error or success. Fall through for field-not-found.
-            return retVal;
-        }
-    }
-
-    CLRDATA_ADDRESS dwAddr = vMethodTableFields.FirstField;
-    ToRelease<IMetaDataImport> pImport = MDImportForModule(&module);
-
-    while (numInstanceFields < vMethodTableFields.wNumInstanceFields)
-    {
-        EXITPOINT(vFieldDesc.Request(g_sos, dwAddr) == S_OK);
-
-        if (!vFieldDesc.bIsStatic)
-        {
-            NameForToken_s(TokenFromRid(vFieldDesc.mb, mdtFieldDef), pImport, g_mdName, mdNameLen, false);
-            if (_wcscmp(wszFieldName, g_mdName) == 0)
-            {
-                if (pDacpFieldDescData != NULL)
-                {
-                    *pDacpFieldDescData = vFieldDesc;
-                }
-                return vFieldDesc.dwOffset;
-            }
-            numInstanceFields++;
-        }
-
-        dwAddr = vFieldDesc.NextField;
-    }
-
-    // Field name not found...
-    return NOT_FOUND;
-
-#undef EXITPOINT
-}
-
 // Returns an AppDomain address if AssemblyPtr is loaded into that domain only. Otherwise
 // returns NULL
 CLRDATA_ADDRESS IsInOneDomainOnly(CLRDATA_ADDRESS AssemblyPtr)
index 9fd90c7ba0ecc38408943f343393826fbe4b690a..2fb1eb0bea639a9e450f5dbba70eba20c7f80d49 100644 (file)
@@ -1584,7 +1584,6 @@ void IP2MethodDesc (DWORD_PTR IP, DWORD_PTR &methodDesc, JITTypes &jitType,
 const char *ElementTypeName (unsigned type);
 void DisplayFields (CLRDATA_ADDRESS cdaMT, DacpMethodTableData *pMTD, DacpMethodTableFieldData *pMTFD,
                     DWORD_PTR dwStartAddr = 0, BOOL bFirst=TRUE, BOOL bValueClass=FALSE);
-HRESULT GetNonSharedStaticFieldValueFromName(UINT64* pValue, DWORD_PTR moduleAddr, const char *typeName, __in_z LPCWSTR wszFieldName, CorElementType fieldType);
 int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, __in_z LPCWSTR wszFieldName, BOOL bFirst=TRUE);
 int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, CLRDATA_ADDRESS cdaMT, __in_z LPCWSTR wszFieldName, BOOL bFirst=TRUE, DacpFieldDescData* pDacpFieldDescData=NULL);
 int GetValueFieldOffset(CLRDATA_ADDRESS cdaMT, __in_z LPCWSTR wszFieldName, DacpFieldDescData* pDacpFieldDescData=NULL);
index a5dbbbefc53f8811a3868306be2e26c254bfee3d..21857783042a4bf0905481e81b2f0dd74327cd86 100644 (file)
@@ -219,7 +219,7 @@ sosCommandInitialize(lldb::SBDebugger debugger)
     g_services->AddCommand("sosstatus", new sosCommand("SOSStatus"), "Displays the global SOS status.");
     g_services->AddCommand("sosflush", new sosCommand("SOSFlush"), "Resets the internal cached state.");
     g_services->AddCommand("syncblk", new sosCommand("SyncBlk"), "Displays the SyncBlock holder info.");
-    g_services->AddCommand("threadpool", new sosCommand("ThreadPool"), "Displays info about the runtime thread pool.");
+    g_services->AddManagedCommand("threadpool", "Displays info about the runtime thread pool.");
     g_services->AddCommand("threadstate", new sosCommand("ThreadState"), "Pretty prints the meaning of a threads state.");
     g_services->AddCommand("token2ee", new sosCommand("token2ee"), "Displays the MethodTable structure and MethodDesc structure for the specified token and module.");
     g_services->AddManagedCommand("verifyheap", "Checks the GC heap for signs of corruption.");