[scudo][standalone] Get statistics in a char buffer

Summary:
Following up on D68471, this CL introduces some `getStats` APIs to
gather statistics in char buffers (`ScopedString` really) instead of
printing them out right away. Ultimately `printStats` will just
output the buffer, but that allows us to potentially do some work
on the intermediate buffer, and can be used for a `mallocz` type
of functionality. This allows us to pretty much get rid of all the
`Printf` calls around, but I am keeping the function in for
debugging purposes.

This changes the existing tests to use the new APIs when required.

I will add new tests as suggested in D68471 in another CL.

Reviewers: morehouse, hctim, vitalybuka, eugenis, cferris

Reviewed By: morehouse

Subscribers: delcypher, #sanitizers, llvm-commits

Tags: #llvm, #sanitizers

Differential Revision: https://reviews.llvm.org/D68653

git-svn-id: https://llvm.org/svn/llvm-project/compiler-rt/trunk@374173 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/lib/scudo/standalone/combined.h b/lib/scudo/standalone/combined.h
index b2dc25f..60be1dd 100644
--- a/lib/scudo/standalone/combined.h
+++ b/lib/scudo/standalone/combined.h
@@ -369,12 +369,31 @@
     Primary.enable();
   }
 
-  void printStats() {
+  // The function returns the amount of bytes required to store the statistics,
+  // which might be larger than the amount of bytes provided. Note that the
+  // statistics buffer is not necessarily constant between calls to this
+  // function. This can be called with a null buffer or zero size for buffer
+  // sizing purposes.
+  uptr getStats(char *Buffer, uptr Size) {
+    ScopedString Str(1024);
     disable();
-    Primary.printStats();
-    Secondary.printStats();
-    Quarantine.printStats();
+    const uptr Length = getStats(&Str) + 1;
     enable();
+    if (Length < Size)
+      Size = Length;
+    if (Buffer && Size) {
+      memcpy(Buffer, Str.data(), Size);
+      Buffer[Size - 1] = '\0';
+    }
+    return Length;
+  }
+
+  void printStats() {
+    ScopedString Str(1024);
+    disable();
+    getStats(&Str);
+    enable();
+    Str.output();
   }
 
   void releaseToOS() { Primary.releaseToOS(); }
@@ -563,6 +582,13 @@
       *Size = getSize(Ptr, &Header);
     return P;
   }
+
+  uptr getStats(ScopedString *Str) {
+    Primary.getStats(Str);
+    Secondary.getStats(Str);
+    Quarantine.getStats(Str);
+    return Str->length();
+  }
 };
 
 } // namespace scudo
diff --git a/lib/scudo/standalone/crc32_hw.cpp b/lib/scudo/standalone/crc32_hw.cpp
index f4dae7b..62841ba 100644
--- a/lib/scudo/standalone/crc32_hw.cpp
+++ b/lib/scudo/standalone/crc32_hw.cpp
@@ -1,4 +1,4 @@
-//===-- crc32_hw.h ----------------------------------------------*- C++ -*-===//
+//===-- crc32_hw.cpp --------------------------------------------*- C++ -*-===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
diff --git a/lib/scudo/standalone/primary32.h b/lib/scudo/standalone/primary32.h
index a9fbb70..9123d07 100644
--- a/lib/scudo/standalone/primary32.h
+++ b/lib/scudo/standalone/primary32.h
@@ -143,7 +143,7 @@
       }
   }
 
-  void printStats() {
+  void getStats(ScopedString *Str) {
     // TODO(kostyak): get the RSS per region.
     uptr TotalMapped = 0;
     uptr PoppedBlocks = 0;
@@ -154,11 +154,11 @@
       PoppedBlocks += Sci->Stats.PoppedBlocks;
       PushedBlocks += Sci->Stats.PushedBlocks;
     }
-    Printf("Stats: SizeClassAllocator32: %zuM mapped in %zu allocations; "
-           "remains %zu\n",
-           TotalMapped >> 20, PoppedBlocks, PoppedBlocks - PushedBlocks);
+    Str->append("Stats: SizeClassAllocator32: %zuM mapped in %zu allocations; "
+                "remains %zu\n",
+                TotalMapped >> 20, PoppedBlocks, PoppedBlocks - PushedBlocks);
     for (uptr I = 0; I < NumClasses; I++)
-      printStats(I, 0);
+      getStats(Str, I, 0);
   }
 
   uptr releaseToOS() {
@@ -328,17 +328,17 @@
     return B;
   }
 
