[scudo] Track maximum resident memory in cache (#219036) Add tracking for the resident memory in MapAllocatorCache. Unreleased cache entries where Time != 0 are assumed to be resident, while entries that have passed through releaseOlderThan where Time == 0 have had their physical pages discarded. Tracking is performed by updating CurrentResidentBytes on store, remove, and release operations, recoding the peak in maxResidentBytes and reporting it in getStats. GitOrigin-RevId: 5964066e909c5c443bd2f48a88dc54f263a4d47e
diff --git a/lib/scudo/standalone/secondary.h b/lib/scudo/standalone/secondary.h index 16e94f0..5750636 100644 --- a/lib/scudo/standalone/secondary.h +++ b/lib/scudo/standalone/secondary.h
@@ -142,6 +142,9 @@ return true; } + uptr getMaxResidentBytesTestOnly() const { return 0; } + uptr getCurrentResidentBytesTestOnly() const { return 0; } + void getStats(UNUSED ScopedString *Str) { Str->append("Secondary Cache Disabled\n"); } @@ -231,11 +234,12 @@ Str->append( "Stats: MapAllocatorCache: EntriesCount: %zu, " "MaxEntriesCount: %u, MaxEntrySize: %zu, ReleaseToOsSkips: " - "%zu, ReleaseToOsIntervalMs = %d, Unmapped due to eviction: %u, ", + "%zu, ReleaseToOsIntervalMs = %d, Unmapped due to eviction: %u, " + "MaxResidentBytes: %zu, CurrentResidentBytes: %zu\n", LRUEntries.size(), atomic_load_relaxed(&MaxEntriesCount), atomic_load_relaxed(&MaxEntrySize), atomic_load_relaxed(&ReleaseToOsSkips), Interval >= 0 ? Interval : -1, - EvictedCount); + EvictedCount, MaxResidentBytes, CurrentResidentBytes); Str->append("Stats: CacheRetrievalStats: SuccessRate: %u/%u " "(%zu.%02zu%%)\n", SuccessfulRetrieves, CallsToRetrieve, Integral, Fractional); @@ -565,6 +569,16 @@ void enable() NO_THREAD_SAFETY_ANALYSIS { Mutex.unlock(); } + uptr getMaxResidentBytesTestOnly() { + ScopedLock L(Mutex); + return MaxResidentBytes; + } + + uptr getCurrentResidentBytesTestOnly() { + ScopedLock L(Mutex); + return CurrentResidentBytes; + } + void unmapTestOnly() { empty(); } void releaseOlderThanTestOnly(u64 ReleaseTime) { @@ -581,6 +595,11 @@ LRUEntries.push_front(AvailEntry); if (OldestPresentEntry == nullptr && AvailEntry->Time != 0) OldestPresentEntry = AvailEntry; + if (AvailEntry->Time != 0) { + CurrentResidentBytes += Entry.CommitSize; + if (CurrentResidentBytes > MaxResidentBytes) + MaxResidentBytes = CurrentResidentBytes; + } } void remove(CachedBlock *Entry) REQUIRES(Mutex) { @@ -590,6 +609,8 @@ DCHECK(OldestPresentEntry == nullptr || OldestPresentEntry->Time != 0); } LRUEntries.remove(Entry); + if (Entry->Time != 0) + CurrentResidentBytes -= Entry->CommitSize; Entry->invalidate(); AvailEntries.push_front(Entry); } @@ -604,6 +625,7 @@ MapInfo[N++] = Entry.MemMap; LRUEntries.clear(); OldestPresentEntry = nullptr; + CurrentResidentBytes = 0; } for (uptr I = 0; I < N; I++) { MemMapT &MemMap = MapInfo[I]; @@ -638,6 +660,7 @@ Entry->MemMap.releaseAndZeroPagesToOS(Entry->CommitBase, Entry->CommitSize); + CurrentResidentBytes -= Entry->CommitSize; Entry->Time = 0; } OldestPresentEntry = nullptr; @@ -651,6 +674,8 @@ u32 CallsToRetrieve GUARDED_BY(Mutex) = 0; u32 SuccessfulRetrieves GUARDED_BY(Mutex) = 0; u32 EvictedCount GUARDED_BY(Mutex) = 0; + uptr CurrentResidentBytes GUARDED_BY(Mutex) = 0; + uptr MaxResidentBytes GUARDED_BY(Mutex) = 0; atomic_uptr ReleaseToOsSkips = {}; CachedBlock Entries[Config::getEntriesArraySize()] GUARDED_BY(Mutex) = {}; @@ -736,6 +761,14 @@ void unmapTestOnly() { Cache.unmapTestOnly(); } + uptr getMaxResidentBytesTestOnly() { + return Cache.getMaxResidentBytesTestOnly(); + } + + uptr getCurrentResidentBytesTestOnly() { + return Cache.getCurrentResidentBytesTestOnly(); + } + void getStats(ScopedString *Str); private:
diff --git a/lib/scudo/standalone/tests/secondary_test.cpp b/lib/scudo/standalone/tests/secondary_test.cpp index a3cb493..8e9d78c 100644 --- a/lib/scudo/standalone/tests/secondary_test.cpp +++ b/lib/scudo/standalone/tests/secondary_test.cpp
@@ -680,3 +680,47 @@ EXPECT_EQ(*reinterpret_cast<scudo::u32 *>(Info.MemMaps[I].getBase()), 0U); } } + +TEST(ScudoSecondaryTest, AllocatorCacheMaxResidentBytes) { + CacheInfoType<TestCacheConfig> Info; + + Info.Cache->setOption(scudo::Option::ReleaseInterval, -1); + Info.Cache->setOption(scudo::Option::MaxCacheEntriesCount, 10); + Info.Cache->setOption(scudo::Option::MaxCacheEntrySize, 1024 * 1024); + + EXPECT_EQ(Info.Cache->getCurrentResidentBytesTestOnly(), 0U); + EXPECT_EQ(Info.Cache->getMaxResidentBytesTestOnly(), 0U); + + Info.MemMaps.emplace_back(Info.allocate(1024)); + const scudo::uptr Size1 = Info.MemMaps[0].getCapacity(); + EXPECT_NE(0U, Size1); + Info.storeMemMap(Info.MemMaps[0]); + + EXPECT_EQ(Info.Cache->getCurrentResidentBytesTestOnly(), Size1); + EXPECT_EQ(Info.Cache->getMaxResidentBytesTestOnly(), Size1); + + Info.MemMaps.emplace_back(Info.allocate(1024)); + const scudo::uptr Size2 = Info.MemMaps[1].getCapacity(); + EXPECT_NE(0U, Size2); + Info.storeMemMap(Info.MemMaps[1]); + + EXPECT_EQ(Info.Cache->getCurrentResidentBytesTestOnly(), Size1 + Size2); + EXPECT_EQ(Info.Cache->getMaxResidentBytesTestOnly(), Size1 + Size2); + + const scudo::uptr PeakBytes = Size1 + Size2; + + // Releasing pages should drop CurrentResidentBytes to 0, while + // MaxResidentBytes stays at peak + Info.Cache->releaseOlderThanTestOnly(UINT64_MAX); + EXPECT_EQ(Info.Cache->getCurrentResidentBytesTestOnly(), 0U); + EXPECT_EQ(Info.Cache->getMaxResidentBytesTestOnly(), PeakBytes); + + // Store a third map to verify CurrentResidentBytes increases again + Info.MemMaps.emplace_back(Info.allocate(1024)); + const scudo::uptr Size3 = Info.MemMaps[2].getCapacity(); + EXPECT_NE(0U, Size3); + Info.storeMemMap(Info.MemMaps[2]); + + EXPECT_EQ(Info.Cache->getCurrentResidentBytesTestOnly(), Size3); + EXPECT_EQ(Info.Cache->getMaxResidentBytesTestOnly(), PeakBytes); +}