Generate boot image profile from profman

The CL adds support for boot image profile generation in profman.
It reuses the class BootImageProfile to do so which functionality was
obsolete and no longer working due to updates in the boot image profile
format.

The boot image profile generation accepts a single profile as input and
will output 2 files: the boot image profile and the associated preloaded
classes file.

The generation can be tweaked via command line options by specifying
different thresholds for classes and methods.

Test: gtest
Bug: 152574358
Change-Id: I928cd469cbe8a6c25b414b84aaaf43937e7f688e
diff --git a/profman/boot_image_profile.cc b/profman/boot_image_profile.cc
index db18fb3..8c9d0cd 100644
--- a/profman/boot_image_profile.cc
+++ b/profman/boot_image_profile.cc
@@ -14,11 +14,15 @@
  * limitations under the License.
  */
 
+#include "boot_image_profile.h"
+
 #include <memory>
 #include <set>
 
-#include "boot_image_profile.h"
+#include "android-base/file.h"
+#include "base/unix_file/fd_file.h"
 #include "dex/class_accessor-inl.h"
+#include "dex/descriptors_names.h"
 #include "dex/dex_file-inl.h"
 #include "dex/method_reference.h"
 #include "dex/type_reference.h"
@@ -28,111 +32,228 @@
 
 using Hotness = ProfileCompilationInfo::MethodHotness;
 