-  void printStats(uptr ClassId, uptr Rss) {
+  void getStats(ScopedString *Str, uptr ClassId, uptr Rss) {
     SizeClassInfo *Sci = getSizeClassInfo(ClassId);
     if (Sci->AllocatedUser == 0)
       return;
     const uptr InUse = Sci->Stats.PoppedBlocks - Sci->Stats.PushedBlocks;
     const uptr AvailableChunks = Sci->AllocatedUser / getSizeByClassId(ClassId);
-    Printf("  %02zu (%6zu): mapped: %6zuK popped: %7zu pushed: %7zu inuse: %6zu"
-           " avail: %6zu rss: %6zuK\n",
-           ClassId, getSizeByClassId(ClassId), Sci->AllocatedUser >> 10,
-           Sci->Stats.PoppedBlocks, Sci->Stats.PushedBlocks, InUse,
-           AvailableChunks, Rss >> 10);
+    Str->append("  %02zu (%6zu): mapped: %6zuK popped: %7zu pushed: %7zu "
+                "inuse: %6zu avail: %6zu rss: %6zuK\n",
+                ClassId, getSizeByClassId(ClassId), Sci->AllocatedUser >> 10,
+                Sci->Stats.PoppedBlocks, Sci->Stats.PushedBlocks, InUse,
+                AvailableChunks, Rss >> 10);
   }
 
   NOINLINE uptr releaseToOSMaybe(SizeClassInfo *Sci, uptr ClassId,
diff --git a/lib/scudo/standalone/primary64.h b/lib/scudo/standalone/primary64.h
index f56387b..8f443ea 100644
--- a/lib/scudo/standalone/primary64.h
+++ b/lib/scudo/standalone/primary64.h
@@ -147,7 +147,7 @@
     }
   }
 
