private void DumpCallStacksCollected(ITestHeapStacks obj, HeapCollectStats heapStats, int nSizeSpecial) { foreach (var det in heapStats.details) { int numStacks = 0; IntPtr allocStacks = IntPtr.Zero; obj.GetCollectedAllocStacks(det.AllocSize, ref numStacks, ref allocStacks); for (int nStack = 0; nStack < numStacks; nStack++) { var collectedStack = Marshal.PtrToStructure <CollectedStack>(allocStacks + nStack * Marshal.SizeOf <CollectedStack>()); LogMessage($"Num AllocStacks Collected for size {det.AllocSize}= {numStacks} {collectedStack}"); //The 1st time a stack is encountered, the stack is slightly diff because the code adds an new entry for the stack. // subsequent occurrences of the same stack don't need to add the entry, so they are a different stack. // Release builds have optimized C++ code, so the stacks are the same for (int iFrame = 0; iFrame < collectedStack.numFrames; iFrame++) { var addr = Marshal.ReadIntPtr(collectedStack.pFrameArray + iFrame * IntPtr.Size); LogMessage($" {(uint)addr:x8}"); } Marshal.FreeHGlobal(collectedStack.pFrameArray); } if (!heapStats.fReachedMemLimit && det.AllocSize == nSizeSpecial) { Assert.IsTrue(numStacks > 0, $"Expected > 0 stacks for {det}"); } Marshal.FreeHGlobal(allocStacks); } }
async Task TestCollectStacksHelper( string strStacksToCollect, int nSizeSpecial, int NumFramesToCapture, int HeapAllocSizeMinValue, int StlAllocLimit, int nIter = 10000, int nThreads = 60, bool IsLimited = false, bool WaitForDoCsLife = false) { using (var oInterop = new Interop()) { var obj = GetTestHeapStacks(oInterop); obj.SetHeapCollectParams(strStacksToCollect, NumFramesToCapture, HeapAllocSizeMinValue, StlAllocLimit); obj.StartDetours(out var pDetours); var lstTasks = new List <Task> { Task.Run(() => DoCSLife()) }; if (WaitForDoCsLife) { await lstTasks[0]; } var procHeap = Heap.GetProcessHeap(); var lstIntentionalLeaks = new List <IntPtr>(); for (int iThread = 0; iThread < nThreads; iThread++) { var task = Task.Run(() => { DoSomeNativeAllocs(nIter, iThread, nSizeSpecial, lstIntentionalLeaks); }); lstTasks.Add(task); } await Task.WhenAll(lstTasks); // await Task.Delay(TimeSpan.FromSeconds(1)); // let things settle down before undetouring LogMessage($"Intentional Leaks {lstIntentionalLeaks.Count:n0} TotAllocs={nIter * nThreads:n0}"); obj.StopDetours(pDetours); // await Task.Delay(TimeSpan.FromSeconds(1)); // let things settle down before undetouring var heapStats = new HeapCollectStats(strStacksToCollect); var ptrHeapStats = heapStats.GetPointer(); obj.GetStats(ptrHeapStats); heapStats = HeapCollectStats.FromPointer(ptrHeapStats); LogMessage($"Allocation stacks {heapStats}"); Array.ForEach <HeapCollectStatDetail>(heapStats.details, (detail) => { LogMessage($" {detail}"); }); Assert.IsTrue(heapStats.MyRtlAllocateHeapCount > nIter * nThreads, $"Expected > {nIter * nThreads}, got {heapStats.MyRtlAllocateHeapCount}"); var det = heapStats.details.Where(s => s.AllocSize == nSizeSpecial).Single(); if (IsLimited) { Assert.IsTrue(heapStats.fReachedMemLimit, $"Test says limited memory, but didn't reach limit"); } else { Assert.IsFalse(heapStats.fReachedMemLimit, $"Test says not limited mem, but did reach limit"); } if (!heapStats.fReachedMemLimit) { Assert.IsTrue(det.NumStacksCollected >= nIter, $"Expected numstacks collected ({det.NumStacksCollected}) > {nIter}"); } DumpCallStacksCollected(obj, heapStats, nSizeSpecial); foreach (var addr in lstIntentionalLeaks) { Heap.HeapFree(procHeap, 0, addr); } heapStats.Dispose(); obj.CollectStacksUninitialize(); Marshal.FreeHGlobal(ptrHeapStats); Marshal.ReleaseComObject(obj); // Assert.Fail($"#HeapAlloc={heapStats.MyRtlAllocateHeapCount}"); } }
public void TestCollectStackAddresses() { int nSizeSpecial = 1027; var strStacksToCollect = $"{nSizeSpecial}:0"; using (var oInterop = new Interop()) { var obj = GetTestHeapStacks(oInterop); obj.SetHeapCollectParams(strStacksToCollect, NumFramesToCapture: 20, HeapAllocSizeMinValue: 1048576, StlAllocLimit: 65536 * 10); obj.StartDetours(out var pDetours); int nIter = 100; int nThreads = 1; var lstIntentionalLeaks = new List <IntPtr>(); for (int iThread = 0; iThread < nThreads; iThread++) { DoSomeNativeAllocs(nIter, iThread, nSizeSpecial, lstIntentionalLeaks); } LogMessage($"Intentional Leaks {lstIntentionalLeaks.Count:n0} TotAllocs={nIter * nThreads:n0}"); var heapStats = new HeapCollectStats(strStacksToCollect); obj.StopDetours(pDetours); var ptrHeapStats = heapStats.GetPointer(); obj.GetStats(ptrHeapStats); heapStats = HeapCollectStats.FromPointer(ptrHeapStats); LogMessage($"Allocation stacks {heapStats}"); Array.ForEach <HeapCollectStatDetail>(heapStats.details, (detail) => { LogMessage($" {detail}"); }); Assert.IsTrue(heapStats.MyRtlAllocateHeapCount > nIter * nThreads, $"Expected > {nIter * nThreads}, got {heapStats.MyRtlAllocateHeapCount}"); if (!heapStats.fReachedMemLimit) { var det = heapStats.details.Where(s => s.AllocSize == nSizeSpecial).Single(); Assert.IsTrue(det.NumStacksCollected >= nIter, $"Expected numstacks collected ({det.NumStacksCollected}) > {nIter}"); } { DumpCallStacksCollected(obj, heapStats, nSizeSpecial); } { var lstLiveAllocs = new List <LiveHeapAllocation>(); int numAllocs = 0; IntPtr ptrData = IntPtr.Zero; obj.GetAllocationAddresses(ref numAllocs, ref ptrData); LogMessage($"# LiveAllocStacks (allocs that are still live)= {numAllocs}"); for (int i = 0; i < numAllocs; i++) { var allocdata = Marshal.PtrToStructure <LiveHeapAllocation>(ptrData + i * IntPtr.Size); var str = "Freed"; if (lstIntentionalLeaks.Contains(allocdata.addr)) { str = Marshal.PtrToStringAnsi(allocdata.addr); Assert.IsTrue(str.StartsWith("This is a test string")); } LogMessage($" {i} addr={(uint)(allocdata.addr):x8} stackhash={allocdata.stackhash:x8} str={str}"); } Marshal.FreeHGlobal(ptrData); } foreach (var addr in lstIntentionalLeaks) { Heap.HeapFree(Heap.GetProcessHeap(), 0, addr); } heapStats.Dispose(); obj.CollectStacksUninitialize(); Marshal.FreeHGlobal(ptrHeapStats); Marshal.ReleaseComObject(obj); // Assert.Fail($"#HeapAlloc={heapStats.MyRtlAllocateHeapCount}"); } }