Add support for generating boot image profile

Added three options:
--generate-boot-image-profile:
If this option is passed in, profman creates a boot image profile.

--boot-image-clean-class-threshold=<value>
Specifies how many occurrences of a likely clean class are required
before a class is added to the profile.

--boot-image-class-threshold=<value>
Specify how many occurrences of a possibly dirty class are required
before a class is added to the profile.

Added unit test.

Test: test-art-host

Bug: 37966211

Change-Id: I8e12b0ec34dfa1d1bed0b51f342fffde09815348
diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc
index c6b06af..75f8ec9 100644
--- a/profman/profile_assistant_test.cc
+++ b/profman/profile_assistant_test.cc
@@ -621,6 +621,100 @@
   EXPECT_GT(method_count, 0u);
 }
 
+TEST_F(ProfileAssistantTest, TestBootImageProfile) {
+  const std::string core_dex = GetLibCoreDexFileNames()[0];
+
+  std::vector<ScratchFile> profiles;
+
+  // In image with enough clean occurrences.
+  const std::string kCleanClass = "Ljava/lang/CharSequence;";
+  // In image with enough dirty occurrences.
+  const std::string kDirtyClass = "Ljava/lang/Object;";
+  // Not in image becauseof not enough occurrences.
+  const std::string kUncommonCleanClass = "Ljava/lang/Process;";
+  const std::string kUncommonDirtyClass = "Ljava/lang/Package;";
+  // Method that is hot.
+  // Also adds the class through inference since it is in each dex.
+  const std::string kHotMethod = "Ljava/lang/Comparable;->compareTo(Ljava/lang/Object;)I";
+  // Method that doesn't add the class since its only in one profile. Should still show up in the
+  // boot profile.
+  const std::string kOtherMethod = "Ljava/util/HashMap;-><init>()V";
+
+  // Thresholds for this test.
+  static const size_t kDirtyThreshold = 3;
+  static const size_t kCleanThreshold = 2;
+
+  // Create a bunch of boot profiles.
+  std::string dex1 =
+      kCleanClass + "\n" +
+      kDirtyClass + "\n" +
+      kUncommonCleanClass + "\n" +
+      "H" + kHotMethod + "\n" +
+      kUncommonDirtyClass;
+  profiles.emplace_back(ScratchFile());
+  EXPECT_TRUE(CreateProfile(dex1, profiles.back().GetFilename(), core_dex));
+
+  // Create a bunch of boot profiles.
+  std::string dex2 =
+      kCleanClass + "\n" +
+      kDirtyClass + "\n" +
+      "P" + kHotMethod + "\n" +
+      kUncommonDirtyClass;
+  profiles.emplace_back(ScratchFile());
+  EXPECT_TRUE(CreateProfile(dex2, profiles.back().GetFilename(), core_dex));
+
+  // Create a bunch of boot profiles.
+  std::string dex3 =
+      "S" + kHotMethod + "\n" +
+      "P" + kOtherMethod + "\n" +
+      kDirtyClass + "\n";
+  profiles.emplace_back(ScratchFile());
+  EXPECT_TRUE(CreateProfile(dex3, profiles.back().GetFilename(), core_dex));
+
+  // Generate the boot profile.
+  ScratchFile out_profile;
+  std::vector<std::string> args;
+  args.push_back(GetProfmanCmd());
+  args.push_back("--generate-boot-image-profile");
+  args.push_back("--boot-image-class-threshold=" + std::to_string(kDirtyThreshold));
+  args.push_back("--boot-image-clean-class-threshold=" + std::to_string(kCleanThreshold));
+  args.push_back("--reference-profile-file=" + out_profile.GetFilename());
+  args.push_back("--apk=" + core_dex);
+  args.push_back("--dex-location=" + core_dex);
+  for (const ScratchFile& profile : profiles) {
+    args.push_back("--profile-file=" + profile.GetFilename());
+  }
+  std::string error;
+  EXPECT_EQ(ExecAndReturnCode(args, &error), 0) << error;
+  ASSERT_EQ(0, out_profile.GetFile()->Flush());
+  ASSERT_TRUE(out_profile.GetFile()->ResetOffset());
+
+  // Verify the boot profile contents.
+  std::string output_file_contents;
+  EXPECT_TRUE(DumpClassesAndMethods(out_profile.GetFilename(), &output_file_contents));
+  // Common classes, should be in the classes of the profile.
+  EXPECT_NE(output_file_contents.find(kCleanClass + "\n"), std::string::npos)
+      << output_file_contents;
+  EXPECT_NE(output_file_contents.find(kDirtyClass + "\n"), std::string::npos)
+      << output_file_contents;
+  // Uncommon classes, should not fit preloaded class criteria and should not be in the profile.
+  EXPECT_EQ(output_file_contents.find(kUncommonCleanClass + "\n"), std::string::npos)
+      << output_file_contents;
+  EXPECT_EQ(output_file_contents.find(kUncommonDirtyClass + "\n"), std::string::npos)
+      << output_file_contents;
+  // Inferred class from a method common to all three profiles.
+  EXPECT_NE(output_file_contents.find("Ljava/lang/Comparable;\n"), std::string::npos)
+      << output_file_contents;
+  // Aggregated methods hotness information.
+  EXPECT_NE(output_file_contents.find("HSP" + kHotMethod), std::string::npos)
+      << output_file_contents;
+  EXPECT_NE(output_file_contents.find(kOtherMethod), std::string::npos)
+      << output_file_contents;
+  // Not inferred class, method is only in one profile.
+  EXPECT_EQ(output_file_contents.find("Ljava/util/HashMap;\n"), std::string::npos)
+      << output_file_contents;
+}
+
 TEST_F(ProfileAssistantTest, TestProfileCreationOneNotMatched) {
   // Class names put here need to be in sorted order.
   std::vector<std::string> class_names = {