-  void printStats() const {
+  void getStats(ScopedString *Str) const {
     // TODO(kostyak): get the RSS per region.
     uptr TotalMapped = 0;
     uptr PoppedBlocks = 0;
@@ -159,12 +159,13 @@
       PoppedBlocks += Region->Stats.PoppedBlocks;
       PushedBlocks += Region->Stats.PushedBlocks;
     }
-    Printf("Stats: Primary64: %zuM mapped (%zuM rss) in %zu allocations; "
-           "remains %zu\n",
-           TotalMapped >> 20, 0, PoppedBlocks, PoppedBlocks - PushedBlocks);
+    Str->append("Stats: SizeClassAllocator64: %zuM mapped (%zuM rss) in %zu "
+                "allocations; remains %zu\n",
+                TotalMapped >> 20, 0, PoppedBlocks,
+                PoppedBlocks - PushedBlocks);
 
     for (uptr I = 0; I < NumClasses; I++)
-      printStats(I, 0);
+      getStats(Str, I, 0);
   }
 
   uptr releaseToOS() {
@@ -269,10 +270,12 @@
       if (UNLIKELY(RegionBase + MappedUser + UserMapSize > RegionSize)) {
         if (!Region->Exhausted) {
           Region->Exhausted = true;
-          printStats();
-          Printf(
+          ScopedString Str(1024);
+          getStats(&Str);
+          Str.append(
               "Scudo OOM: The process has Exhausted %zuM for size class %zu.\n",
               RegionSize >> 20, Size);
+          Str.output();
         }
         return nullptr;
       }
@@ -322,21 +325,21 @@
     return B;
   }
 
-  void printStats(uptr ClassId, uptr Rss) const {
+  void getStats(ScopedString *Str, uptr ClassId, uptr Rss) const {
     RegionInfo *Region = getRegionInfo(ClassId);
     if (Region->MappedUser == 0)
       return;
     const uptr InUse = Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks;
     const uptr TotalChunks = Region->AllocatedUser / getSizeByClassId(ClassId);
-    Printf("%s %02zu (%6zu): mapped: %6zuK popped: %7zu pushed: %7zu inuse: "
-           "%6zu total: %6zu rss: %6zuK releases: %6zu last released: %6zuK "
-           "region: 0x%zx (0x%zx)\n",
-           Region->Exhausted ? "F" : " ", ClassId, getSizeByClassId(ClassId),
-           Region->MappedUser >> 10, Region->Stats.PoppedBlocks,
-           Region->Stats.PushedBlocks, InUse, TotalChunks, Rss >> 10,
-           Region->ReleaseInfo.RangesReleased,
-           Region->ReleaseInfo.LastReleasedBytes >> 10, Region->RegionBeg,
-           getRegionBaseByClassId(ClassId));
+    Str->append("%s %02zu (%6zu): mapped: %6zuK popped: %7zu pushed: %7zu "
+                "inuse: %6zu total: %6zu rss: %6zuK releases: %6zu last "
+                "released: %6zuK region: 0x%zx (0x%zx)\n",
+                Region->Exhausted ? "F" : " ", ClassId,
+                getSizeByClassId(ClassId), Region->MappedUser >> 10,
+                Region->Stats.PoppedBlocks, Region->Stats.PushedBlocks, InUse,
+                TotalChunks, Rss >> 10, Region->ReleaseInfo.RangesReleased,
+                Region->ReleaseInfo.LastReleasedBytes >> 10, Region->RegionBeg,
+                getRegionBaseByClassId(ClassId));
   }
 
   NOINLINE uptr releaseToOSMaybe(RegionInfo *Region, uptr ClassId,
diff --git a/lib/scudo/standalone/quarantine.h b/lib/scudo/standalone/quarantine.h
index 732373a..35fd0bc 100644
--- a/lib/scudo/standalone/quarantine.h
+++ b/lib/scudo/standalone/quarantine.h
@@ -130,7 +130,7 @@
     subFromSize(ExtractedSize);
   }
 
-  void printStats() const {
+  void getStats(ScopedString *Str) const {
     uptr BatchCount = 0;
     uptr TotalOverheadBytes = 0;
     uptr TotalBytes = 0;
@@ -152,11 +152,11 @@
         (TotalQuarantinedBytes == 0)
             ? 0
             : TotalOverheadBytes * 100 / TotalQuarantinedBytes;
-    Printf("Global quarantine stats: batches: %zu; bytes: %zu (user: %zu); "
-           "chunks: %zu (capacity: %zu); %zu%% chunks used; %zu%% memory "
-           "overhead\n",
-           BatchCount, TotalBytes, TotalQuarantinedBytes, TotalQuarantineChunks,
-           QuarantineChunksCapacity, ChunksUsagePercent, MemoryOverheadPercent);
+    Str->append(
+        "Stats: Quarantine: batches: %zu; bytes: %zu (user: %zu); chunks: %zu "
+        "(capacity: %zu); %zu%% chunks used; %zu%% memory overhead\n",
+        BatchCount, TotalBytes, TotalQuarantinedBytes, TotalQuarantineChunks,
+        QuarantineChunksCapacity, ChunksUsagePercent, MemoryOverheadPercent);
   }
 
 private:
@@ -218,11 +218,11 @@
     recycle(0, Cb);
   }
 
-  void printStats() const {
+  void getStats(ScopedString *Str) const {
     // It assumes that the world is stopped, just as the allocator's printStats.
-    Printf("Quarantine limits: global: %zuM; thread local: %zuK\n",
-           getMaxSize() >> 20, getCacheSize() >> 10);
-    Cache.printStats();
+    Cache.getStats(Str);
+    Str->append("Quarantine limits: global: %zuK; thread local: %zuK\n",
+                getMaxSize() >> 10, getCacheSize() >> 10);
   }
 
 private:
diff --git a/lib/scudo/standalone/secondary.cpp b/lib/scudo/standalone/secondary.cpp
index d725034..db7361d 100644
--- a/lib/scudo/standalone/secondary.cpp
+++ b/lib/scudo/standalone/secondary.cpp
@@ -123,12 +123,13 @@
   unmap(Addr, Size, UNMAP_ALL, &Data);
 }
 
-void MapAllocator::printStats() const {
-  Printf("Stats: MapAllocator: allocated %zu times (%zuK), freed %zu times "
-         "(%zuK), remains %zu (%zuK) max %zuM\n",
-         NumberOfAllocs, AllocatedBytes >> 10, NumberOfFrees, FreedBytes >> 10,
-         NumberOfAllocs - NumberOfFrees, (AllocatedBytes - FreedBytes) >> 10,
-         LargestSize >> 20);
+void MapAllocator::getStats(ScopedString *Str) const {
+  Str->append(
+      "Stats: MapAllocator: allocated %zu times (%zuK), freed %zu times "
+      "(%zuK), remains %zu (%zuK) max %zuM\n",
+      NumberOfAllocs, AllocatedBytes >> 10, NumberOfFrees, FreedBytes >> 10,
+      NumberOfAllocs - NumberOfFrees, (AllocatedBytes - FreedBytes) >> 10,
+      LargestSize >> 20);
 }
 
 } // namespace scudo
diff --git a/lib/scudo/standalone/secondary.h b/lib/scudo/standalone/secondary.h
index 80cae9f..9d074a5 100644
--- a/lib/scudo/standalone/secondary.h
+++ b/lib/scudo/standalone/secondary.h
@@ -12,6 +12,7 @@
 #include "common.h"
 #include "mutex.h"
 #include "stats.h"
+#include "string_utils.h"
 
 namespace scudo {
 
@@ -70,7 +71,7 @@
     return getBlockEnd(Ptr) - reinterpret_cast<uptr>(Ptr);
   }
 
-  void printStats() const;
+  void getStats(ScopedString *Str) const;
 
   void disable() { Mutex.lock(); }
 
diff --git a/lib/scudo/standalone/size_class_map.h b/lib/scudo/standalone/size_class_map.h
index 3b494af..dfef086 100644
--- a/lib/scudo/standalone/size_class_map.h
+++ b/lib/scudo/standalone/size_class_map.h
@@ -86,6 +86,7 @@
   }
 
   static void print() {
+    ScopedString Buffer(1024);
     uptr PrevS = 0;
     uptr TotalCached = 0;
     for (uptr I = 0; I < NumClasses; I++) {
@@ -93,19 +94,20 @@
         continue;
       const uptr S = getSizeByClassId(I);
       if (S >= MidSize / 2 && (S & (S - 1)) == 0)
-        Printf("\n");
+        Buffer.append("\n");
       const uptr D = S - PrevS;
       const uptr P = PrevS ? (D * 100 / PrevS) : 0;
       const uptr L = S ? getMostSignificantSetBitIndex(S) : 0;
       const uptr Cached = getMaxCachedHint(S) * S;
-      Printf(
+      Buffer.append(
           "C%02zu => S: %zu diff: +%zu %02zu%% L %zu Cached: %zu %zu; id %zu\n",
           I, getSizeByClassId(I), D, P, L, getMaxCachedHint(S), Cached,
           getClassIdBySize(S));
       TotalCached += Cached;
       PrevS = S;
     }
-    Printf("Total Cached: %zu\n", TotalCached);
+    Buffer.append("Total Cached: %zu\n", TotalCached);
+    Buffer.output();
   }
 
   static void validate() {
diff --git a/lib/scudo/standalone/string_utils.cpp b/lib/scudo/standalone/string_utils.cpp
index 22b4fbd..5de8b57 100644
--- a/lib/scudo/standalone/string_utils.cpp
+++ b/lib/scudo/standalone/string_utils.cpp
@@ -208,9 +208,18 @@
 }
 
 void ScopedString::append(const char *Format, va_list Args) {
-  CHECK_LT(Length, String.size());
-  formatString(String.data() + Length, String.size() - Length, Format, Args);
-  Length += strlen(String.data() + Length);
+  DCHECK_LT(Length, String.size());
+  va_list ArgsCopy;
+  va_copy(ArgsCopy, Args);
+  // formatString doesn't currently support a null buffer or zero buffer length,
+  // so in order to get the resulting formatted string length, we use a one-char
+  // buffer.
+  char C[1];
+  const uptr AdditionalLength =
+      static_cast<uptr>(formatString(C, sizeof(C), Format, Args)) + 1;
+  String.resize(Length + AdditionalLength);
+  formatString(String.data() + Length, AdditionalLength, Format, ArgsCopy);
+  Length = strlen(String.data());
   CHECK_LT(Length, String.size());
 }
 
@@ -226,7 +235,7 @@
 void Printf(const char *Format, ...) {
   va_list Args;
   va_start(Args, Format);
-  ScopedString Msg(512);
+  ScopedString Msg(1024);
   Msg.append(Format, Args);
   outputRaw(Msg.data());
   va_end(Args);
diff --git a/lib/scudo/standalone/string_utils.h b/lib/scudo/standalone/string_utils.h
index aea7b3f..acd60bd 100644
--- a/lib/scudo/standalone/string_utils.h
+++ b/lib/scudo/standalone/string_utils.h
@@ -29,6 +29,7 @@
   }
   void append(const char *Format, va_list Args);
   void append(const char *Format, ...);
+  void output() const { outputRaw(String.data()); }
 
 private:
   Vector<char> String;
diff --git a/lib/scudo/standalone/tests/combined_test.cpp b/lib/scudo/standalone/tests/combined_test.cpp
index 3f971a3..d74c07e 100644
--- a/lib/scudo/standalone/tests/combined_test.cpp
+++ b/lib/scudo/standalone/tests/combined_test.cpp
@@ -136,7 +136,21 @@
   }
 
   Allocator->releaseToOS();
-  Allocator->printStats();
+
+  scudo::uptr BufferSize = 8192;
+  std::vector<char> Buffer(BufferSize);
+  scudo::uptr ActualSize = Allocator->getStats(Buffer.data(), BufferSize);
+  while (ActualSize > BufferSize) {
+    BufferSize = ActualSize + 1024;
+    Buffer.resize(BufferSize);
+    ActualSize = Allocator->getStats(Buffer.data(), BufferSize);
+  }
+  std::string Stats(Buffer.begin(), Buffer.end());
+  // Basic checks on the contents of the statistics output, which also allows us
+  // to verify that we got it all.
+  EXPECT_NE(Stats.find("Stats: SizeClassAllocator"), std::string::npos);
+  EXPECT_NE(Stats.find("Stats: MapAllocator"), std::string::npos);
+  EXPECT_NE(Stats.find("Stats: Quarantine"), std::string::npos);
 }
 
 TEST(ScudoCombinedTest, BasicCombined) {
diff --git a/lib/scudo/standalone/tests/primary_test.cpp b/lib/scudo/standalone/tests/primary_test.cpp
index a6cfc6b..7da7b25 100644
--- a/lib/scudo/standalone/tests/primary_test.cpp
+++ b/lib/scudo/standalone/tests/primary_test.cpp
@@ -46,7 +46,9 @@
   }
   Cache.destroy(nullptr);
   Allocator->releaseToOS();
