Refactor statistics printing in oatdump.

Simplify the code so that it is easier to add/remove sections.
(motivated by the non-trivial removal of DexCache arrays)

Test: m dump-oat
Test: m test-art-host-gtest-art_oatdump_tests
Change-Id: I2fe4a3062125d8aa053c1a38eb24a624f95291b0
diff --git a/libartbase/base/stats-inl.h b/libartbase/base/stats-inl.h
index 5fa1372..77a95c9 100644
--- a/libartbase/base/stats-inl.h
+++ b/libartbase/base/stats-inl.h
@@ -34,13 +34,19 @@
                    double unit_size,
                    const char* unit) const {
     double percent = total > 0 ? 100.0 * Value() / total : 0;
+    const size_t name_width = 52 - os.GetIndentation();
+    if (name.length() > name_width) {
+      // Handle very long names by printing them on their own line.
+      os.Stream() << name << " \\\n";
+      name = "";
+    }
     os.Stream()
-        << std::setw(40 - os.GetIndentation()) << std::left << name << std::right << " "
-        << std::setw(8) << Count() << " "
-        << std::setw(12) << std::fixed << std::setprecision(3) << Value() / unit_size << unit
-        << std::setw(8) << std::fixed << std::setprecision(1) << percent << "%\n";
+        << std::setw(name_width) << std::left << name << " "
+        << std::setw(6) << std::right << Count() << " "
+        << std::setw(10) << std::fixed << std::setprecision(3) << Value() / unit_size << unit << " "
+        << std::setw(6) << std::fixed << std::setprecision(1) << percent << "%\n";
 
-    // Sort all children by largest value first, than by name.
+    // Sort all children by largest value first, then by name.
     std::map<std::pair<double, std::string_view>, const Stats&> sorted_children;
     for (const auto& it : Children()) {
       sorted_children.emplace(std::make_pair(-it.second.Value(), it.first), it.second);
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index 7e6a85e..6660bb8 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -1888,6 +1888,7 @@
     os << "\n";
 
     stats_.oat_file_bytes = oat_file->Size();
+    stats_.oat_file_stats.AddBytes(oat_file->Size());
 
     oat_dumper_.reset(new OatDumper(*oat_file, *oat_dumper_options_));
 
@@ -1941,67 +1942,31 @@
     if (file == nullptr) {
       LOG(WARNING) << "Failed to find image in " << image_filename;
     } else {
-      stats_.file_bytes = file->GetLength();
+      size_t file_bytes = file->GetLength();
       // If the image is compressed, adjust to decompressed size.
       size_t uncompressed_size = image_header_.GetImageSize() - sizeof(ImageHeader);
       if (!image_header_.HasCompressedBlock()) {
         DCHECK_EQ(uncompressed_size, data_size) << "Sizes should match for uncompressed image";
       }
-      stats_.file_bytes += uncompressed_size - data_size;
-    }
-    size_t header_bytes = sizeof(ImageHeader);
-    const auto& object_section = image_header_.GetObjectsSection();
-    const auto& field_section = image_header_.GetFieldsSection();
-    const auto& method_section = image_header_.GetMethodsSection();
-    const auto& runtime_method_section = image_header_.GetRuntimeMethodsSection();
-    const auto& intern_section = image_header_.GetInternedStringsSection();
-    const auto& class_table_section = image_header_.GetClassTableSection();
-    const auto& sro_section = image_header_.GetImageStringReferenceOffsetsSection();
-    const auto& metadata_section = image_header_.GetMetadataSection();
-    const auto& bitmap_section = image_header_.GetImageBitmapSection();
-
-    stats_.header_bytes = header_bytes;
-
-    // Objects are kObjectAlignment-aligned.
-    // CHECK_EQ(RoundUp(header_bytes, kObjectAlignment), object_section.Offset());
-    if (object_section.Offset() > header_bytes) {
-      stats_.alignment_bytes += object_section.Offset() - header_bytes;
+      file_bytes += uncompressed_size - data_size;
+      stats_.art_file_stats.AddBytes(file_bytes);
+      stats_.art_file_stats["Header"].AddBytes(sizeof(ImageHeader));
     }
 
-    // Field section is 4-byte aligned.
-    constexpr size_t kFieldSectionAlignment = 4U;
-    uint32_t end_objects = object_section.Offset() + object_section.Size();
-    CHECK_EQ(RoundUp(end_objects, kFieldSectionAlignment), field_section.Offset());
-    stats_.alignment_bytes += field_section.Offset() - end_objects;
+    size_t pointer_size = static_cast<size_t>(image_header_.GetPointerSize());
+    CHECK_ALIGNED(image_header_.GetFieldsSection().Offset(), 4);
+    CHECK_ALIGNED_PARAM(image_header_.GetMethodsSection().Offset(), pointer_size);
+    CHECK_ALIGNED(image_header_.GetInternedStringsSection().Offset(), 8);
+    CHECK_ALIGNED(image_header_.GetImageBitmapSection().Offset(), kPageSize);
 
-    // Method section is 4/8 byte aligned depending on target. Just check for 4-byte alignment.
-    uint32_t end_fields = field_section.Offset() + field_section.Size();
-    CHECK_ALIGNED(method_section.Offset(), 4);
-    stats_.alignment_bytes += method_section.Offset() - end_fields;
+    for (size_t i = 0; i < ImageHeader::ImageSections::kSectionCount; i++) {
+      ImageHeader::ImageSections index = ImageHeader::ImageSections(i);
+      const char* name = ImageHeader::GetImageSectionName(index);
+      stats_.art_file_stats[name].AddBytes(image_header_.GetImageSection(index).Size());
+    }
 
-    // Intern table is 8-byte aligned.
-    uint32_t end_methods = runtime_method_section.Offset() + runtime_method_section.Size();
-    CHECK_EQ(RoundUp(end_methods, 8U), intern_section.Offset());
-    stats_.alignment_bytes += intern_section.Offset() - end_methods;
-
-    // Add space between intern table and class table.
-    uint32_t end_intern = intern_section.Offset() + intern_section.Size();
-    stats_.alignment_bytes += class_table_section.Offset() - end_intern;
-
-    // Add space between end of image data and bitmap. Expect the bitmap to be page-aligned.
-    const size_t bitmap_offset = sizeof(ImageHeader) + data_size;
-    CHECK_ALIGNED(bitmap_section.Offset(), kPageSize);
-    stats_.alignment_bytes += RoundUp(bitmap_offset, kPageSize) - bitmap_offset;
-
-    stats_.bitmap_bytes += bitmap_section.Size();
-    stats_.art_field_bytes += field_section.Size();
-    stats_.art_method_bytes += method_section.Size();
-    stats_.interned_strings_bytes += intern_section.Size();
-    stats_.class_table_bytes += class_table_section.Size();
-    stats_.sro_offset_bytes += sro_section.Size();
-    stats_.metadata_bytes += metadata_section.Size();
-
-    stats_.Dump(os, indent_os);
+    stats_.object_stats.AddBytes(image_header_.GetObjectsSection().Size());
+    stats_.Dump(os);
     os << "\n";
 
     os << std::flush;
@@ -2145,11 +2110,6 @@
       return;
     }
 
-    size_t object_bytes = obj->SizeOf();
-    size_t alignment_bytes = RoundUp(object_bytes, kObjectAlignment) - object_bytes;
-    stats_.object_bytes += object_bytes;
-    stats_.alignment_bytes += alignment_bytes;
-
     std::ostream& os = vios_.Stream();
 
     ObjPtr<mirror::Class> obj_class = obj->GetClass();
@@ -2211,7 +2171,9 @@
       }
     }
     std::string temp;
