using TypeReferenceSet = std::set<TypeReference, TypeReferenceValueComparator>;
// TODO(calin): These tests share a lot with the ProfileCompilationInfo tests. // we should introduce a better abstraction to extract the common parts. class ProfileAssistantTest : public CommonRuntimeTest, public ProfileTestHelper { public: void PostRuntimeCreate() override {
allocator_.reset(new ArenaAllocator(Runtime::Current()->GetArenaPool()));
protected: void SetupProfile(const DexFile* dex_file1, const DexFile* dex_file2,
uint16_t number_of_methods,
uint16_t number_of_classes, const ScratchFile& profile,
ProfileCompilationInfo* info,
uint16_t start_method_index = 0, bool reverse_dex_write_order = false) { for (uint16_t i = start_method_index; i < start_method_index + number_of_methods; i++) { // reverse_dex_write_order controls the order in which the dex files will be added to // the profile and thus written to disk.
std::vector<ProfileInlineCache> inline_caches =
GetTestInlineCaches(dex_file1, dex_file2, dex3);
Hotness::Flag flags = static_cast<Hotness::Flag>(Hotness::kFlagHot | Hotness::kFlagPostStartup); if (reverse_dex_write_order) {
ASSERT_TRUE(AddMethod(info, dex_file2, i, inline_caches, flags));
ASSERT_TRUE(AddMethod(info, dex_file1, i, inline_caches, flags));
} else {
ASSERT_TRUE(AddMethod(info, dex_file1, i, inline_caches, flags));
ASSERT_TRUE(AddMethod(info, dex_file2, i, inline_caches, flags));
}
} for (uint16_t i = 0; i < number_of_classes; i++) {
ASSERT_TRUE(AddClass(info, dex_file1, dex::TypeIndex(i)));
}
// Find the first dex-pc in the given method after 'start_pc' (if given) which // contains a call to any method of 'klass'. If 'start_pc' is not given we // will search from the first dex-pc.
uint16_t GetDexPcOfCallTo(ArtMethod* method,
Handle<mirror::Class> klass,
std::optional<uint32_t> start_pc = std::nullopt)
REQUIRES_SHARED(Locks::mutator_lock_) { const DexFile* dex_file = method->GetDexFile(); for (const DexInstructionPcPair& inst :
CodeItemInstructionAccessor(*dex_file, method->GetCodeItem())) { if (start_pc && inst.DexPc() <= *start_pc) { continue;
} elseif (inst->IsInvoke()) { const dex::MethodId& method_id = dex_file->GetMethodId(inst->VRegB());
std::string_view desc(
dex_file->GetTypeDescriptor(dex_file->GetTypeId(method_id.class_idx_)));
std::string scratch; if (desc == klass->GetDescriptor(&scratch)) { return inst.DexPc();
}
}
}
EXPECT_TRUE(false) << "Unable to find dex-pc in " << method->PrettyMethod() << " for call to "
<< klass->PrettyClass()
<< " after dexpc: " << (start_pc ? static_cast<int64_t>(*start_pc) : -1); return -1;
}
// We should advise compilation.
ASSERT_EQ(ProfmanResult::kCompile, ProcessProfiles(profile_fds, reference_profile_fd)); // The resulting compilation info must be equal to the merge of the inputs.
ProfileCompilationInfo result;
ASSERT_TRUE(result.Load(reference_profile_fd));
// We should advise compilation.
ASSERT_EQ(ProfmanResult::kCompile, ProcessProfiles(profile_fds, reference_profile_fd)); // The resulting compilation info must be equal to the merge of the inputs.
ProfileCompilationInfo result;
ASSERT_TRUE(result.Load(reference_profile_fd));
std::vector<int> profile_fds({
GetFd(profile1),
GetFd(profile2)}); int reference_profile_fd = GetFd(reference_profile);
// The new profile info will contain the methods with indices 0-100. const uint16_t kNumberOfMethodsToEnableCompilation = 100;
ProfileCompilationInfo info1;
SetupProfile(dex1, dex2, kNumberOfMethodsToEnableCompilation, 0, profile1, &info1);
ProfileCompilationInfo info2;
SetupProfile(dex3, dex4, kNumberOfMethodsToEnableCompilation, 0, profile2, &info2);
// The reference profile info will contain the methods with indices 50-150. const uint16_t kNumberOfMethodsAlreadyCompiled = 100;
ProfileCompilationInfo reference_info;
SetupProfile(dex1, dex2, kNumberOfMethodsAlreadyCompiled, 0, reference_profile,
&reference_info, kNumberOfMethodsToEnableCompilation / 2);
// We should advise compilation.
ASSERT_EQ(ProfmanResult::kCompile, ProcessProfiles(profile_fds, reference_profile_fd));
// The resulting compilation info must be equal to the merge of the inputs
ProfileCompilationInfo result;
ASSERT_TRUE(result.Load(reference_profile_fd));
// We should not advise compilation.
ASSERT_EQ(ProfmanResult::kSkipCompilationEmptyProfiles,
ProcessProfiles(profile_fds, reference_profile_fd));
// The information from profiles must remain the same.
ProfileCompilationInfo file_info1;
ASSERT_TRUE(file_info1.Load(GetFd(profile1)));
ASSERT_TRUE(file_info1.Equals(info1));
// We should not advise compilation.
ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
ProcessProfiles(profile_fds, reference_profile_fd));
// The information from profiles must remain the same.
ProfileCompilationInfo file_info1;
ASSERT_TRUE(file_info1.Load(GetFd(profile1)));
ASSERT_TRUE(file_info1.Equals(info1));
// We should not advise compilation.
ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
CheckCompilationMethodPercentChange(
kNumberOfMethodsInCurProfile, kNumberOfMethodsInRefProfile, extra_args));
}
// We should not advise compilation.
ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
CheckCompilationClassPercentChange(
kNumberOfClassesInCurProfile, kNumberOfClassesInRefProfile, extra_args));
}
std::vector<int> profile_fds({
GetFd(profile1),
GetFd(profile2)}); int reference_profile_fd = GetFd(reference_profile);
const uint16_t kNumberOfMethodsToEnableCompilation = 100; // Assign different hashes for the same dex file. This will make merging of information to fail.
ProfileCompilationInfo info1;
SetupProfile(dex1, dex2, kNumberOfMethodsToEnableCompilation, 0, profile1, &info1);
ProfileCompilationInfo info2;
SetupProfile(
dex1_checksum_missmatch, dex2, kNumberOfMethodsToEnableCompilation, 0, profile2, &info2);
// We should fail processing.
ASSERT_EQ(ProfmanResult::kErrorBadProfiles, ProcessProfiles(profile_fds, reference_profile_fd));
// The information from profiles must remain the same.
CheckProfileInfo(profile1, info1);
CheckProfileInfo(profile2, info2);
// Reference profile files must still remain empty.
ASSERT_EQ(0, reference_profile.GetFile()->GetLength());
}
std::vector<int> profile_fds({
GetFd(profile1)}); int reference_profile_fd = GetFd(reference_profile);
const uint16_t kNumberOfMethodsToEnableCompilation = 100; // Assign different hashes for the same dex file. This will make merging of information to fail.
ProfileCompilationInfo info1;
SetupProfile(dex1, dex2, kNumberOfMethodsToEnableCompilation, 0, profile1, &info1);
ProfileCompilationInfo reference_info;
SetupProfile(dex1_checksum_missmatch,
dex2,
kNumberOfMethodsToEnableCompilation, 0,
reference_profile,
&reference_info);
// We should not advise compilation.
ASSERT_EQ(ProfmanResult::kErrorBadProfiles, ProcessProfiles(profile_fds, reference_profile_fd));
// The information from profiles must remain the same.
CheckProfileInfo(profile1, info1);
}
TEST_F(ProfileAssistantTest, TestProfileGeneration) {
ScratchFile profile; // Generate a test profile.
GenerateTestProfile(profile.GetFilename());
// Verify that the generated profile is valid and can be loaded.
ProfileCompilationInfo info;
ASSERT_TRUE(info.Load(GetFd(profile)));
}
TEST_F(ProfileAssistantTest, TestProfileGenerationWithIndexDex) {
ScratchFile profile; // Generate a test profile passing in a dex file as reference.
GenerateTestProfileWithInputDex(profile.GetFilename());
// Verify that the generated profile is valid and can be loaded.
ProfileCompilationInfo info;
ASSERT_TRUE(info.Load(GetFd(profile)));
}
TEST_F(ProfileAssistantTest, TestProfileCreationAllMatch) { // Class names put here need to be in sorted order.
std::vector<std::string> class_names = { "HLjava/lang/Object;-><init>()V", "Ljava/lang/Comparable;", "Ljava/lang/Math;", "Ljava/lang/Object;", "SPLjava/lang/Comparable;->compareTo(Ljava/lang/Object;)I", "[[[[[[[[I", // No `TypeId`s in core-oj with this many array dimensions, "[[[[[[[[Ljava/lang/Object;", // "extra descriptors" shall be used for these array classes.
};
std::string file_contents; for (std::string& class_name : class_names) {
file_contents += class_name + std::string("\n");
}
std::string output_file_contents;
ASSERT_TRUE(CreateAndDump(file_contents, &output_file_contents));
ASSERT_EQ(output_file_contents, file_contents);
}
// 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 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"; // Denylisted class const std::string kPreloadedDenylistedClass = "Ljava/lang/Thread;";
TEST_F(ProfileAssistantTest, TestProfileCreationOneNotMatched) { // Class names put here need to be in sorted order.
std::vector<std::string> class_names = { "Ldoesnt/match/this/one;", "Ljava/lang/Comparable;", "Ljava/lang/Object;"
};
std::string input_file_contents; for (std::string& class_name : class_names) {
input_file_contents += class_name + std::string("\n");
}
std::string output_file_contents;
ASSERT_TRUE(CreateAndDump(input_file_contents, &output_file_contents));
std::string expected_contents =
class_names[1] + std::string("\n") +
class_names[2] + std::string("\n");
ASSERT_EQ(output_file_contents, expected_contents);
}
TEST_F(ProfileAssistantTest, TestProfileCreationNoneMatched) { // Class names put here need to be in sorted order.
std::vector<std::string> class_names = { "Ldoesnt/match/this/one;", "Ldoesnt/match/this/one/either;", "Lnor/this/one;"
};
std::string input_file_contents; for (std::string& class_name : class_names) {
input_file_contents += class_name + std::string("\n");
}
std::string output_file_contents;
ASSERT_TRUE(CreateAndDump(input_file_contents, &output_file_contents));
std::string expected_contents("");
ASSERT_EQ(output_file_contents, expected_contents);
}
// Test that we can dump profiles in a way they can be re-constituted. // Test goes 'txt -> prof -> txt -> prof' and then compares the two profs.
TEST_F(ProfileAssistantTest, TestProfileRoundTrip) { // Create the profile content.
std::vector<std::string_view> methods = { "HLTestInline;->inlineMonomorphic(LSuper;)I+LSubA;", "HLTestInline;->inlinePolymorphic(LSuper;)I+LSubA;,LSubB;,LSubC;", "HLTestInline;->inlineMegamorphic(LSuper;)I+LSubA;,LSubB;,LSubC;,LSubD;,LSubE;", "HLTestInline;->inlineMissingTypes(LSuper;)I+missing_types", "HLTestInline;->noInlineCache(LSuper;)I", "HLTestInline;->inlineMultiMonomorphic(LSuper;LSecret;)I+]LSuper;LSubA;]LSecret;LSubB;", "HLTestInline;->inlineMultiPolymorphic(LSuper;LSecret;)I+]LSuper;LSubA;,LSubB;,LSubC;]LSecret;LSubB;,LSubC;", "HLTestInline;->inlineMultiMegamorphic(LSuper;LSecret;)I+]LSuper;LSubA;,LSubB;,LSubC;,LSubD;,LSubE;]LSecret;megamorphic_types", "HLTestInline;->inlineMultiMissingTypes(LSuper;LSecret;)I+]LSuper;missing_types]LSecret;missing_types", "HLTestInline;->inlineTriplePolymorphic(LSuper;LSecret;LSecret;)I+]LSuper;LSubA;,LSubB;,LSubC;]LSecret;LSubB;,LSubC;", "HLTestInline;->noInlineCacheMulti(LSuper;LSecret;)I",
};
std::ostringstream input_file_contents; for (const std::string_view& m : methods) {
input_file_contents << m << "\n";
}
// Create the profile and save it to disk.
ScratchFile profile_file;
ASSERT_TRUE(CreateProfile(input_file_contents.str(),
profile_file.GetFilename(),
GetTestDexFileName("ProfileTestMultiDex")));
// Dump the file back into text.
std::string text_two;
ASSERT_TRUE(DumpClassesAndMethods(
profile_file.GetFilename(), &text_two, GetTestDexFileName("ProfileTestMultiDex")));
// Create another profile and save it to the disk as well.
ScratchFile profile_two;
ASSERT_TRUE(CreateProfile(
text_two, profile_two.GetFilename(), GetTestDexFileName("ProfileTestMultiDex")));
// These two profiles should be bit-identical. // TODO We could compare the 'text_two' to the methods but since the order is // arbitrary for many parts and there are multiple 'correct' dumps we'd need // to basically parse everything and this is simply easier.
std::string error;
std::vector<std::string> args { kIsTargetBuild ? "/system/bin/cmp" : "/usr/bin/cmp", "-s",
profile_file.GetFilename(),
profile_two.GetFilename() };
ASSERT_EQ(ExecAndReturnCode(args, &error), 0) << error << " from " << text_two;
}
// Test that we can dump profiles in a way they can be re-constituted and // annotations don't interfere. Test goes 'txt -> ProfileWithAnnotations -> txt // -> prof' and then compares that to one that is 'txt -> // prof_without_annotations'.
TEST_F(ProfileAssistantTest, TestProfileRoundTripWithAnnotations) { // Create the profile content.
std::vector<std::string_view> methods = { "HLTestInline;->inlineMonomorphic(LSuper;)I+LSubA;", "HLTestInline;->inlinePolymorphic(LSuper;)I+LSubA;,LSubB;,LSubC;", "HLTestInline;->inlineMegamorphic(LSuper;)I+LSubA;,LSubB;,LSubC;,LSubD;,LSubE;", "HLTestInline;->inlineMissingTypes(LSuper;)I+missing_types", "HLTestInline;->noInlineCache(LSuper;)I", "HLTestInline;->inlineMultiMonomorphic(LSuper;LSecret;)I+]LSuper;LSubA;]LSecret;LSubB;", "HLTestInline;->inlineMultiPolymorphic(LSuper;LSecret;)I+]LSuper;LSubA;,LSubB;,LSubC;]LSecret;LSubB;,LSubC;", "HLTestInline;->inlineMultiMegamorphic(LSuper;LSecret;)I+]LSuper;LSubA;,LSubB;,LSubC;,LSubD;,LSubE;]LSecret;megamorphic_types", "HLTestInline;->inlineMultiMissingTypes(LSuper;LSecret;)I+]LSuper;missing_types]LSecret;missing_types", "HLTestInline;->inlineTriplePolymorphic(LSuper;LSecret;LSecret;)I+]LSuper;LSubA;,LSubB;,LSubC;]LSecret;LSubB;,LSubC;", "HLTestInline;->noInlineCacheMulti(LSuper;LSecret;)I",
};
std::ostringstream no_annotation_input_file_contents;
std::ostringstream with_annotation_input_file_contents; for (const std::string_view& m : methods) {
no_annotation_input_file_contents << m << "\n";
with_annotation_input_file_contents << "{foobar}" << m << "\n";
}
// Create the profile and save it to disk.
ScratchFile with_annotation_profile_file;
ASSERT_TRUE(CreateProfile(with_annotation_input_file_contents.str(),
with_annotation_profile_file.GetFilename(),
GetTestDexFileName("ProfileTestMultiDex")));
// Dump the file back into text.
std::string text_two;
ASSERT_TRUE(DumpClassesAndMethods(with_annotation_profile_file.GetFilename(),
&text_two,
GetTestDexFileName("ProfileTestMultiDex")));
// Create another profile and save it to the disk as well.
ScratchFile profile_two;
ASSERT_TRUE(CreateProfile(
text_two, profile_two.GetFilename(), GetTestDexFileName("ProfileTestMultiDex")));
// These two profiles should be bit-identical. // TODO We could compare the 'text_two' to the methods but since the order is // arbitrary for many parts and there are multiple 'correct' dumps we'd need // to basically parse everything and this is simply easier.
std::string error;
std::vector<std::string> args { kIsTargetBuild ? "/system/bin/cmp" : "/usr/bin/cmp", "-s",
no_annotation_profile_file.GetFilename(),
profile_two.GetFilename() };
ASSERT_EQ(ExecAndReturnCode(args, &error), 0) << error << " from " << text_two;
}
TEST_F(ProfileAssistantTest, TestProfileCreateInlineCache) { // Create the profile content.
std::vector<std::string_view> methods = { "HLTestInline;->inlineMonomorphic(LSuper;)I+LSubA;", "HLTestInline;->inlinePolymorphic(LSuper;)I+LSubA;,LSubB;,LSubC;", "HLTestInline;->inlineMegamorphic(LSuper;)I+LSubA;,LSubB;,LSubC;,LSubD;,LSubE;", "HLTestInline;->inlineMissingTypes(LSuper;)I+missing_types", "HLTestInline;->noInlineCache(LSuper;)I", "HLTestInline;->inlineMultiMonomorphic(LSuper;LSecret;)I+]LSuper;LSubA;]LSecret;LSubB;", "HLTestInline;->inlineMultiPolymorphic(LSuper;LSecret;)I+]LSuper;LSubA;,LSubB;,LSubC;]LSecret;LSubB;,LSubC;", "HLTestInline;->inlineMultiMegamorphic(LSuper;LSecret;)I+]LSuper;LSubA;,LSubB;,LSubC;,LSubD;,LSubE;]LSecret;LSubA;,LSubB;,LSubC;,LSubD;,LSubE;", "HLTestInline;->inlineMultiMissingTypes(LSuper;LSecret;)I+]LSuper;missing_types]LSecret;missing_types", "HLTestInline;->inlineTriplePolymorphic(LSuper;LSecret;LSecret;)I+]LSuper;LSubA;,LSubB;,LSubC;]LSecret;LSubB;,LSubC;", "HLTestInline;->noInlineCacheMulti(LSuper;LSecret;)I",
};
std::ostringstream input_file_contents; for (const std::string_view& m : methods) {
input_file_contents << m << "\n";
}
// Create the profile and save it to disk.
ScratchFile profile_file;
ASSERT_TRUE(CreateProfile(input_file_contents.str(),
profile_file.GetFilename(),
GetTestDexFileName("ProfileTestMultiDex")));
// Load the profile from disk.
ProfileCompilationInfo info;
ASSERT_TRUE(info.Load(GetFd(profile_file)));
// Load the dex files and verify that the profile contains the expected methods info.
ScopedObjectAccess soa(Thread::Current());
jobject class_loader = LoadDex("ProfileTestMultiDex");
ASSERT_NE(class_loader, nullptr);
std::vector<int> profile_fds({GetFd(profile1)}); int reference_profile_fd = GetFd(reference_profile);
// The new profile info will contain the methods with indices 0-100. const uint16_t kNumberOfMethodsToEnableCompilation = 100;
ProfileCompilationInfo info1;
SetupProfile(dex1, dex2, kNumberOfMethodsToEnableCompilation, 0, profile1, &info1, /*start_method_index=*/0, /*reverse_dex_write_order=*/false);
// The reference profile info will contain the methods with indices 50-150. // When setting up the profile reverse the order in which the dex files // are added to the profile. This will verify that profman merges profiles // with a different dex order correctly. const uint16_t kNumberOfMethodsAlreadyCompiled = 100;
ProfileCompilationInfo reference_info;
SetupProfile(dex1, dex2, kNumberOfMethodsAlreadyCompiled, 0, reference_profile,
&reference_info, kNumberOfMethodsToEnableCompilation / 2, /*reverse_dex_write_order=*/true);
// We should advise compilation.
ASSERT_EQ(ProfmanResult::kCompile, ProcessProfiles(profile_fds, reference_profile_fd));
// The resulting compilation info must be equal to the merge of the inputs.
ProfileCompilationInfo result;
ASSERT_TRUE(result.Load(reference_profile_fd));
// The information from profile must remain the same.
CheckProfileInfo(profile1, info1);
}
TEST_F(ProfileAssistantTest, TestProfileCreateWithSubtype) { // Create the profile content.
std::vector<std::string> profile_methods = { "HLTestInlineSubtype;->inlineMonomorphic(LSuper;)I+]LSuper;LSubA;",
};
std::string input_file_contents; for (std::string& m : profile_methods) {
input_file_contents += m + std::string("\n");
}
// Create the profile and save it to disk.
ScratchFile profile_file;
std::string dex_filename = GetTestDexFileName("ProfileTestMultiDex");
ASSERT_TRUE(CreateProfile(input_file_contents, profile_file.GetFilename(), dex_filename));
// Load the profile from disk.
ProfileCompilationInfo info;
ASSERT_TRUE(info.Load(GetFd(profile_file)));
LOG(ERROR) << profile_file.GetFilename();
// Load the dex files and verify that the profile contains the expected // methods info.
ScopedObjectAccess soa(Thread::Current());
jobject class_loader = LoadDex("ProfileTestMultiDex");
ASSERT_NE(class_loader, nullptr);
// NB This is the supertype of the declared line!
ArtMethod* inline_monomorphic_super =
GetVirtualMethod(class_loader, "LTestInline;", "inlineMonomorphic"); const DexFile* dex_file = inline_monomorphic_super->GetDexFile();
// Verify that the inline cache is present in the superclass
ProfileCompilationInfo::MethodHotness hotness_super = info.GetMethodHotness(
MethodReference(dex_file, inline_monomorphic_super->GetDexMethodIndex()));
ASSERT_TRUE(hotness_super.IsHot()); const ProfileCompilationInfo::InlineCacheMap* inline_caches = hotness_super.GetInlineCacheMap();
ASSERT_EQ(inline_caches->size(), 1u); const ProfileCompilationInfo::DexPcData& dex_pc_data = inline_caches->begin()->second;
dex::TypeIndex target_type_index(dex_file->GetIndexForTypeId(*dex_file->FindTypeId("LSubA;")));
ASSERT_EQ(1u, dex_pc_data.classes.size());
ASSERT_EQ(target_type_index, *dex_pc_data.classes.begin());
// Verify that the method is present in subclass but there are no // inline-caches (since there is no code). const dex::MethodId& super_method_id =
dex_file->GetMethodId(inline_monomorphic_super->GetDexMethodIndex());
uint32_t sub_method_index = dex_file->GetIndexForMethodId(
*dex_file->FindMethodId(*dex_file->FindTypeId("LTestInlineSubtype;"),
dex_file->GetStringId(super_method_id.name_idx_),
dex_file->GetProtoId(super_method_id.proto_idx_)));
ProfileCompilationInfo::MethodHotness hotness_sub =
info.GetMethodHotness(MethodReference(dex_file, sub_method_index));
ASSERT_TRUE(hotness_sub.IsHot());
ASSERT_EQ(hotness_sub.GetInlineCacheMap()->size(), 0u);
}
TEST_F(ProfileAssistantTest, TestProfileCreateWithSubtypeAndDump) { // Create the profile content.
std::vector<std::string> profile_methods = { "HLTestInlineSubtype;->inlineMonomorphic(LSuper;)I+]LSuper;LSubA;",
};
std::string input_file_contents; for (std::string& m : profile_methods) {
input_file_contents += m + std::string("\n");
}
// Create the profile and save it to disk.
ScratchFile profile_file;
std::string dex_filename = GetTestDexFileName("ProfileTestMultiDex");
ASSERT_TRUE(CreateProfile(input_file_contents, profile_file.GetFilename(), dex_filename));
TEST_F(ProfileAssistantTest, TestProfileCreateWithInvalidData) { // Create the profile content.
std::vector<std::string> profile_methods = { "HLTestInline;->inlineMonomorphic(LSuper;)I+invalid_class", // Invalid descriptor for IC. "HLTestInline;->invalid_method", // Invalid method spec (no signature). "invalid_class", // Invalid descriptor.
};
std::string input_file_contents; for (std::string& m : profile_methods) {
input_file_contents += m + std::string("\n");
}
// Create the profile and save it to disk.
ScratchFile profile_file;
std::string dex_filename = GetTestDexFileName("ProfileTestMultiDex");
ASSERT_TRUE(CreateProfile(input_file_contents,
profile_file.GetFilename(),
dex_filename));
// Load the profile from disk.
ProfileCompilationInfo info;
ASSERT_TRUE(info.Load(GetFd(profile_file)));
// Load the dex files and verify that the profile contains the expected methods info.
ScopedObjectAccess soa(Thread::Current());
jobject class_loader = LoadDex("ProfileTestMultiDex");
ASSERT_NE(class_loader, nullptr);
// Invalid descriptor in IC results in rejection of the entire line.
ProfileCompilationInfo::MethodHotness hotness =
info.GetMethodHotness(MethodReference(dex_file, inline_monomorphic->GetDexMethodIndex()));
ASSERT_FALSE(hotness.IsHot());
// No data was recorded, so the dex file does not appear in the profile. // TODO: Record all dex files passed to `profman` in the profile. Note that // this makes sense only if there are no annotations, otherwise we do not // know what annotation to use with each dex file.
std::set<dex::TypeIndex> classes;
std::set<uint16_t> hot_methods;
std::set<uint16_t> startup_methods;
std::set<uint16_t> post_start_methods;
ASSERT_FALSE(info.GetClassesAndMethods(*dex_file,
&classes,
&hot_methods,
&startup_methods,
&post_start_methods));
}
std::vector<int> profile_fds({
GetFd(profile1),
GetFd(profile2)}); int reference_profile_fd = GetFd(reference_profile);
// Use a real dex file to generate profile test data. // The file will be used during merging to filter unwanted data.
std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("ProfileTestMultiDex"); const DexFile& d1 = *dex_files[0]; const DexFile& d2 = *dex_files[1]; // The new profile info will contain the methods with indices 0-100. const uint16_t kNumberOfMethodsToEnableCompilation = 100;
ProfileCompilationInfo info1;
SetupProfile(&d1, dex1, kNumberOfMethodsToEnableCompilation, 0, profile1, &info1);
ProfileCompilationInfo info2;
SetupProfile(&d2, dex2, kNumberOfMethodsToEnableCompilation, 0, profile2, &info2);
// The reference profile info will contain the methods with indices 50-150. const uint16_t kNumberOfMethodsAlreadyCompiled = 100;
ProfileCompilationInfo reference_info;
SetupProfile(&d1, dex1,
kNumberOfMethodsAlreadyCompiled, 0, reference_profile,
&reference_info, kNumberOfMethodsToEnableCompilation / 2);
// Run profman and pass the dex file with --apk-fd.
android::base::unique_fd apk_fd(
open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY)); // NOLINT
ASSERT_GE(apk_fd.get(), 0);
// Verify that the result filtered out data not belonging to the dex file. // This is equivalent to checking that the result is equal to the merging of // all profiles while filtering out data not belonging to the dex file.
// Use a real dex file to generate profile test data.
std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("ProfileTestMultiDex"); const DexFile& d1 = *dex_files[0]; const DexFile& d2 = *dex_files[0];
// The reference profile info will contain the methods with indices 0-100.
ProfileCompilationInfo reference_info;
SetupProfile(&d1,
&d2, /*number_of_methods=*/ 100, /*number_of_classes=*/ 0,
reference_profile,
&reference_info);
// Run profman and pass the dex file with --apk-fd.
android::base::unique_fd apk_fd( // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
ASSERT_GE(apk_fd.get(), 0);
// Verify that the content has not changed.
std::string content_after;
ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_after));
EXPECT_EQ(content_before, content_after);
}
// Use a real dex file to generate profile test data.
std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("ProfileTestMultiDex"); const DexFile& d1 = *dex_files[0]; const DexFile& d2 = *dex_files[0];
// The reference profile info will contain the methods with indices 0-100.
ProfileCompilationInfo reference_info;
SetupProfile(&d1,
&d2, /*number_of_methods=*/100, /*number_of_classes=*/0,
reference_profile,
&reference_info);
// Run profman and pass the dex file with --apk-fd.
android::base::unique_fd apk_fd( // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
ASSERT_GE(apk_fd.get(), 0);
// Verify that the content has not changed.
std::string content_after;
ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_after));
EXPECT_EQ(content_before, content_after);
}
// The reference profile info will only contain the header.
ProfileCompilationInfo reference_info;
SetupProfile(/*dex_file1=*/ nullptr, /*dex_file2=*/ nullptr, /*number_of_methods=*/ 0, /*number_of_classes=*/ 0,
reference_profile,
&reference_info);
// Run profman and pass the dex file with --apk-fd.
android::base::unique_fd apk_fd( // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
ASSERT_GE(apk_fd.get(), 0);
// Verify that the content has not changed.
std::string content_after;
ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_after));
EXPECT_EQ(content_before, content_after);
}
// Use fake dex files to generate profile test data. // All the methods will be filtered out during the profman invocation.
ProfileCompilationInfo reference_info;
SetupProfile(dex1,
dex2, /*number_of_methods=*/ 100, /*number_of_classes=*/ 0,
reference_profile,
&reference_info);
// Run profman and pass the real dex file with --apk-fd.
android::base::unique_fd apk_fd( // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
ASSERT_GE(apk_fd.get(), 0);
// Verify that the content has not changed.
std::string content_after;
ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_after));
EXPECT_EQ(content_before, content_after);
}
// Use a real dex file to generate profile test data. During the copy-and-update the // matching is done based on checksum so we have to match with the real thing.
std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("ProfileTestMultiDex"); const DexFile& d1 = *dex_files[0]; const DexFile& d2 = *dex_files[1];
// Run profman and pass the dex file with --apk-fd.
android::base::unique_fd apk_fd( // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
ASSERT_GE(apk_fd.get(), 0);
// Must return kCopyAndUpdateSuccess.
ASSERT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kCopyAndUpdateSuccess) << error;
// Verify that we can load the result.
ProfileCompilationInfo result;
ASSERT_TRUE(result.Load(reference_profile.GetFd()));
// Verify that the renaming was done. for (uint16_t i = 0; i < num_methods_to_add; i ++) {
ASSERT_TRUE(result.GetMethodHotness(MethodReference(&d1, i)).IsHot()) << i;
ASSERT_TRUE(result.GetMethodHotness(MethodReference(&d2, i)).IsHot()) << i;
// Use fake dex files to generate profile test data.
ProfileCompilationInfo info1;
SetupProfile(dex1,
dex2, /*number_of_methods=*/ 100, /*number_of_classes=*/ 0,
profile1,
&info1);
// Run profman and pass the real dex file with --apk-fd. It won't match any entry in the profile.
android::base::unique_fd apk_fd( // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
ASSERT_GE(apk_fd.get(), 0);
// Must return kCopyAndUpdateNoMatch.
ASSERT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kCopyAndUpdateNoMatch) << error;
// Verify that the content is the same.
ProfileCompilationInfo result;
ASSERT_TRUE(result.Load(reference_profile.GetFd()));
EXPECT_TRUE(result.Equals(info1));
}
TEST_F(ProfileAssistantTest, BootImageMerge) {
ScratchFile profile1;
ScratchFile profile2;
ScratchFile profile3;
ScratchFile output_profile;
std::vector<uint32_t> hot_methods_1;
std::vector<uint32_t> hot_methods_2;
std::vector<uint32_t> hot_methods_3; for (size_t i = 0; i < 100; ++i) {
hot_methods_1.push_back(i);
} for (size_t i = 50; i < 150; ++i) {
hot_methods_2.push_back(i);
} for (size_t i = 100; i < 200; ++i) {
hot_methods_3.push_back(i);
}
ProfileCompilationInfo info1(/*for_boot_image=*/false);
SetupBasicProfile(
dex1, hot_methods_1, /*startup_methods=*/{}, /*post_startup_methods=*/{}, profile1, &info1);
ProfileCompilationInfo info2(/*for_boot_image=*/true);
SetupBasicProfile(
dex1, hot_methods_2, /*startup_methods=*/{}, /*post_startup_methods=*/{}, profile2, &info2);
ProfileCompilationInfo info3(/*for_boot_image=*/true);
SetupBasicProfile(
dex1, hot_methods_3, /*startup_methods=*/{}, /*post_startup_methods=*/{}, profile3, &info3);
// Verify the result: it should be equal to info2 union info3 since info1 is a regular profile // and should be ignored.
ProfileCompilationInfo result(/*for_boot_image=*/true);
ASSERT_TRUE(result.Load(output_profile.GetFd()));
ASSERT_TRUE(info2.MergeWith(info3));
ASSERT_TRUE(result.Equals(info2));
}
// Same for the legacy force merge mode.
{ int return_code = ProcessProfiles({profile1.GetFd(), profile2.GetFd(), profile3.GetFd()},
output_profile.GetFd(),
{"--force-merge", "--boot-image-merge"});
ASSERT_EQ(return_code, ProfmanResult::kSuccess);
// Verify the result: it should be equal to info2 union info3 since info1 is a regular profile // and should be ignored.
ProfileCompilationInfo result(/*for_boot_image=*/true);
ASSERT_TRUE(result.Load(output_profile.GetFd()));
ASSERT_TRUE(info2.MergeWith(info3));
ASSERT_TRUE(result.Equals(info2));
}
}
// Under default behaviour we should not advice compilation // and the reference profile should not be updated. // However we pass --force-merge to force aggregation and in this case // we should see an update.
TEST_F(ProfileAssistantTest, ForceMerge) { const uint16_t kNumberOfClassesInRefProfile = 6000; const uint16_t kNumberOfClassesInCurProfile = 6110; // Threshold is 2%.
std::vector<std::string> extra_args({"--force-merge"}); int return_code = ProcessProfiles(profile_fds, reference_profile_fd, extra_args);
ASSERT_EQ(return_code, ProfmanResult::kSuccess);
// Check that the result is the aggregation.
ProfileCompilationInfo result;
ASSERT_TRUE(result.Load(reference_profile.GetFd()));
ASSERT_TRUE(info1.MergeWith(info2));
ASSERT_TRUE(result.Equals(info1));
}
std::vector<std::string> extra_args({"--force-merge-and-analyze"}); int return_code = ProcessProfiles({cur_profile.GetFd()}, ref_profile.GetFd(), extra_args);
ASSERT_EQ(return_code, ProfmanResult::kCompile);
// Check that the result is the aggregation.
ProfileCompilationInfo result;
ASSERT_TRUE(result.Load(ref_profile.GetFd()));
ASSERT_TRUE(ref_info.MergeWith(cur_info));
ASSERT_TRUE(result.Equals(ref_info));
}
// Check that the reference profile is unchanged.
ProfileCompilationInfo result;
ASSERT_TRUE(result.Load(ref_profile.GetFd()));
ASSERT_TRUE(result.Equals(ref_info));
}
// Check that the reference profile is unchanged.
ProfileCompilationInfo result;
ASSERT_TRUE(result.Load(ref_profile.GetFd()));
ASSERT_TRUE(result.Equals(ref_info));
}
// Test that we consider the annations when we merge boot image profiles.
TEST_F(ProfileAssistantTest, BootImageMergeWithAnnotations) {
ScratchFile profile;
ScratchFile reference_profile;
std::vector<int> profile_fds({GetFd(profile)}); int reference_profile_fd = GetFd(reference_profile);
// Use a real dex file to generate profile test data so that we can pass descriptors to profman.
std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("ProfileTestMultiDex"); const DexFile& d1 = *dex_files[0]; const DexFile& d2 = *dex_files[1]; // The new profile info will contain the methods with indices 0-100.
ProfileCompilationInfo info(/*for_boot_image=*/ true);
ProfileCompilationInfo::ProfileSampleAnnotation psa1("package1");
ProfileCompilationInfo::ProfileSampleAnnotation psa2("package2");
// Run profman and pass the dex file with --apk-fd.
android::base::unique_fd apk_fd( // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
ASSERT_GE(apk_fd.get(), 0);
// Verify that we can load the result and that it equals to what we saved.
ProfileCompilationInfo result(/*for_boot_image=*/ true);
ASSERT_TRUE(result.Load(reference_profile_fd));
ASSERT_TRUE(info.Equals(result));
}
// Reverse the order of the profiles to verify we get the same behaviour.
profile_fds[0] = GetFd(profile2);
reference_profile_fd = GetFd(profile1);
ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd, boot_image_args),
ProfmanResult::kErrorBadProfiles);
ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd),
ProfmanResult::kErrorDifferentVersions);
}
// Under default behaviour we will abort if we cannot load a profile during a merge // operation. However, if we pass --force-merge to force aggregation we should // ignore files we cannot load
TEST_F(ProfileAssistantTest, ForceMergeIgnoreProfilesItCannotLoad) {
ScratchFile profile1;
ScratchFile profile2;
// Write corrupt data in the first file.
std::string content = "giberish";
ASSERT_TRUE(profile1.GetFile()->WriteFully(content.c_str(), content.length()));
std::vector<int> profile_fds({GetFd(profile1)}); int reference_profile_fd = GetFd(profile2);
// With force-merge we should merge successfully.
{
ASSERT_EQ(
ProcessProfiles(
profile_fds, reference_profile_fd, {"--force-merge-and-analyze", "--boot-image-merge"}),
ProfmanResult::kSkipCompilationEmptyProfiles);
// Same for the legacy force merge mode.
{
ASSERT_EQ(
ProcessProfiles(profile_fds, reference_profile_fd, {"--force-merge", "--boot-image-merge"}),
ProfmanResult::kSuccess);
// Without force-merge we should fail.
{
ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd, {"--boot-image-merge"}),
ProfmanResult::kErrorBadProfiles);
}
}
// Test --record-preloaded-classes-denylist option.
TEST_F(ProfileAssistantTest, TestPreloadedClassesDenylist) { // A few classes from core-oj in the profile (must be sorted). staticconst std::string classes = "Ljava/lang/Object;\n" "Ljava/util/concurrent/ThreadLocalRandom;\n" "Lsun/nio/fs/UnixChannelFactory;\n";
ScratchFile text_profile;
EXPECT_TRUE(text_profile.GetFile()->WriteFully(classes.c_str(), classes.length()));
// A few no-preload classes in the denylist that partially overlap with core-oj classes. staticconst std::string denylist = "android.content.AsyncTaskLoader$LoadTask\n" "com.android.internal.util.LatencyTracker$SLatencyTrackerHolder\n" "gov.nist.core.net.DefaultNetworkLayer\n" "java.util.concurrent.ThreadLocalRandom\n" "sun.nio.fs.UnixChannelFactory\n";
ScratchFile denylist_file;
EXPECT_TRUE(denylist_file.GetFile()->WriteFully(denylist.c_str(), denylist.length()));
// Create binary profile from the text profile and record no-preload classes.
ScratchFile binary_profile;
std::vector<std::string> argv_str;
argv_str.push_back(GetProfmanCmd());
argv_str.push_back("--output-profile-type=boot");
argv_str.push_back("--create-profile-from=" + text_profile.GetFilename());
argv_str.push_back("--reference-profile-file=" + binary_profile.GetFilename());
argv_str.push_back("--apk=" + GetLibCoreDexFileNames()[0]);
argv_str.push_back("--dex-location=" + GetLibCoreDexFileNames()[0]);
argv_str.push_back("--record-preloaded-classes-denylist");
argv_str.push_back("--preloaded-classes-denylist=" + denylist_file.GetFilename());
std::string error;
EXPECT_EQ(ExecAndReturnCode(argv_str, &error), 0) << error;
// Check that the expected no-preload classes are recorded. staticconst std::string expect = "\tclasses-no-preload: \n" "\t\tjava.util.concurrent.ThreadLocalRandom\n" "\t\tsun.nio.fs.UnixChannelFactory\n";
std::string output;
EXPECT_TRUE(DumpOnly(binary_profile.GetFilename(), &output)); const size_t offset = output.find(expect);
ASSERT_NE(offset, std::string::npos) << "cannot find no-preload classes in the profile";
}
} // namespace art
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.53 Sekunden
(vorverarbeitet am 2026-06-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.