-  Allocator->printStats();
+  scudo::ScopedString Str(1024);
+  Allocator->getStats(&Str);
+  Str.output();
 }
 
 TEST(ScudoPrimaryTest, BasicPrimary) {
@@ -86,7 +88,9 @@
   }
   Cache.destroy(nullptr);
   Allocator.releaseToOS();
-  Allocator.printStats();
+  scudo::ScopedString Str(1024);
+  Allocator.getStats(&Str);
+  Str.output();
   EXPECT_EQ(AllocationFailed, true);
   Allocator.unmapTestOnly();
 }
@@ -125,7 +129,9 @@
   }
   Cache.destroy(nullptr);
   Allocator->releaseToOS();
-  Allocator->printStats();
+  scudo::ScopedString Str(1024);
+  Allocator->getStats(&Str);
+  Str.output();
 }
 
 TEST(ScudoPrimaryTest, PrimaryIterate) {
@@ -180,7 +186,9 @@
   for (auto &T : Threads)
     T.join();
   Allocator->releaseToOS();
-  Allocator->printStats();
+  scudo::ScopedString Str(1024);
+  Allocator->getStats(&Str);
+  Str.output();
 }
 
 TEST(ScudoPrimaryTest, PrimaryThreaded) {
@@ -203,8 +211,7 @@
   Cache.init(nullptr, Allocator.get());
   const scudo::uptr Size = scudo::getPageSizeCached() * 2;
   EXPECT_TRUE(Primary::canAllocate(Size));
-  const scudo::uptr ClassId =
-      Primary::SizeClassMap::getClassIdBySize(Size);
+  const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size);
   void *P = Cache.allocate(ClassId);
   EXPECT_NE(P, nullptr);
   Cache.deallocate(ClassId, P);
