diff --git a/src/prim/windows/windbg/mimalloc_windbg_arenas.cpp b/src/prim/windows/windbg/mimalloc_windbg_arenas.cpp index ce2aa8fe..9ac5947d 100644 --- a/src/prim/windows/windbg/mimalloc_windbg_arenas.cpp +++ b/src/prim/windows/windbg/mimalloc_windbg_arenas.cpp @@ -43,6 +43,8 @@ extern "C" __declspec(dllexport) HRESULT CALLBACK mi_dump_arenas(PDEBUG_CLIENT c size_t totalCommittedPages = 0; size_t totalAbandonedPages = 0; + std::vector potentialFragmentation; + std::vector highAbandonedPagesRatio; for (size_t i = 0; i < arenaCount; ++i) { ULONG64 arenaAddr = subprocMainAddr + offsetof(mi_subproc_t, arenas) + (i * sizeof(std::atomic)); ULONG64 arenaValueAddr = 0; @@ -83,6 +85,7 @@ extern "C" __declspec(dllexport) HRESULT CALLBACK mi_dump_arenas(PDEBUG_CLIENT c totalCommittedPages += committedPages; // For abandoned pages, iterate over each bin (0 to MI_BIN_COUNT - 1). + size_t abandonedPages = 0; for (int bin = 0; bin < MI_BIN_COUNT; bin++) { ULONG64 itemAdr = reinterpret_cast(arena.pages_abandoned[bin]); mi_bitmap_t abandonedBmp {}; @@ -92,9 +95,21 @@ extern "C" __declspec(dllexport) HRESULT CALLBACK mi_dump_arenas(PDEBUG_CLIENT c return hr; } - size_t abandonedPages = mi_bitmap_count(&abandonedBmp); - totalAbandonedPages += abandonedPages; + abandonedPages = mi_bitmap_count(&abandonedBmp); } + + totalAbandonedPages += abandonedPages; + + // --- Anomaly Detection --- + float pctCommittedSlices = (arena.slice_count > 0) ? (committed * 100.0 / arena.slice_count) : 0.0; + potentialFragmentation.push_back(pctCommittedSlices); + + float abandonedPagesRatio = 0.0; + if (committedPages > 0 && abandonedPages > 0) { + abandonedPagesRatio = (abandonedPages * 100.0) / committedPages; + } + + highAbandonedPagesRatio.push_back(abandonedPagesRatio); } g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\nTotal Arenas: %d\n", arenaCount); @@ -111,6 +126,19 @@ extern "C" __declspec(dllexport) HRESULT CALLBACK mi_dump_arenas(PDEBUG_CLIENT c for (size_t i = 0; i < arenaCount; ++i) { ULONG64 arenaAddr = subprocMainAddr + offsetof(mi_subproc_t, arenas) + (i * sizeof(std::atomic)); PrintLink(arenaAddr, std::format("!mi_dump_arena 0x{:016X}", arenaAddr), std::format("Arena {}", i)); + + if (potentialFragmentation[i] < MinCommittedSlicesPct) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, " Warning: Low committed slices percentage (%.2f%%). Potential fragmentation detected.\n", potentialFragmentation[i]); + } + + if (highAbandonedPagesRatio[i] > MaxAbandonedPagesRatio) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, " Warning: High ratio of abandoned pages (%.2f%%). Possible memory leak or delayed decommitment.\n", + highAbandonedPagesRatio[i]); + } + + if (potentialFragmentation[i] < MinCommittedSlicesPct || highAbandonedPagesRatio[i] > MaxAbandonedPagesRatio) { + g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\n"); + } } g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\n"); @@ -191,6 +219,7 @@ extern "C" __declspec(dllexport) HRESULT CALLBACK mi_dump_arena(PDEBUG_CLIENT cl totalCommittedPages += committedPages; // For abandoned pages, iterate over each bin (0 to MI_BIN_COUNT - 1). + size_t abandonedPages = 0; for (int bin = 0; bin < MI_BIN_COUNT; bin++) { ULONG64 itemAdr = reinterpret_cast(arena.pages_abandoned[bin]); mi_bitmap_t abandonedBmp {}; @@ -200,10 +229,11 @@ extern "C" __declspec(dllexport) HRESULT CALLBACK mi_dump_arena(PDEBUG_CLIENT cl return hr; } - size_t abandonedPages = mi_bitmap_count(&abandonedBmp); - totalAbandonedPages += abandonedPages; + abandonedPages = mi_bitmap_count(&abandonedBmp); } + totalAbandonedPages += abandonedPages; + // Summary that aids in detecting fragmentation or undercommitment. double pctCommitted = (totalSlices > 0) ? (totalCommittedSlices * 100.0 / totalSlices) : 0.0; const auto arenaStats = std::format(" Total Slices: {}\n Committed Slices: {} ({}%)", totalSlices, totalCommittedSlices, pctCommitted); @@ -213,5 +243,22 @@ extern "C" __declspec(dllexport) HRESULT CALLBACK mi_dump_arena(PDEBUG_CLIENT cl g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\nAggregated Page Statistics:\n%s\n", pageStats.c_str()); g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\n"); + // --- Anomaly Detection --- + float pctCommittedSlices = (arena.slice_count > 0) ? (committed * 100.0 / arena.slice_count) : 0.0; + + float abandonedPagesRatio = 0.0; + if (committedPages > 0 && abandonedPages > 0) { + abandonedPagesRatio = (abandonedPages * 100.0) / committedPages; + } + + if (pctCommittedSlices < MinCommittedSlicesPct) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, " Warning: Low committed slices percentage (%.2f%%). Potential fragmentation detected.\n", pctCommittedSlices); + } + + if (abandonedPagesRatio > MaxAbandonedPagesRatio) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, " Warning: High ratio of abandoned pages (%.2f%%). Possible memory leak or delayed decommitment.\n", abandonedPagesRatio); + } + + g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\n"); return S_OK; } diff --git a/src/prim/windows/windbg/mimalloc_windbg_utils.h b/src/prim/windows/windbg/mimalloc_windbg_utils.h index 66d15401..c688d7ce 100644 --- a/src/prim/windows/windbg/mimalloc_windbg_utils.h +++ b/src/prim/windows/windbg/mimalloc_windbg_utils.h @@ -25,6 +25,9 @@ extern IDebugControl4* g_DebugControl; extern IDebugSymbols3* g_DebugSymbols; extern IDebugDataSpaces* g_DataSpaces; +constexpr double MinCommittedSlicesPct = 60.0; // if below threshold, potential fragmentation. +constexpr double MaxAbandonedPagesRatio = 0.4; // If abandoned pages exceed 40% of committed pages. + HRESULT FindMimallocBase(); HRESULT GetSymbolOffset(const char* symbolName, ULONG64& outOffset); HRESULT ReadMemory(const char* symbolName, void* outBuffer, size_t bufferSize);