-    stats_.Update(obj_class->GetDescriptor(&temp), object_bytes);
+    const char* desc = obj_class->GetDescriptor(&temp);
+    desc = stats_.descriptors.emplace(desc).first->c_str();  // Dedup and keep alive.
+    stats_.object_stats[desc].AddBytes(obj->SizeOf());
   }
 
   void DumpMethod(ArtMethod* method, std::ostream& indent_os)
@@ -2224,7 +2186,7 @@
       uint32_t quick_oat_code_size = GetQuickOatCodeSize(method);
       ComputeOatSize(quick_oat_code_begin, &first_occurrence);
       if (first_occurrence) {
-        stats_.native_to_managed_code_bytes += quick_oat_code_size;
+        stats_.oat_file_stats["native_code"].AddBytes(quick_oat_code_size);
       }
       if (quick_oat_code_begin != method->GetEntryPointFromQuickCompiledCodePtrSize(
           image_header_.GetPointerSize())) {
@@ -2275,14 +2237,16 @@
       ComputeOatSize(quick_oat_code_begin, &first_occurrence);
       if (first_occurrence) {
         stats_.managed_code_bytes += quick_oat_code_size;
+        art::Stats& managed_code_stats = stats_.oat_file_stats["managed_code"];
+        managed_code_stats.AddBytes(quick_oat_code_size);
         if (method->IsConstructor()) {
           if (method->IsStatic()) {
-            stats_.class_initializer_code_bytes += quick_oat_code_size;
+            managed_code_stats["class_initializer"].AddBytes(quick_oat_code_size);
           } else if (dex_instruction_bytes > kLargeConstructorDexBytes) {
-            stats_.large_initializer_code_bytes += quick_oat_code_size;
+            managed_code_stats["large_initializer"].AddBytes(quick_oat_code_size);
           }
         } else if (dex_instruction_bytes > kLargeMethodDexBytes) {
-          stats_.large_method_code_bytes += quick_oat_code_size;
+          managed_code_stats["large_method"].AddBytes(quick_oat_code_size);
         }
       }
       stats_.managed_code_bytes_ignoring_deduplication += quick_oat_code_size;
@@ -2319,26 +2283,14 @@
 
  public:
   struct Stats {
+    art::Stats art_file_stats;
+    art::Stats oat_file_stats;
+    art::Stats object_stats;
+    std::set<std::string> descriptors;
+
     size_t oat_file_bytes = 0u;
-    size_t file_bytes = 0u;
-
-    size_t header_bytes = 0u;
-    size_t object_bytes = 0u;
-    size_t art_field_bytes = 0u;
-    size_t art_method_bytes = 0u;
-    size_t interned_strings_bytes = 0u;
-    size_t class_table_bytes = 0u;
-    size_t sro_offset_bytes = 0u;
-    size_t metadata_bytes = 0u;
-    size_t bitmap_bytes = 0u;
-    size_t alignment_bytes = 0u;
-
     size_t managed_code_bytes = 0u;
     size_t managed_code_bytes_ignoring_deduplication = 0u;
-    size_t native_to_managed_code_bytes = 0u;
-    size_t class_initializer_code_bytes = 0u;
-    size_t large_initializer_code_bytes = 0u;
-    size_t large_method_code_bytes = 0u;
 
     size_t vmap_table_bytes = 0u;
 
@@ -2351,36 +2303,10 @@
 
     Stats() {}
 
-    struct SizeAndCount {
-      SizeAndCount(size_t bytes_in, size_t count_in) : bytes(bytes_in), count(count_in) {}
-      size_t bytes;
-      size_t count;
-    };
-    using SizeAndCountTable = SafeMap<std::string, SizeAndCount>;
-    SizeAndCountTable sizes_and_counts;
-
-    void Update(const char* descriptor, size_t object_bytes_in) {
-      SizeAndCountTable::iterator it = sizes_and_counts.find(descriptor);
-      if (it != sizes_and_counts.end()) {
-        it->second.bytes += object_bytes_in;
-        it->second.count += 1;
-      } else {
-        sizes_and_counts.Put(descriptor, SizeAndCount(object_bytes_in, 1));
-      }
-    }
-
     double PercentOfOatBytes(size_t size) {
       return (static_cast<double>(size) / static_cast<double>(oat_file_bytes)) * 100;
     }
 
-    double PercentOfFileBytes(size_t size) {
-      return (static_cast<double>(size) / static_cast<double>(file_bytes)) * 100;
-    }
-
-    double PercentOfObjectBytes(size_t size) {
-      return (static_cast<double>(size) / static_cast<double>(object_bytes)) * 100;
-    }
-
     void ComputeOutliers(size_t total_size, double expansion, ArtMethod* method) {
       method_outlier_size.push_back(total_size);
       method_outlier_expansion.push_back(expansion);
@@ -2491,69 +2417,16 @@
       os << "\n" << std::flush;
     }
 
-    void Dump(std::ostream& os, std::ostream& indent_os)
+    void Dump(std::ostream& os)
         REQUIRES_SHARED(Locks::mutator_lock_) {
-      {
-        os << "art_file_bytes = " << PrettySize(file_bytes) << "\n\n"
-           << "art_file_bytes = header_bytes + object_bytes + alignment_bytes\n";
-        indent_os << StringPrintf("header_bytes           =  %8zd (%2.0f%% of art file bytes)\n"
-                                  "object_bytes           =  %8zd (%2.0f%% of art file bytes)\n"
-                                  "art_field_bytes        =  %8zd (%2.0f%% of art file bytes)\n"
-                                  "art_method_bytes       =  %8zd (%2.0f%% of art file bytes)\n"
-                                  "interned_string_bytes  =  %8zd (%2.0f%% of art file bytes)\n"
-                                  "class_table_bytes      =  %8zd (%2.0f%% of art file bytes)\n"
-                                  "sro_bytes              =  %8zd (%2.0f%% of art file bytes)\n"
-                                  "metadata_bytes         =  %8zd (%2.0f%% of art file bytes)\n"
-                                  "bitmap_bytes           =  %8zd (%2.0f%% of art file bytes)\n"
-                                  "alignment_bytes        =  %8zd (%2.0f%% of art file bytes)\n\n",
-                                  header_bytes, PercentOfFileBytes(header_bytes),
-                                  object_bytes, PercentOfFileBytes(object_bytes),
-                                  art_field_bytes, PercentOfFileBytes(art_field_bytes),
-                                  art_method_bytes, PercentOfFileBytes(art_method_bytes),
-                                  interned_strings_bytes,
-                                  PercentOfFileBytes(interned_strings_bytes),
-                                  class_table_bytes, PercentOfFileBytes(class_table_bytes),
-                                  sro_offset_bytes, PercentOfFileBytes(sro_offset_bytes),
-                                  metadata_bytes, PercentOfFileBytes(metadata_bytes),
-                                  bitmap_bytes, PercentOfFileBytes(bitmap_bytes),
-                                  alignment_bytes, PercentOfFileBytes(alignment_bytes))
-            << std::flush;
-      }
-
-      os << "object_bytes breakdown:\n";
-      size_t object_bytes_total = 0;
-      for (const auto& sizes_and_count : sizes_and_counts) {
-        const std::string& descriptor(sizes_and_count.first);
-        double average = static_cast<double>(sizes_and_count.second.bytes) /
-            static_cast<double>(sizes_and_count.second.count);
-        double percent = PercentOfObjectBytes(sizes_and_count.second.bytes);
-        os << StringPrintf("%32s %8zd bytes %6zd instances "
-                           "(%4.0f bytes/instance) %2.0f%% of object_bytes\n",
-                           descriptor.c_str(), sizes_and_count.second.bytes,
-                           sizes_and_count.second.count, average, percent);
-        object_bytes_total += sizes_and_count.second.bytes;
-      }
+      VariableIndentationOutputStream vios(&os);
+      art_file_stats.DumpSizes(vios, "ArtFile");
       os << "\n" << std::flush;
-      CHECK_EQ(object_bytes, object_bytes_total);
+      object_stats.DumpSizes(vios, "Objects");
+      os << "\n" << std::flush;
+      oat_file_stats.DumpSizes(vios, "OatFile");
+      os << "\n" << std::flush;
 
-      os << StringPrintf("oat_file_bytes               = %8zd\n"
-                         "managed_code_bytes           = %8zd (%2.0f%% of oat file bytes)\n"
-                         "native_to_managed_code_bytes = %8zd (%2.0f%% of oat file bytes)\n\n"
-                         "class_initializer_code_bytes = %8zd (%2.0f%% of oat file bytes)\n"
-                         "large_initializer_code_bytes = %8zd (%2.0f%% of oat file bytes)\n"
-                         "large_method_code_bytes      = %8zd (%2.0f%% of oat file bytes)\n\n",
-                         oat_file_bytes,
-                         managed_code_bytes,
-                         PercentOfOatBytes(managed_code_bytes),
-                         native_to_managed_code_bytes,
-                         PercentOfOatBytes(native_to_managed_code_bytes),
-                         class_initializer_code_bytes,
-                         PercentOfOatBytes(class_initializer_code_bytes),
-                         large_initializer_code_bytes,
-                         PercentOfOatBytes(large_initializer_code_bytes),
-                         large_method_code_bytes,
-                         PercentOfOatBytes(large_method_code_bytes))
-            << "DexFile sizes:\n";
       for (const std::pair<std::string, size_t>& oat_dex_file_size : oat_dex_file_sizes) {
         os << StringPrintf("%s = %zd (%2.0f%% of oat file bytes)\n",
                            oat_dex_file_size.first.c_str(), oat_dex_file_size.second,
diff --git a/runtime/image.cc b/runtime/image.cc
index 56eee3b..61b8a0f 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -200,4 +200,21 @@
   return true;
 }
 
+const char* ImageHeader::GetImageSectionName(ImageSections index) {
+  switch (index) {
+    case kSectionObjects: return "Objects";
+    case kSectionArtFields: return "ArtFields";
+    case kSectionArtMethods: return "ArtMethods";
+    case kSectionRuntimeMethods: return "RuntimeMethods";
+    case kSectionImTables: return "ImTables";
+    case kSectionIMTConflictTables: return "IMTConflictTables";
+    case kSectionInternedStrings: return "InternedStrings";
+    case kSectionClassTable: return "ClassTable";
+    case kSectionStringReferenceOffsets: return "StringReferenceOffsets";
+    case kSectionMetadata: return "Metadata";
+    case kSectionImageBitmap: return "ImageBitmap";
+    case kSectionCount: return nullptr;
+  }
+}
+
 }  // namespace art
diff --git a/runtime/image.h b/runtime/image.h
index c8e5948..c5773ec 100644
--- a/runtime/image.h
+++ b/runtime/image.h
@@ -274,6 +274,8 @@
 
   ArtMethod* GetImageMethod(ImageMethod index) const;
 
+  static const char* GetImageSectionName(ImageSections index);
+
   ImageSection& GetImageSection(ImageSections index) {
     DCHECK_LT(static_cast<size_t>(index), kSectionCount);
     return sections_[index];