diff --git a/lib/scudo/standalone/tests/quarantine_test.cpp b/lib/scudo/standalone/tests/quarantine_test.cpp
index d7453aa..28baf8f 100644
--- a/lib/scudo/standalone/tests/quarantine_test.cpp
+++ b/lib/scudo/standalone/tests/quarantine_test.cpp
@@ -213,7 +213,9 @@
   Quarantine.drainAndRecycle(&Cache, Cb);
   EXPECT_EQ(Cache.getSize(), 0UL);
 
-  Quarantine.printStats();
+  scudo::ScopedString Str(1024);
+  Quarantine.getStats(&Str);
+  Str.output();
 }
 
 void *populateQuarantine(void *Param) {
@@ -236,5 +238,7 @@
   for (scudo::uptr I = 0; I < NumberOfThreads; I++)
     pthread_join(T[I], 0);
 
-  Quarantine.printStats();
+  scudo::ScopedString Str(1024);
+  Quarantine.getStats(&Str);
+  Str.output();
 }
diff --git a/lib/scudo/standalone/tests/secondary_test.cpp b/lib/scudo/standalone/tests/secondary_test.cpp
index 84c3754..b602b8d 100644
--- a/lib/scudo/standalone/tests/secondary_test.cpp
+++ b/lib/scudo/standalone/tests/secondary_test.cpp
@@ -45,7 +45,9 @@
     L->deallocate(V.back());
     V.pop_back();
   }
-  L->printStats();
+  scudo::ScopedString Str(1024);
+  L->getStats(&Str);
+  Str.output();
 }
 
 // This exercises a variety of combinations of size and alignment for the
@@ -76,7 +78,9 @@
       }
     }
   }
-  L->printStats();
+  scudo::ScopedString Str(1024);
+  L->getStats(&Str);
+  Str.output();
 }
 
 TEST(ScudoSecondaryTest, SecondaryIterate) {
@@ -97,7 +101,9 @@
     L->deallocate(V.back());
     V.pop_back();
   }
-  L->printStats();
+  scudo::ScopedString Str(1024);
+  L->getStats(&Str);
+  Str.output();
 }
 
 static std::mutex Mutex;
@@ -133,5 +139,7 @@
   }
   for (auto &T : Threads)
     T.join();
-  L->printStats();
+  scudo::ScopedString Str(1024);
+  L->getStats(&Str);
+  Str.output();
 }