-void GenerateBootImageProfile(
+static const std::string kMethodSep = "->";  // NOLINT [runtime/string] [4]
+static const std::string kPackageUseDelim = "@";  // NOLINT [runtime/string] [4]
+static constexpr char kMethodFlagStringHot = 'H';
+static constexpr char kMethodFlagStringStartup = 'S';
+static constexpr char kMethodFlagStringPostStartup = 'P';
+
+// Returns the type descriptor of the given reference.
+static std::string GetTypeDescriptor(const TypeReference& ref) {
+  const dex::TypeId& type_id = ref.dex_file->GetTypeId(ref.TypeIndex());
+  return ref.dex_file->GetTypeDescriptor(type_id);
+}
+
+// Returns the method representation used in the text format of the boot image profile.
+static std::string BootImageRepresentation(const MethodReference& ref) {
+  const DexFile* dex_file = ref.dex_file;
+  const dex::MethodId& id = ref.GetMethodId();
+  std::string signature_string(dex_file->GetMethodSignature(id).ToString());
+  std::string type_string(dex_file->GetTypeDescriptor(dex_file->GetTypeId(id.class_idx_)));
+  std::string method_name(dex_file->GetMethodName(id));
+  return type_string +
+        kMethodSep +
+        method_name +
+        signature_string;
+}
+
+// Returns the class representation used in the text format of the boot image profile.
+static std::string BootImageRepresentation(const TypeReference& ref) {
+  return GetTypeDescriptor(ref);
+}
+
+// Returns the class representation used in preloaded classes.
+static std::string PreloadedClassesRepresentation(const TypeReference& ref) {
+  std::string descriptor = GetTypeDescriptor(ref);
+  return DescriptorToDot(descriptor.c_str());
+}
+
+// Formats the list of packages from the item metadata as a debug string.
+static std::string GetPackageUseString(const FlattenProfileData::ItemMetadata& metadata) {
+  std::string result;
+  for (const auto& it : metadata.GetAnnotations()) {
+    result += it.GetOriginPackageName() + ",";
+  }
+
+  return metadata.GetAnnotations().empty()
+      ? result
+      : result.substr(0, result.size() - 1);
+}
+
+// Converts a method representation to its final profile format.
+static std::string MethodToProfileFormat(
+    const std::string& method,
+    const FlattenProfileData::ItemMetadata& metadata,
+    bool output_package_use) {
+  std::string flags_string;
+  if (metadata.HasFlagSet(Hotness::kFlagHot)) {
+    flags_string += kMethodFlagStringHot;
+  }
+  if (metadata.HasFlagSet(Hotness::kFlagStartup)) {
+    flags_string += kMethodFlagStringStartup;
+  }
+  if (metadata.HasFlagSet(Hotness::kFlagPostStartup)) {
+    flags_string += kMethodFlagStringPostStartup;
+  }
+  std::string extra;
+  if (output_package_use) {
+    extra = kPackageUseDelim + GetPackageUseString(metadata);
+  }
+
+  return flags_string + method + extra;
+}
+
+// Converts a class representation to its final profile or preloaded classes format.
+static std::string ClassToProfileFormat(
+    const std::string& classString,
+    const FlattenProfileData::ItemMetadata& metadata,
+    bool output_package_use) {
+  std::string extra;
+  if (output_package_use) {
+    extra = kPackageUseDelim + GetPackageUseString(metadata);
+  }
+
+  return classString + extra;
+}
+
+// Tries to asses if the given type reference is a clean class.
+static bool MaybeIsClassClean(const TypeReference& ref) {
+  const dex::ClassDef* class_def = ref.dex_file->FindClassDef(ref.TypeIndex());
+  if (class_def == nullptr) {
+    return false;
+  }
+
+  ClassAccessor accessor(*ref.dex_file, *class_def);
+  for (auto& it : accessor.GetStaticFields()) {
+    if (!it.IsFinal()) {
+      // Not final static field will probably dirty the class.
+      return false;
+    }
+  }
+  for (auto& it : accessor.GetMethods()) {
+    uint32_t flags = it.GetAccessFlags();
+    if ((flags & kAccNative) != 0) {
+      // Native method will get dirtied.
+      return false;
+    }
+    if ((flags & kAccConstructor) != 0 && (flags & kAccStatic) != 0) {
+      // Class initializer, may get dirtied (not sure).
+      return false;
+    }
+  }
+
+  return true;
+}
+
+// Returns true iff the item should be included in the profile.
+// (i.e. it passes the given aggregation thresholds)
+static bool IncludeItemInProfile(uint32_t max_aggregation_count,
+                                 uint32_t item_threshold,
+                                 const FlattenProfileData::ItemMetadata& metadata,
+                                 const BootImageOptions& options) {
+  CHECK_NE(max_aggregation_count, 0u);
+  float item_percent = metadata.GetAnnotations().size() / static_cast<float>(max_aggregation_count);
+  for (const auto& annotIt : metadata.GetAnnotations()) {
+    const auto&thresholdIt =
+        options.special_packages_thresholds.find(annotIt.GetOriginPackageName());
+    if (thresholdIt != options.special_packages_thresholds.end()) {
+      if (item_percent >= (thresholdIt->second / 100.f)) {
+        return true;
+      }
+    }
+  }
+  return item_percent >= (item_threshold / 100.f);
+}
+
+// Returns true iff a method with the given metada should be included in the profile.
+static bool IncludeMethodInProfile(uint32_t max_aggregation_count,
+                                   const FlattenProfileData::ItemMetadata& metadata,
+                                   const BootImageOptions& options) {
+  return IncludeItemInProfile(max_aggregation_count, options.method_threshold, metadata, options);
+}
+
+// Returns true iff a class with the given metada should be included in the profile.
+static bool IncludeClassInProfile(const TypeReference& type_ref,
+                                  uint32_t max_aggregation_count,
+                                  const FlattenProfileData::ItemMetadata& metadata,
+                                  const BootImageOptions& options) {
+  uint32_t threshold = MaybeIsClassClean(type_ref)
+      ? options.image_class_clean_threshold
+      : options.image_class_threshold;
+  return IncludeItemInProfile(max_aggregation_count, threshold, metadata, options);
+}
+
+// Returns true iff a class with the given metada should be included in the list of
+// prelaoded classes.
+static bool IncludeInPreloadedClasses(uint32_t max_aggregation_count,
+                                      const FlattenProfileData::ItemMetadata& metadata,
+                                      const BootImageOptions& options) {
+  return IncludeItemInProfile(
+      max_aggregation_count, options.preloaded_class_threshold, metadata, options);
+}
+
+bool GenerateBootImageProfile(
     const std::vector<std::unique_ptr<const DexFile>>& dex_files,
-    const std::vector<std::unique_ptr<const ProfileCompilationInfo>>& profiles,
+    const ProfileCompilationInfo& profile,
     const BootImageOptions& options,
-    bool verbose,
-    ProfileCompilationInfo* out_profile) {
-  for (const std::unique_ptr<const ProfileCompilationInfo>& profile : profiles) {
-    // Avoid merging classes since we may want to only add classes that fit a certain criteria.
-    // If we merged the classes, every single class in each profile would be in the out_profile,
-    // but we want to only included classes that are in at least a few profiles.
-    out_profile->MergeWith(*profile, /*merge_classes=*/ false);
-  }
+    const std::string& boot_profile_out_path,
+    const std::string& preloaded_classes_out_path) {
+  std::unique_ptr<FlattenProfileData> flattenData = profile.ExtractProfileData(dex_files);
 
-  // Image classes that were added because they are commonly used.
-  size_t class_count = 0;
-  // Image classes that were only added because they were clean.
-  size_t clean_class_count = 0;
-  // Total clean classes.
-  size_t clean_count = 0;
-  // Total dirty classes.
-  size_t dirty_count = 0;
+  // We want the output sorted by the method/class name.
+  // So we use an intermediate map for that.
+  // There's no attempt to optimize this as it's not part of any critical path,
+  // and mostly executed on hosts.
+  SafeMap<std::string, FlattenProfileData::ItemMetadata> profile_methods;
+  SafeMap<std::string, FlattenProfileData::ItemMetadata> profile_classes;
+  SafeMap<std::string, FlattenProfileData::ItemMetadata> preloaded_classes;
 
-  for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
-    // Inferred classes are classes inferred from method samples.
-    std::set<std::pair<const ProfileCompilationInfo*, dex::TypeIndex>> inferred_classes;
-    for (size_t i = 0; i < dex_file->NumMethodIds(); ++i) {
-      MethodReference ref(dex_file.get(), i);
-      // This counter is how many profiles contain the method as sampled or hot.
-      size_t counter = 0;
-      for (const std::unique_ptr<const ProfileCompilationInfo>& profile : profiles) {
-        Hotness hotness = profile->GetMethodHotness(ref);
-        if (hotness.IsInProfile()) {
-          ++counter;
-          out_profile->AddMethod(
-              ProfileMethodInfo(ref),
-              static_cast<ProfileCompilationInfo::MethodHotness::Flag>(hotness.GetFlags()));
-          inferred_classes.emplace(profile.get(), ref.GetMethodId().class_idx_);
-        }
+  for (const auto& it : flattenData->GetMethodData()) {
+    if (IncludeMethodInProfile(flattenData->GetMaxAggregationForMethods(), it.second, options)) {
+      FlattenProfileData::ItemMetadata metadata(it.second);
+      if (options.upgrade_startup_to_hot
+          && ((metadata.GetFlags() & Hotness::Flag::kFlagStartup) != 0)) {
+        metadata.AddFlag(Hotness::Flag::kFlagHot);
       }
-      // If the counter is greater or equal to the compile threshold, mark the method as hot.
-      // Note that all hot methods are also marked as hot in the out profile during the merging
-      // process.
-      if (counter >= options.compiled_method_threshold) {
-        Hotness hotness;
-        hotness.AddFlag(Hotness::kFlagHot);
-        out_profile->AddMethod(
-             ProfileMethodInfo(ref),
-             static_cast<ProfileCompilationInfo::MethodHotness::Flag>(hotness.GetFlags()));
-      }
-    }
-    // Walk all of the classes and add them to the profile if they meet the requirements.
-    for (ClassAccessor accessor : dex_file->GetClasses()) {
-      TypeReference ref(dex_file.get(), accessor.GetClassIdx());
-      bool is_clean = true;
-      auto method_visitor = [&](const ClassAccessor::Method& method) {
-        const uint32_t flags = method.GetAccessFlags();
-        if ((flags & kAccNative) != 0) {
-          // Native method will get dirtied.
-          is_clean = false;
-        }
-        if ((flags & kAccConstructor) != 0 && (flags & kAccStatic) != 0) {
-          // Class initializer, may get dirtied (not sure).
-          is_clean = false;
-        }
-      };
-      accessor.VisitFieldsAndMethods(
-          [&](const ClassAccessor::Field& field) {
-            if (!field.IsFinal()) {
-              // Not final static field will probably dirty the class.
-              is_clean = false;
-            }
-          },
-          /*instance_field_visitor=*/ VoidFunctor(),
-          method_visitor,
-          method_visitor);
-
-      ++(is_clean ? clean_count : dirty_count);
-      // This counter is how many profiles contain the class.
-      size_t counter = 0;
-      for (const std::unique_ptr<const ProfileCompilationInfo>& profile : profiles) {
-        auto it = inferred_classes.find(std::make_pair(profile.get(), ref.TypeIndex()));
-        if (it != inferred_classes.end() ||
-            profile->ContainsClass(*ref.dex_file, ref.TypeIndex())) {
-          ++counter;
-        }
-      }
-      if (counter == 0) {
-        continue;
-      }
-      std::set<dex::TypeIndex> classes;
-      if (counter >= options.image_class_theshold) {
-        ++class_count;
-        classes.insert(ref.TypeIndex());
-      } else if (is_clean && counter >= options.image_class_clean_theshold) {
-        ++clean_class_count;
-        classes.insert(ref.TypeIndex());
-      }
-      out_profile->AddClassesForDex(ref.dex_file, classes.begin(), classes.end());
+      profile_methods.Put(BootImageRepresentation(it.first), metadata);
     }
   }
-  if (verbose) {
-    LOG(INFO) << "Image classes " << class_count + clean_class_count
-              << " added because clean " << clean_class_count
-              << " total clean " << clean_count << " total dirty " << dirty_count;
+
+  for (const auto& it : flattenData->GetClassData()) {
+    const TypeReference& type_ref = it.first;
+    const FlattenProfileData::ItemMetadata& metadata = it.second;
+    if (IncludeClassInProfile(type_ref,
+            flattenData->GetMaxAggregationForClasses(),
+            metadata,
+            options)) {
+      profile_classes.Put(BootImageRepresentation(it.first), it.second);
+    }
+    if (IncludeInPreloadedClasses(
+            flattenData->GetMaxAggregationForClasses(),
+            metadata,
+            options)) {
+      preloaded_classes.Put(PreloadedClassesRepresentation(it.first), it.second);
+    }
   }
+
+  // Create the output content
+  std::string profile_content;
+  std::string preloaded_content;
+  for (const auto& it : profile_classes) {
+    profile_content += ClassToProfileFormat(it.first, it.second, options.append_package_use_list)
+        + "\n";
+  }
+  for (const auto& it : profile_methods) {
+    profile_content += MethodToProfileFormat(it.first, it.second, options.append_package_use_list)
+        + "\n";
+  }
+  for (const auto& it : preloaded_classes) {
+    preloaded_content += ClassToProfileFormat(it.first, it.second, options.append_package_use_list)
+        + "\n";
+  }
+
+  return android::base::WriteStringToFile(profile_content, boot_profile_out_path)
+      && android::base::WriteStringToFile(preloaded_content, preloaded_classes_out_path);
 }
 
 }  // namespace art
diff --git a/profman/boot_image_profile.h b/profman/boot_image_profile.h
index 99e5a75..b3b4ba9 100644
--- a/profman/boot_image_profile.h
+++ b/profman/boot_image_profile.h
@@ -21,6 +21,7 @@
 #include <memory>
 #include <vector>
 
+#include "base/safe_map.h"
 #include "dex/dex_file.h"
 
 namespace art {
@@ -29,27 +30,51 @@
 
 struct BootImageOptions {
  public:
-  // Threshold for classes that may be dirty or clean. The threshold specifies how
-  // many different profiles need to have the class before it gets added to the boot profile.
-  uint32_t image_class_theshold = 10;
+  // Threshold for preloaded. The threshold specifies, as percentage
+  // of maximum number or aggregations, how many different profiles need to have the class
+  // before it gets added to the list of preloaded classes.
+  uint32_t preloaded_class_threshold = 10;
 
-  // Threshold for classes that are likely to remain clean. The threshold specifies how
-  // many different profiles need to have the class before it gets added to the boot profile.
-  uint32_t image_class_clean_theshold = 3;
+  // Threshold for classes that may be dirty or clean. The threshold specifies, as percentage
+  // of maximum number or aggregations, how many different profiles need to have the class
+  // before it gets added to the boot profile.
+  uint32_t image_class_threshold = 10;
 
-  // Threshold for non-hot methods to be compiled. The threshold specifies how
-  // many different profiles need to have the method before it gets added to the boot profile.
-  uint32_t compiled_method_threshold = std::numeric_limits<uint32_t>::max();
+  // Threshold for classes that are likely to remain clean. The threshold specifies, as percentage
+  // of maximum number or aggregations, how many different profiles need to have the class
+  // before it gets added to the boot profile.
+  uint32_t image_class_clean_threshold = 5;
+
+  // Threshold for including a method in the profile. The threshold specifies, as percentage
+  // of maximum number or aggregations, how many different profiles need to have the method
+  // before it gets added to the boot profile.
+  uint32_t method_threshold = 10;
+
+  // Whether or not we should upgrade the startup methods to hot.
+  bool upgrade_startup_to_hot = true;
+
+  // A special set of thresholds (classes and methods) that apply if a method/class is being used
+  // by a special package. This can be used to lower the thresholds for methods used by important
+  // packages (e.g. system server of system ui) or packages which have special needs (e.g. camera
+  // needs more hardware methods).
+  SafeMap<std::string, uint32_t> special_packages_thresholds;
+
+  // Whether or not to append package use list to each profile element.
+  // Should be use only for debugging as it will add additional elements to the text output
+  // that are not compatible with the default profile format.
+  bool append_package_use_list = false;
 };
 
-// Merge a bunch of profiles together to generate a boot profile. Classes and methods are added
-// to the out_profile if they meet the options.
-void GenerateBootImageProfile(
+// Generate a boot image profile according to the specified options.
+// Boot classpaths dex files are identified by the given vector and the output is
+// written to the two specified paths. The paths will be open with O_CREAT | O_WRONLY.
+// Returns true if the generation was successful, false otherwise.
+bool GenerateBootImageProfile(
     const std::vector<std::unique_ptr<const DexFile>>& dex_files,
-    const std::vector<std::unique_ptr<const ProfileCompilationInfo>>& profiles,
+    const ProfileCompilationInfo& profile,
     const BootImageOptions& options,
-    bool verbose,
-    ProfileCompilationInfo* out_profile);
+    const std::string& boot_profile_out_path,
+    const std::string& preloaded_classes_out_path);
 
 }  // namespace art
 
diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc
index caaff4b..ffdc8be 100644
--- a/profman/profile_assistant_test.cc
+++ b/profman/profile_assistant_test.cc
@@ -16,6 +16,7 @@
 
 #include <gtest/gtest.h>
 
+#include "android-base/file.h"
 #include "android-base/strings.h"
 #include "art_method-inl.h"
 #include "base/unix_file/fd_file.h"
@@ -213,7 +214,7 @@
     argv_str.push_back(profman_cmd);
     argv_str.push_back("--generate-test-profile=" + filename);
     std::string error;
-    return ExecAndReturnCode(argv_str, &error);
+     return ExecAndReturnCode(argv_str, &error);
   }
 
   bool GenerateTestProfileWithInputDex(const std::string& filename) {
@@ -734,6 +735,11 @@
   EXPECT_GT(method_count, 0u);
 }
 
+static std::string JoinProfileLines(const std::vector<std::string>& lines) {
+  std::string result = android::base::Join(lines, '\n');
+  return result + '\n';
+}
+
 TEST_F(ProfileAssistantTest, TestBootImageProfile) {
   const std::string core_dex = GetLibCoreDexFileNames()[0];
 
@@ -746,102 +752,114 @@
   // 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";
-  // Method that gets marked as hot since it's in multiple profiles.
-  const std::string kMultiMethod = "Ljava/util/ArrayList;->clear()V";
+  // Method that is common and hot. Should end up in profile.
+  const std::string kCommonHotMethod = "Ljava/lang/Comparable;->compareTo(Ljava/lang/Object;)I";
+  // Uncommon method, should not end up in profile
+  const std::string kUncommonMethod = "Ljava/util/HashMap;-><init>()V";
+  // Method that gets marked as hot since it's in multiple profile and marked as startup.
+  const std::string kStartupMethodForUpgrade = "Ljava/util/ArrayList;->clear()V";
+  // Startup method used by a special package which will get a different threshold;
+  const std::string kSpecialPackageStartupMethod =
+      "Ljava/lang/Object;->toString()Ljava/lang/String;";
+  // Method used by a special package which will get a different threshold;
+  const std::string kUncommonSpecialPackageMethod = "Ljava/lang/Object;->hashCode()I";
 
   // Thresholds for this test.
-  static const size_t kDirtyThreshold = 3;
-  static const size_t kCleanThreshold = 2;
-  static const size_t kMethodThreshold = 2;
+  static const size_t kDirtyThreshold = 100;
+  static const size_t kCleanThreshold = 50;
+  static const size_t kPreloadedThreshold = 100;
+  static const size_t kMethodThreshold = 75;
+  static const size_t kSpecialThreshold = 50;
+  const std::string kSpecialPackage = "dex4";
 
-  // 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 boot profile content, attributing the classes and methods to different dex files.
+  std::vector<std::string> input_data = {
+      "{dex1}" + kCleanClass,
+      "{dex1}" + kDirtyClass,
+      "{dex1}" + kUncommonCleanClass,
+      "{dex1}H" + kCommonHotMethod,
+      "{dex1}P" + kStartupMethodForUpgrade,
+      "{dex1}" + kUncommonDirtyClass,
 
-  // Create a bunch of boot profiles.
-  std::string dex2 =
-      kCleanClass + "\n" +
-      kDirtyClass + "\n" +
-      "P" + kHotMethod + "\n" +
-      "P" + kMultiMethod + "\n" +
-      kUncommonDirtyClass;
-  profiles.emplace_back(ScratchFile());
-  EXPECT_TRUE(CreateProfile(
-      dex2, profiles.back().GetFilename(), core_dex));
+      "{dex2}" + kCleanClass,
+      "{dex2}" + kDirtyClass,
+      "{dex2}P" + kCommonHotMethod,
+      "{dex2}P" + kStartupMethodForUpgrade,
+      "{dex2}" + kUncommonDirtyClass,
 
-  // Create a bunch of boot profiles.
-  std::string dex3 =
-      "S" + kHotMethod + "\n" +
-      "P" + kOtherMethod + "\n" +
-      "P" + kMultiMethod + "\n" +
-      kDirtyClass + "\n";
-  profiles.emplace_back(ScratchFile());
-  EXPECT_TRUE(CreateProfile(
-      dex3, profiles.back().GetFilename(), core_dex));
+      "{dex3}P" + kUncommonMethod,
+      "{dex3}PS" + kStartupMethodForUpgrade,
+      "{dex3}S" + kCommonHotMethod,
+      "{dex3}S" + kSpecialPackageStartupMethod,
+      "{dex3}" + kDirtyClass,
+
+      "{dex4}" + kDirtyClass,
+      "{dex4}P" + kCommonHotMethod,
+      "{dex4}S" + kSpecialPackageStartupMethod,
+      "{dex4}P" + kUncommonSpecialPackageMethod
+  };
+  std::string input_file_contents = JoinProfileLines(input_data);
+
+  // Expected data
+  std::vector<std::string> expected_data = {
+      kCleanClass,
+      kDirtyClass,
+      "HSP" + kCommonHotMethod,
+      "HS" + kSpecialPackageStartupMethod,
+      "HSP" + kStartupMethodForUpgrade
+  };
+  std::string expected_profile_content = JoinProfileLines(expected_data);
+
+  std::vector<std::string> expected_preloaded_data = {
+       DescriptorToDot(kDirtyClass.c_str())
+  };
+  std::string expected_preloaded_content = JoinProfileLines(expected_preloaded_data);
+
+  ScratchFile profile;
+  EXPECT_TRUE(CreateProfile(input_file_contents, profile.GetFilename(), core_dex));
+
+  ProfileCompilationInfo bootProfile;
+  bootProfile.Load(profile.GetFilename(), /*for_boot_image*/ true);
 
   // Generate the boot profile.
   ScratchFile out_profile;
+  ScratchFile out_preloaded_classes;
+  ASSERT_TRUE(out_profile.GetFile()->ResetOffset());
+  ASSERT_TRUE(out_preloaded_classes.GetFile()->ResetOffset());
   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("--boot-image-sampled-method-threshold=" + std::to_string(kMethodThreshold));
-  args.push_back("--reference-profile-file=" + out_profile.GetFilename());
+  args.push_back("--class-threshold=" + std::to_string(kDirtyThreshold));
+  args.push_back("--clean-class-threshold=" + std::to_string(kCleanThreshold));
+  args.push_back("--method-threshold=" + std::to_string(kMethodThreshold));
+  args.push_back("--preloaded-class-threshold=" + std::to_string(kPreloadedThreshold));
+  args.push_back(
+      "--special-package=" + kSpecialPackage + ":" + std::to_string(kSpecialThreshold));
+  args.push_back("--profile-file=" + profile.GetFilename());
+  args.push_back("--out-profile-path=" + out_profile.GetFilename());
+  args.push_back("--out-preloaded-classes-path=" + out_preloaded_classes.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_EQ(ExecAndReturnCode(args, &error), 0) << error;
   ASSERT_TRUE(out_profile.GetFile()->ResetOffset());
 
+  // std::vector<std::string> args1({"cp", out_profile.GetFilename(), "~/profile-test"});
+  // EXPECT_EQ(ExecAndReturnCode(args1, &error), 0) << error;
+
   // 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("P" + 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 the sampled methods that became hot.
-  // Other method is in only one profile, it should not become hot.
-  EXPECT_EQ(output_file_contents.find("HP" + kOtherMethod), std::string::npos)
-      << output_file_contents;
-  // Multi method is in at least two profiles, it should become hot.
-  EXPECT_NE(output_file_contents.find("HP" + kMultiMethod), std::string::npos)
-      << output_file_contents;
+  std::string output_profile_contents;
+  ASSERT_TRUE(android::base::ReadFileToString(
+      out_profile.GetFilename(), &output_profile_contents));
+  ASSERT_EQ(output_profile_contents, expected_profile_content);
+
+    // Verify the boot profile contents.
+  std::string output_preloaded_contents;
+  ASSERT_TRUE(android::base::ReadFileToString(
+      out_preloaded_classes.GetFilename(), &output_preloaded_contents));
+  ASSERT_EQ(output_preloaded_contents, expected_preloaded_content);
 }
 
 TEST_F(ProfileAssistantTest, TestProfileCreationOneNotMatched) {
diff --git a/profman/profman.cc b/profman/profman.cc
index cfd1bf6..8d46ad4 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -29,6 +29,7 @@
 #include <unordered_set>
 #include <vector>
 
+#include "android-base/parsebool.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 
@@ -148,15 +149,26 @@
   UsageError("");
   UsageError("  --generate-boot-image-profile: Generate a boot image profile based on input");
   UsageError("      profiles. Requires passing in dex files to inspect properties of classes.");
-  UsageError("  --boot-image-class-threshold=<value>: specify minimum number of class occurrences");
-  UsageError("      to include a class in the boot image profile. Default is 10.");
-  UsageError("  --boot-image-clean-class-threshold=<value>: specify minimum number of clean class");
-  UsageError("      occurrences to include a class in the boot image profile. A clean class is a");
-  UsageError("      class that doesn't have any static fields or native methods and is likely to");
-  UsageError("      remain clean in the image. Default is 3.");
-  UsageError("  --boot-image-sampled-method-threshold=<value>: minimum number of profiles a");
-  UsageError("      non-hot method needs to be in order to be hot in the output profile. The");
-  UsageError("      default is max int.");
+  UsageError("  --method-threshold=percentage between 0 and 100"
+             "      what threshold to apply to the methods when deciding whether or not to"
+             "      include it in the final profile.");
+  UsageError("  --class-threshold=percentage between 0 and 100"
+             "      what threshold to apply to the classes when deciding whether or not to"
+             "      include it in the final profile.");
+  UsageError("  --clean-class-threshold=percentage between 0 and 100"
+             "      what threshold to apply to the clean classes when deciding whether or not to"
+             "      include it in the final profile.");
+  UsageError("  --preloaded-class-threshold=percentage between 0 and 100"
+            "      what threshold to apply to the classes when deciding whether or not to"
+            "       include it in the final preloaded classes.");
+  UsageError("  --upgrade-startup-to-hot=true|false:"
+             "      whether or not to upgrade startup methods to hot");
+  UsageError("  --special-package=pkg_name:percentage between 0 and 100"
+             "      what threshold to apply to the methods/classes that are used by the given"
+             "      package when deciding whether or not to include it in the final profile.");
+  UsageError("  --debug-append-uses=bool: whether or not to append package use as debug info.");
+  UsageError("  --out-profile-path=path: boot image profile output path");
+  UsageError("  --out-preloaded-classes-path=path: preloaded classes output path");
   UsageError("  --copy-and-update-profile-key: if present, profman will copy the profile from");
   UsageError("      the file passed with --profile-fd(file) to the profile passed with");
   UsageError("      --reference-profile-fd(file) and update at the same time the profile-key");
@@ -194,26 +206,25 @@
   LOG(ERROR) << msg;
   exit(1);
 }
-
 template <typename T>
-static void ParseUintOption(const char* raw_option,
-                            std::string_view option_prefix,
-                            T* out) {
-  DCHECK(EndsWith(option_prefix, "="));
-  DCHECK(StartsWith(raw_option, option_prefix)) << raw_option << " " << option_prefix;
-  const char* value_string = raw_option + option_prefix.size();
+static void ParseUintValue(const std::string& option_name,
+                           const std::string& value,
+                           T* out,
+                           T min = std::numeric_limits<T>::min(),
+                           T max = std::numeric_limits<T>::max()) {
   int64_t parsed_integer_value = 0;
-  if (!android::base::ParseInt(value_string, &parsed_integer_value)) {
-    std::string option_name(option_prefix.substr(option_prefix.size() - 1u));
-    Usage("Failed to parse %s '%s' as an integer", option_name.c_str(), value_string);
+  if (!android::base::ParseInt(
+      value,
+      &parsed_integer_value,
+      static_cast<int64_t>(min),
+      static_cast<int64_t>(max))) {
+    Usage("Failed to parse %s '%s' as an integer", option_name.c_str(), value.c_str());
   }
   if (parsed_integer_value < 0) {
-    std::string option_name(option_prefix.substr(option_prefix.size() - 1u));
     Usage("%s passed a negative value %" PRId64, option_name.c_str(), parsed_integer_value);
   }
   if (static_cast<uint64_t>(parsed_integer_value) >
       static_cast<std::make_unsigned_t<T>>(std::numeric_limits<T>::max())) {
-    std::string option_name(option_prefix.substr(option_prefix.size() - 1u));
     Usage("%s passed a value %" PRIu64 " above max (%" PRIu64 ")",
           option_name.c_str(),
           static_cast<uint64_t>(parsed_integer_value),
@@ -222,6 +233,35 @@
   *out = dchecked_integral_cast<T>(parsed_integer_value);
 }
 
+template <typename T>
+static void ParseUintOption(const char* raw_option,
+                            std::string_view option_prefix,
+                            T* out,
+                            T min = std::numeric_limits<T>::min(),
+                            T max = std::numeric_limits<T>::max()) {
+  DCHECK(EndsWith(option_prefix, "="));
+  DCHECK(StartsWith(raw_option, option_prefix)) << raw_option << " " << option_prefix;
+  std::string option_name(option_prefix.substr(option_prefix.size() - 1u));
+  const char* value_string = raw_option + option_prefix.size();
+
+  ParseUintValue(option_name, value_string, out, min, max);
+}
+
+static void ParseBoolOption(const char* raw_option,
+                            std::string_view option_prefix,
+                            bool* out) {
+  DCHECK(EndsWith(option_prefix, "="));
+  DCHECK(StartsWith(raw_option, option_prefix)) << raw_option << " " << option_prefix;
+  const char* value_string = raw_option + option_prefix.size();
+  android::base::ParseBoolResult result = android::base::ParseBool(value_string);
+  if (result == android::base::ParseBoolResult::kError) {
+    std::string option_name(option_prefix.substr(option_prefix.size() - 1u));
+    Usage("Failed to parse %s '%s' as an integer", option_name.c_str(), value_string);
+  }
+
+  *out = result == android::base::ParseBoolResult::kTrue;
+}
+
 // TODO(calin): This class has grown too much from its initial design. Split the functionality
 // into smaller, more contained pieces.
 class ProfMan final {
@@ -279,18 +319,52 @@
         generate_boot_profile_ = true;
       } else if (option == "--generate-boot-image-profile") {
         generate_boot_image_profile_ = true;
-      } else if (StartsWith(option, "--boot-image-class-threshold=")) {
+      } else if (StartsWith(option, "--method-threshold=")) {
         ParseUintOption(raw_option,
-                        "--boot-image-class-threshold=",
-                        &boot_image_options_.image_class_theshold);
-      } else if (StartsWith(option, "--boot-image-clean-class-threshold=")) {
+                        "--method-threshold=",
+                        &boot_image_options_.method_threshold,
+                        0u,
+                        100u);
+      } else if (StartsWith(option, "--class-threshold=")) {
         ParseUintOption(raw_option,
-                        "--boot-image-clean-class-threshold=",
-                        &boot_image_options_.image_class_clean_theshold);
-      } else if (StartsWith(option, "--boot-image-sampled-method-threshold=")) {
+                        "--class-threshold=",
+                        &boot_image_options_.image_class_threshold,
+                        0u,
+                        100u);
+      } else if (StartsWith(option, "--clean-class-threshold=")) {
         ParseUintOption(raw_option,
-                        "--boot-image-sampled-method-threshold=",
-                        &boot_image_options_.compiled_method_threshold);
+                        "--clean-class-threshold=",
+                        &boot_image_options_.image_class_clean_threshold,
+                        0u,
+                        100u);
+      } else if (StartsWith(option, "--preloaded-class-threshold=")) {
+        ParseUintOption(raw_option,
+                        "--preloaded-class-threshold=",
+                        &boot_image_options_.preloaded_class_threshold,
+                        0u,
+                        100u);
+      } else if (StartsWith(option, "--upgrade-startup-to-hot=")) {
+        ParseBoolOption(raw_option,
+                        "--upgrade-startup-to-hot=",
+                        &boot_image_options_.upgrade_startup_to_hot);
+      } else if (StartsWith(option, "--special-package=")) {
+        std::vector<std::string> values;
+        Split(std::string(option.substr(strlen("--special-package="))), ':', &values);
+        if (values.size() != 2) {
+          Usage("--special-package needs to be specified as pkg_name:threshold");
+        }
+        uint32_t threshold;
+        ParseUintValue("special-package", values[1], &threshold, 0u, 100u);
+        boot_image_options_.special_packages_thresholds.Overwrite(values[0], threshold);
+      } else if (StartsWith(option, "--debug-append-uses=")) {
+        ParseBoolOption(raw_option,
+                        "--debug-append-uses=",
+                        &boot_image_options_.append_package_use_list);
+      } else if (StartsWith(option, "--out-profile-path=")) {
+        boot_profile_out_path_ = std::string(option.substr(strlen("--out-profile-path=")));
+      } else if (StartsWith(option, "--out-preloaded-classes-path=")) {
+        preloaded_classes_out_path_ = std::string(
+            option.substr(strlen("--out-preloaded-classes-path=")));
       } else if (StartsWith(option, "--profile-file=")) {
         profile_files_.push_back(std::string(option.substr(strlen("--profile-file="))));
       } else if (StartsWith(option, "--profile-file-fd=")) {
@@ -1259,10 +1333,9 @@
 
   // Create and store a ProfileCompilationInfo for the boot image.
   int CreateBootImageProfile() {
-    // Open the profile output file.
-    const int reference_fd = OpenReferenceProfile();
-    if (!FdIsValid(reference_fd)) {
-      PLOG(ERROR) << "Error opening reference profile";
+    // Open the input profile file.
+    if (profile_files_.size() != 1) {
+      LOG(ERROR) << "A single --profile-file must be specified.";
       return -1;
     }
     // Open the dex files.
@@ -1272,34 +1345,21 @@
       PLOG(ERROR) << "Expected dex files for creating boot profile";
       return -2;
     }
-    // Open the input profiles.
-    std::vector<std::unique_ptr<const ProfileCompilationInfo>> profiles;
-    if (!profile_files_fd_.empty()) {
-      for (int profile_file_fd : profile_files_fd_) {
-        std::unique_ptr<const ProfileCompilationInfo> profile(LoadProfile("", profile_file_fd));
-        if (profile == nullptr) {
-          return -3;
-        }
-        profiles.emplace_back(std::move(profile));
-      }
+
+    ProfileCompilationInfo profile;
+    if (!profile.Load(profile_files_[0], /*clear_if_invalid=*/ false)) {
+      LOG(ERROR) << "Reference profile is not a valid profile.";
+      return -3;
     }
-    if (!profile_files_.empty()) {
-      for (const std::string& profile_file : profile_files_) {
-        std::unique_ptr<const ProfileCompilationInfo> profile(LoadProfile(profile_file, kInvalidFd));
-        if (profile == nullptr) {
-          return -4;
-        }
-        profiles.emplace_back(std::move(profile));
-      }
+
+    if (!GenerateBootImageProfile(dex_files,
+                                  profile,
+                                  boot_image_options_,
+                                  boot_profile_out_path_,
+                                  preloaded_classes_out_path_)) {
+      LOG(ERROR) << "There was an error when generating the boot image profiles";
+      return -4;
     }
-    ProfileCompilationInfo out_profile;
-    GenerateBootImageProfile(dex_files,
-                             profiles,
-                             boot_image_options_,
-                             VLOG_IS_ON(profiler),
-                             &out_profile);
-    out_profile.Save(reference_fd);
-    close(reference_fd);
     return 0;
   }
 
@@ -1453,6 +1513,8 @@
   uint64_t start_ns_;
   bool copy_and_update_profile_key_;
   ProfileAssistant::Options profile_assistant_options_;
+  std::string boot_profile_out_path_;
+  std::string preloaded_classes_out_path_;
 };
 
 // See ProfileAssistant::ProcessingResult for return codes.