UsageError("Command: %s", CommandLine().c_str());
UsageError("Usage: profman [options]...");
UsageError("");
UsageError(" --dump-only: dumps the content of the specified profile files");
UsageError(" to standard output (default) in a human readable form.");
UsageError("");
UsageError(" --dump-output-to-fd=<number>: redirects --dump-only output to a file descriptor.");
UsageError("");
UsageError(" --dump-classes-and-methods: dumps a sorted list of classes and methods that are");
UsageError(" in the specified profile file to standard output (default) in a human");
UsageError(" readable form. The output is valid input for --create-profile-from");
UsageError("");
UsageError(" --profile-file=<filename>: specify profiler output file to use for compilation.");
UsageError(" Can be specified multiple time, in which case the data from the different");
UsageError(" profiles will be aggregated. Can also be specified zero times, in which case");
UsageError(" profman will still analyze the reference profile against the given --apk and");
UsageError(" return exit code based on whether the reference profile is empty and whether");
UsageError(" an error occurs, but no merge will happen.");
UsageError("");
UsageError(" --profile-file-fd=<number>: same as --profile-file but accepts a file descriptor.");
UsageError(" Cannot be used together with --profile-file.");
UsageError("");
UsageError(" --reference-profile-file=<filename>: specify a reference profile.");
UsageError(" The data in this file will be compared with the data obtained by merging");
UsageError(" all the files specified with --profile-file or --profile-file-fd.");
UsageError(" If the exit code is ProfmanResult::kCompile then all --profile-file will be");
UsageError(" merged into --reference-profile-file. ");
UsageError("");
UsageError(" --reference-profile-file-fd=<number>: same as --reference-profile-file but");
UsageError(" accepts a file descriptor. Cannot be used together with");
UsageError(" --reference-profile-file.");
UsageError("");
UsageError(" --generate-test-profile=<filename>: generates a random profile file for testing.");
UsageError(" --generate-test-profile-num-dex=<number>: number of dex files that should be");
UsageError(" included in the generated profile. Defaults to 20.");
UsageError(" --generate-test-profile-method-percentage=<number>: the percentage from the maximum");
UsageError(" number of methods that should be generated. Defaults to 5.");
UsageError(" --generate-test-profile-class-percentage=<number>: the percentage from the maximum");
UsageError(" number of classes that should be generated. Defaults to 5.");
UsageError(" --generate-test-profile-seed=<number>: seed for random number generator used when");
UsageError(" generating random test profiles. Defaults to using NanoTime.");
UsageError("");
UsageError(" --create-profile-from=<filename>: creates a profile from a list of classes,");
UsageError(" methods and inline caches.");
UsageError(" --output-profile-type=(app|boot|bprof): Select output profile format for");
UsageError(" the --create-profile-from option. Default: app.");
UsageError("");
UsageError(" --dex-location=<string>: location string to use with corresponding");
UsageError(" apk-fd to find dex files");
UsageError("");
UsageError(" --apk-fd=<number>: file descriptor containing an open APK to");
UsageError(" search for dex files");
UsageError(" --apk=<filename>: an APK to search for dex files");
UsageError(" --skip-apk-verification: do not attempt to verify APKs");
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(" --method-threshold=percentage between 0 and 100");
UsageError(" what threshold to apply to the methods when deciding whether or not to");
UsageError(" include it in the final profile.");
UsageError(" --class-threshold=percentage between 0 and 100");
UsageError(" what threshold to apply to the classes when deciding whether or not to");
UsageError(" include it in the final profile.");
UsageError(" --clean-class-threshold=percentage between 0 and 100");
UsageError(" what threshold to apply to the clean classes when deciding whether or not to");
UsageError(" include it in the final profile.");
UsageError(" --preloaded-class-threshold=percentage between 0 and 100");
UsageError(" what threshold to apply to the classes when deciding whether or not to");
UsageError(" include it in the final preloaded classes.");
UsageError(" --preloaded-classes-denylist=file");
UsageError(" a file listing the classes that should not be preloaded in Zygote");
UsageError(" --record-preloaded-classes-denylist");
UsageError(" record preloaded classes denylist in the binary profile");
UsageError(" --upgrade-startup-to-hot=true|false:");
UsageError(" whether or not to upgrade startup methods to hot");
UsageError(" --special-package=pkg_name:percentage between 0 and 100");
UsageError(" what threshold to apply to the methods/classes that are used by the given");
UsageError(" 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");
UsageError(" of entries corresponding to the apks passed with --apk(-fd).");
UsageError(" --boot-image-merge: indicates that this merge is for a boot image profile.");
UsageError(" In this case, the reference profile must have a boot profile version.");
UsageError(" --force-merge: performs a forced merge, without analyzing if there is a");
UsageError(" significant difference between before and after the merge.");
UsageError(" Deprecated. Use --force-merge-and-analyze instead.");
UsageError(" --force-merge-and-analyze: performs a forced merge and analyzes if there is any");
UsageError(" difference between before and after the merge.");
UsageError(" --min-new-methods-percent-change=percentage between 0 and 100 (default 2)");
UsageError(" the min percent of new methods to trigger a compilation.");
UsageError(" --min-new-classes-percent-change=percentage between 0 and 100 (default 2)");
UsageError(" the min percent of new classes to trigger a compilation.");
UsageError("");
exit(ProfmanResult::kErrorUsage);
}
// Note: make sure you update the Usage if you change these values. static constexpr uint16_t kDefaultTestProfileNumDex = 20; static constexpr uint16_t kDefaultTestProfileMethodPercentage = 5; static constexpr uint16_t kDefaultTestProfileClassPercentage = 5;
// TODO(calin): This class has grown too much from its initial design. Split the functionality // into smaller, more contained pieces. class ProfMan final { public:
ProfMan() :
reference_profile_file_fd_(File::kInvalidFd),
dump_only_(false),
dump_classes_and_methods_(false),
generate_boot_image_profile_(false),
output_profile_type_(OutputProfileType::kApp),
dump_output_to_fd_(File::kInvalidFd),
test_profile_num_dex_(kDefaultTestProfileNumDex),
test_profile_method_percerntage_(kDefaultTestProfileMethodPercentage),
test_profile_class_percentage_(kDefaultTestProfileClassPercentage),
test_profile_seed_(NanoTime()),
start_ns_(NanoTime()),
copy_and_update_profile_key_(false),
profile_assistant_options_(ProfileAssistant::Options()) {}
// Validate global consistency between file/fd options. if (!profile_files_.empty() && !profile_files_fd_.empty()) {
Usage("Profile files should not be specified with both --profile-file-fd and --profile-file");
} if (!reference_profile_file_.empty() && FdIsValid(reference_profile_file_fd_)) {
Usage("Reference profile should not be specified with both " "--reference-profile-file-fd and --reference-profile-file");
} if (!apk_files_.empty() && !apks_fd_.empty()) {
Usage("APK files should not be specified with both --apk-fd and --apk");
}
}
ProfmanResult::ProcessingResult ProcessProfiles() { // Validate that a reference profile was passed, at the very least. It's okay that profiles are // missing, in which case profman will still analyze the reference profile (to check whether // it's empty), but no merge will happen. if (reference_profile_file_.empty() && !FdIsValid(reference_profile_file_fd_)) {
Usage("No reference profile file specified.");
} if ((!profile_files_.empty() && FdIsValid(reference_profile_file_fd_)) ||
(!profile_files_fd_.empty() && !FdIsValid(reference_profile_file_fd_))) {
Usage("Options --profile-file-fd and --reference-profile-file-fd " "should only be used together");
}
// Check if we have any apks which we should use to filter the profile data.
std::set<ProfileFilterKey> profile_filter_keys; if (!GetProfileFilterKeyFromApks(&profile_filter_keys)) { return ProfmanResult::kErrorIO;
}
// Build the profile filter function. If the set of keys is empty it means we // don't have any apks; as such we do not filter anything. const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn =
[profile_filter_keys](const std::string& profile_key, uint32_t checksum) { if (profile_filter_keys.empty()) { // No --apk was specified. Accept all dex files. returntrue;
} else { // Remove any annotations from the profile key before comparing with the keys we get from apks.
std::string base_key = ProfileCompilationInfo::GetBaseKeyFromAugmentedKey(profile_key);
std::string base_location = DexFileLoader::GetBaseLocation(base_key); return profile_filter_keys.count(ProfileFilterKey(base_location, checksum)) != 0;
}
};
ProfmanResult::ProcessingResult result;
if (reference_profile_file_.empty()) { // The file doesn't need to be flushed here (ProcessProfiles will do it) // so don't check the usage.
File file(reference_profile_file_fd_, false);
result = ProfileAssistant::ProcessProfiles(profile_files_fd_,
reference_profile_file_fd_,
filter_fn,
profile_assistant_options_);
CloseAllFds(profile_files_fd_, "profile_files_fd_");
} else {
result = ProfileAssistant::ProcessProfiles(profile_files_,
reference_profile_file_,
filter_fn,
profile_assistant_options_);
} return result;
}
bool ForEachApkFile( const std::function<bool(File file, const std::string& location)>& process_fn) { bool use_apk_fd_list = !apks_fd_.empty(); if (use_apk_fd_list) { // Get the APKs from the collection of FDs. if (dex_locations_.empty()) { // Try to compute the dex locations from the file paths of the descriptions. // This will make it easier to invoke profman with --apk-fd and without // being force to pass --dex-location when the location would be the apk path. if (!ComputeDexLocationsFromApkFds()) { returnfalse;
}
} else { if (dex_locations_.size() != apks_fd_.size()) {
Usage("The number of apk-fds must match the number of dex-locations.");
}
}
} elseif (!apk_files_.empty()) { if (dex_locations_.empty()) { // If no dex locations are specified use the apk names as locations.
dex_locations_ = apk_files_;
} elseif (dex_locations_.size() != apk_files_.size()) {
Usage("The number of apk-fds must match the number of dex-locations.");
}
} else { // No APKs were specified.
CHECK(dex_locations_.empty()); returntrue;
} for (size_t i = 0; i < dex_locations_.size(); ++i) { // We do not need to verify the apk for processing profiles. if (use_apk_fd_list) {
File file(apks_fd_[i], /*check_usage=*/false); if (!process_fn(std::move(file), dex_locations_[i])) { returnfalse;
}
} else {
File file(apk_files_[i], O_RDONLY, /*check_usage=*/false); if (file.Fd() < 0) {
PLOG(ERROR) << "Unable to open '" << apk_files_[i] << "'"; returnfalse;
} if (!process_fn(std::move(file), dex_locations_[i])) { returnfalse;
}
}
} returntrue;
}
// Get the dex locations from the apk fds. // The methods reads the links from /proc/self/fd/ to find the original apk paths // and puts them in the dex_locations_ vector. bool ComputeDexLocationsFromApkFds() { #ifdef _WIN32
PLOG(ERROR) << "ComputeDexLocationsFromApkFds is unsupported on Windows."; returnfalse; #else // We can't use a char array of PATH_MAX size without exceeding the frame size. // So we use a vector as the buffer for the path.
std::vector<char> buffer(PATH_MAX, 0); for (size_t i = 0; i < apks_fd_.size(); ++i) {
std::string fd_path = "/proc/self/fd/" + std::to_string(apks_fd_[i]);
ssize_t len = readlink(fd_path.c_str(), buffer.data(), buffer.size() - 1); if (len == -1) {
PLOG(ERROR) << "Could not open path from fd"; returnfalse;
}
bool GetClassNamesAndMethods(int fd,
std::vector<std::unique_ptr<const DexFile>>* dex_files,
std::set<std::string>* out_lines) { // For dumping, try loading as app profile and if that fails try loading as boot profile. for (bool for_boot_image : {false, true}) {
ProfileCompilationInfo profile_info(for_boot_image); if (profile_info.Load(fd)) { return GetClassNamesAndMethods(profile_info, dex_files, out_lines);
}
}
LOG(ERROR) << "Cannot load profile info"; returnfalse;
}
bool GetClassNamesAndMethods(const std::string& profile_file,
std::vector<std::unique_ptr<const DexFile>>* dex_files,
std::set<std::string>* out_lines) { #ifdef _WIN32 int flags = O_RDONLY; #else int flags = O_RDONLY | O_CLOEXEC; #endif int fd = open(profile_file.c_str(), flags); if (!FdIsValid(fd)) {
PLOG(ERROR) << "Cannot open " << profile_file; returnfalse;
} if (!GetClassNamesAndMethods(fd, dex_files, out_lines)) { returnfalse;
} if (close(fd) < 0) {
PLOG(WARNING) << "Failed to close descriptor";
} returntrue;
}
int DumpClassesAndMethods() { // Validate that at least one profile file or reference was specified. if (profile_files_.empty() && profile_files_fd_.empty() &&
reference_profile_file_.empty() && !FdIsValid(reference_profile_file_fd_)) {
Usage("No profile files or reference profile specified.");
}
// Open the dex files to get the names for classes.
std::vector<std::unique_ptr<const DexFile>> dex_files;
OpenApkFilesFromLocations(&dex_files); // Build a vector of class names from individual profile files.
std::set<std::string> class_names; if (!profile_files_fd_.empty()) { for (int profile_file_fd : profile_files_fd_) { if (!GetClassNamesAndMethods(profile_file_fd, &dex_files, &class_names)) { return -1;
}
}
} if (!profile_files_.empty()) { for (const std::string& profile_file : profile_files_) { if (!GetClassNamesAndMethods(profile_file, &dex_files, &class_names)) { return -1;
}
}
} // Concatenate class names from reference profile file. if (FdIsValid(reference_profile_file_fd_)) { if (!GetClassNamesAndMethods(reference_profile_file_fd_, &dex_files, &class_names)) { return -1;
}
} if (!reference_profile_file_.empty()) { if (!GetClassNamesAndMethods(reference_profile_file_, &dex_files, &class_names)) { return -1;
}
} // Dump the class names.
std::string dump; for (const std::string& class_name : class_names) {
dump += class_name + std::string("\n");
} if (!FdIsValid(dump_output_to_fd_)) {
std::cout << dump;
} else {
unix_file::FdFile out_fd(dump_output_to_fd_, /*check_usage=*/ false); if (!out_fd.WriteFully(dump.c_str(), dump.length())) { return -1;
}
} return0;
}
// Read lines from the given file, dropping comments and empty lines. Post-process each line with // the given function. template <typename T> static T* ReadCommentedInputFromFile( constchar* input_filename, std::function<std::string(constchar*)>* process) {
std::unique_ptr<std::ifstream> input_file(new std::ifstream(input_filename, std::ifstream::in)); if (input_file.get() == nullptr) {
LOG(ERROR) << "Failed to open input file " << input_filename; return nullptr;
}
std::unique_ptr<T> result(
ReadCommentedInputStream<T>(*input_file, process));
input_file->close(); return result.release();
}
// Read lines from the given stream, dropping comments and empty lines. Post-process each line // with the given function. template <typename T> static T* ReadCommentedInputStream(
std::istream& in_stream,
std::function<std::string(constchar*)>* process) {
std::unique_ptr<T> output(new T()); while (in_stream.good()) {
std::string dot;
std::getline(in_stream, dot); if (dot.starts_with("#") || dot.empty()) { continue;
} if (process != nullptr) {
std::string descriptor((*process)(dot.c_str()));
output->insert(output->end(), descriptor);
} else {
output->insert(output->end(), dot);
}
} return output.release();
}
// Find class klass_descriptor in the given dex_files and store its reference // in the out parameter class_ref, and/or the in_out class_def parameter if provided. // Return true if a reference of the class was found in any of the dex_files (and the ClassDef // handle was available, if requested). template <typename Container> bool FindClassImpl(const Container& dex_files,
std::string_view klass_descriptor, /*out*/ TypeReference* class_ref, /*in_out*/ const dex::ClassDef** class_def = nullptr) { auto find_class_impl = [&](std::string_view desc) { for (constauto& dex_file : dex_files) { const dex::TypeId* type_id = dex_file->FindTypeId(desc); if (type_id == nullptr) { continue;
}
dex::TypeIndex type_index = dex_file->GetIndexForTypeId(*type_id); if (class_def != nullptr) {
*class_def = dex_file->FindClassDef(type_index); if (*class_def == nullptr) { continue;
}
}
*class_ref = TypeReference(std::to_address(dex_file), type_index); returntrue;
} returnfalse;
};
if (find_class_impl(klass_descriptor)) { returntrue;
}
// Try to find the class with the rewritten name. if (auto rewritten_klass = RewriteSyntheticProfileClassIfNeeded(klass_descriptor)) { return find_class_impl(*rewritten_klass);
}
// Find class klass_descriptor in the given dex_files and store its reference // in the out parameter class_ref. // Return true if a reference of the class was found in any of the dex_files. bool FindClass(const std::vector<std::unique_ptr<const DexFile>>& dex_files,
std::string_view klass_descriptor, /*out*/ TypeReference* class_ref) { return FindClassImpl(dex_files, klass_descriptor, class_ref);
}
// Find the method specified by method_spec in the class class_ref.
uint32_t FindMethodIndex(const TypeReference& class_ref,
std::string_view method_spec) { const DexFile* dex_file = class_ref.dex_file;
size_t signature_start = method_spec.find(kProfileParsingFirstCharInSignature); if (signature_start == std::string_view::npos) {
LOG(ERROR) << "Invalid method name and signature: " << method_spec; return dex::kDexNoIndex;
}
// Get dex-pcs of any virtual + interface invokes referencing a method of the // 'target' type in the given method. void GetAllInvokes(const TypeReference& class_ref,
uint16_t method_idx,
dex::TypeIndex target, /*out*/ std::vector<uint32_t>* dex_pcs) { const DexFile* dex_file = class_ref.dex_file;
VisitAllInstructions(class_ref, method_idx, [&](const DexInstructionPcPair& inst) -> bool { switch (inst->Opcode()) { case Instruction::INVOKE_INTERFACE: case Instruction::INVOKE_INTERFACE_RANGE: case Instruction::INVOKE_VIRTUAL: case Instruction::INVOKE_VIRTUAL_RANGE: { const dex::MethodId& meth = dex_file->GetMethodId(inst->VRegB()); if (meth.class_idx_ == target) {
dex_pcs->push_back(inst.DexPc());
} break;
} default: break;
} returntrue;
});
}
// Given a method, return true if the method has a single INVOKE_VIRTUAL in its byte code. // Upon success it returns true and stores the method index and the invoke dex pc // in the output parameters. // The format of the method spec is "inlinePolymorphic(LSuper;)I+LSubA;,LSubB;,LSubC;". bool HasSingleInvoke(const TypeReference& class_ref,
uint16_t method_index, /*out*/ uint32_t* dex_pc) { bool found_invoke = false; bool found_multiple_invokes = false;
VisitAllInstructions(class_ref, method_index, [&](const DexInstructionPcPair& inst) -> bool { if (inst->Opcode() == Instruction::INVOKE_VIRTUAL ||
inst->Opcode() == Instruction::INVOKE_VIRTUAL_RANGE ||
inst->Opcode() == Instruction::INVOKE_INTERFACE ||
inst->Opcode() == Instruction::INVOKE_INTERFACE_RANGE) { if (found_invoke) {
LOG(ERROR) << "Multiple invoke INVOKE_VIRTUAL found: "
<< class_ref.dex_file->PrettyMethod(method_index); returnfalse;
}
found_invoke = true;
*dex_pc = inst.DexPc();
} returntrue;
}); if (!found_invoke) {
LOG(ERROR) << "Could not find any INVOKE_VIRTUAL/INTERFACE: "
<< class_ref.dex_file->PrettyMethod(method_index);
} return found_invoke && !found_multiple_invokes;
}
struct InlineCacheSegment { public: using IcArray =
std::array<std::string_view, ProfileCompilationInfo::kIndividualInlineCacheSize + 1>; staticvoid SplitInlineCacheSegment(std::string_view ic_line, /*out*/ std::vector<InlineCacheSegment>* res) { if (ic_line[0] != kProfileParsingInlineChacheTargetSep) { // single target
InlineCacheSegment out;
Split(ic_line, kProfileParsingTypeSep, &out.inline_caches_);
res->push_back(out); return;
}
std::vector<std::string_view> targets_and_resolutions; // Avoid a zero-length entry. for (std::string_view t :
SplitString(ic_line.substr(1), kProfileParsingInlineChacheTargetSep)) {
InlineCacheSegment out; // The target may be an array for methods defined in `j.l.Object`, such as `clone()`.
size_t recv_end; if (UNLIKELY(t[0] == '[')) {
recv_end = t.find_first_not_of('[', 1u);
DCHECK_NE(recv_end, std::string_view::npos); if (t[recv_end] == 'L') {
recv_end = t.find_first_of(';', recv_end + 1u);
DCHECK_NE(recv_end, std::string_view::npos);
} else { // Primitive array.
DCHECK_NE(Primitive::GetType(t[recv_end]), Primitive::kPrimNot);
}
} else {
DCHECK_EQ(t[0], 'L') << "Target is not a class? " << t;
recv_end = t.find_first_of(';', 1u);
DCHECK_NE(recv_end, std::string_view::npos);
}
out.receiver_ = t.substr(0, recv_end + 1);
Split(t.substr(recv_end + 1), kProfileParsingTypeSep, &out.inline_caches_);
res->push_back(out);
}
}
std::ostream& Dump(std::ostream& os) const { if (!IsSingleReceiver()) {
os << "[" << GetReceiverType();
} bool first = true; for (std::string_view target : inline_caches_) { if (target.empty()) { break;
} elseif (!first) {
os << ",";
}
first = false;
os << target;
} return os;
}
private:
std::optional<std::string_view> receiver_; // Max number of ics in the profile file. Don't need to store more than this // (although internally we can have as many as we want). If we fill this up // we are megamorphic.
IcArray inline_caches_;
// Try to perform simple method resolution to produce a more useful profile. // This will resolve to the nearest class+method-index which is within the // same dexfile and in a declared supertype of the starting class. It will // return nullopt if it cannot find an appropriate method or the nearest // possibility is private. // TODO: This should ideally support looking in other dex files. That's getting // to the point of needing to have a whole class-linker so it's probably not // worth it.
std::optional<ClassMethodReference> ResolveMethod(TypeReference class_ref,
uint32_t method_index) { const DexFile* dex = class_ref.dex_file; const dex::ClassDef* def = dex->FindClassDef(class_ref.TypeIndex()); if (def == nullptr || method_index >= dex->NumMethodIds()) { // Class not in dex-file. return std::nullopt;
} if (dex->GetClassData(*def) == nullptr) { // Class has no fields or methods. return std::nullopt;
} if (LIKELY(dex->GetCodeItemOffset(*def, method_index).has_value())) { return ClassMethodReference{class_ref, method_index};
} // What to look for. const dex::MethodId& method_id = dex->GetMethodId(method_index); // No going between different dexs so use name and proto directly const dex::ProtoIndex& method_proto = method_id.proto_idx_; const dex::StringIndex& method_name = method_id.name_idx_; // Floyd's algo to prevent infinite loops. // Slow-iterator position for Floyd's
dex::TypeIndex slow_class_type = def->class_idx_; // Whether to take a step with the slow iterator. bool update_slow = false; for (dex::TypeIndex cur_candidate = def->superclass_idx_;
cur_candidate != dex::TypeIndex::Invalid() && cur_candidate != slow_class_type;) { const dex::ClassDef* cur_class_def = dex->FindClassDef(cur_candidate); if (cur_class_def == nullptr) { // We left the dex file. return std::nullopt;
} const dex::MethodId* cur_id =
dex->FindMethodIdByIndex(cur_candidate, method_name, method_proto); if (cur_id != nullptr) { if (dex->GetClassData(*cur_class_def) == nullptr) { // Class has no fields or methods. return std::nullopt;
} if (dex->GetCodeItemOffset(*cur_class_def, dex->GetIndexForMethodId(*cur_id)).has_value()) { return ClassMethodReference{TypeReference(dex, cur_candidate),
dex->GetIndexForMethodId(*cur_id)};
}
} // Floyd's algo step.
cur_candidate = cur_class_def->superclass_idx_;
slow_class_type =
update_slow ? dex->FindClassDef(slow_class_type)->superclass_idx_ : slow_class_type;
update_slow = !update_slow;
} return std::nullopt;
}
// Process preloaded-classes-denylist. For every class on the list, try to find it in one of the // dex files. If found, add the class to the profile. If not found, this is also fine: we may use // one aggregated denylist that contains classes from different dex files, or the denylist may be // outdated. bool ProcessPreloadedClassesDenylist(const std::vector<std::unique_ptr<const DexFile>>& dex_files, /*out*/ ProfileCompilationInfo* profile) {
DCHECK(boot_image_options_.record_preloaded_classes_denylist);
if (boot_image_options_.preloaded_classes_denylist.empty() &&
!profile->AddNoPreloadMarker(dex_files)) {
LOG(ERROR) << "Unable to add no-preload marker to the profile"; returnfalse;
}
for (const std::string& klass : boot_image_options_.preloaded_classes_denylist) { // There should be no arrays in preloaded-classes-denylist.
CHECK_EQ(klass.find('['), std::string::npos);
std::string descriptor = DotToDescriptor(klass);
TypeReference class_ref(/*dex_file=*/nullptr, dex::TypeIndex()); if (FindClassDef(dex_files, descriptor, &class_ref) != nullptr) { if (!profile->AddClassNoPreload(*class_ref.dex_file, class_ref.TypeIndex())) {
LOG(ERROR) << "Unable to add class '" << klass
<< "' from preloaded-classes-denylist to the profile"; returnfalse;
}
}
} returntrue;
}
// Process a line defining a class or a method and its inline caches. // Upon success return true and add the class or the method info to profile. // Inline caches are identified by the type of the declared receiver type. // The possible line formats are: // "LJustTheClass;". // "LTestInline;->inlinePolymorphic(LSuper;)I+LSubA;,LSubB;,LSubC;". // "LTestInline;->inlineMissingTypes(LSuper;)I+missing_types". // // Note no ',' after [LTarget; // "LTestInline;->multiInlinePolymorphic(LSuper;)I+]LTarget1;LResA;,LResB;]LTarget2;LResC;,LResD;". // "LTestInline;->multiInlinePolymorphic(LSuper;)I+]LTarget1;missing_types]LTarget2;LResC;,LResD;". // "{annotation}LTestInline;->inlineNoInlineCaches(LSuper;)I". // "LTestInline;->*". // The method and classes are searched only in the given dex files. bool ProcessLine(const std::vector<std::unique_ptr<const DexFile>>& dex_files,
std::string_view maybe_annotated_line, /*out*/ProfileCompilationInfo* profile) { // First, process the annotation. if (maybe_annotated_line.empty()) { returntrue;
} // Working line variable which will contain the user input without the annotations.
std::string_view line = maybe_annotated_line;
std::string_view annotation_string; if (maybe_annotated_line[0] == kAnnotationStart) {
size_t end_pos = maybe_annotated_line.find(kAnnotationEnd, 0); if (end_pos == std::string::npos || end_pos == 0) {
LOG(ERROR) << "Invalid line: " << maybe_annotated_line; returnfalse;
}
annotation_string = maybe_annotated_line.substr(1, end_pos - 1); // Update the working line.
line = maybe_annotated_line.substr(end_pos + 1);
}
if (method_str.empty()) { auto array_it = std::find_if(klass.begin(), klass.end(), [](char c) { return c != '['; });
size_t array_dim = std::distance(klass.begin(), array_it); if (klass.size() == array_dim + 1u) { // Attribute primitive types and their arrays to the first dex file.
profile->AddClass(*dex_files[0], klass, annotation); returntrue;
} // Attribute non-primitive classes and their arrays to the dex file with the definition.
TypeReference class_ref(/* dex_file= */ nullptr, dex::TypeIndex()); if (FindClassDef(dex_files, klass.substr(array_dim), &class_ref) == nullptr) {
LOG(WARNING) << "Could not find class definition: " << klass.substr(array_dim); returnfalse;
} if (array_dim != 0) { // Let the ProfileCompilationInfo find the type index or add an extra descriptor. return profile->AddClass(*class_ref.dex_file, klass, annotation);
} else { return profile->AddClass(*class_ref.dex_file, class_ref.TypeIndex(), annotation);
}
}
uint32_t flags = 0; if (is_hot) {
flags |= ProfileCompilationInfo::MethodHotness::kFlagHot;
} if (is_startup) {
flags |= ProfileCompilationInfo::MethodHotness::kFlagStartup;
} if (is_post_startup) {
flags |= ProfileCompilationInfo::MethodHotness::kFlagPostStartup;
}
if (method_str == kClassAllMethods) { // Start by adding the class.
profile->AddClass(*class_ref.dex_file, class_ref.TypeIndex(), annotation);
uint16_t class_def_index = class_ref.dex_file->GetIndexForClassDef(*class_def);
ClassAccessor accessor(*class_ref.dex_file, class_def_index);
std::vector<ProfileMethodInfo> methods; for (const ClassAccessor::Method& method : accessor.GetMethods()) { if (method.GetCodeItemOffset() != 0) { // Add all of the methods that have code to the profile.
methods.push_back(ProfileMethodInfo(method.GetReference()));
}
} // TODO: Check return value?
profile->AddMethods(
methods, static_cast<ProfileCompilationInfo::MethodHotness::Flag>(flags), annotation); returntrue;
}
// Process the method.
std::string method_spec;
// If none of the flags are set, default to hot. // TODO: Why is this done after we have already calculated `flags`?
is_hot = is_hot || (!is_hot && !is_startup && !is_post_startup);
// Lifetime of segments is same as method_elems since it contains pointers into the string-data
std::vector<InlineCacheSegment> segments;
std::vector<std::string_view> method_elems;
Split(method_str, kProfileParsingInlineChacheSep, &method_elems); if (method_elems.size() == 2) {
method_spec = method_elems[0];
InlineCacheSegment::SplitInlineCacheSegment(method_elems[1], &segments);
} elseif (method_elems.size() == 1) {
method_spec = method_elems[0];
} else {
LOG(ERROR) << "Invalid method line: " << line; returnfalse;
}
const uint32_t method_index = FindMethodIndex(class_ref, method_spec); if (method_index == dex::kDexNoIndex) {
LOG(WARNING) << "Could not find method " << klass << "->" << method_spec; returnfalse;
}
std::vector<ProfileMethodInfo::ProfileInlineCache> inline_caches; // We can only create inline-caches when we actually have code we can // examine. If we couldn't resolve the method don't bother trying to create // inline-caches. if (resolved_class_method_ref) { for (const InlineCacheSegment& segment : segments) {
std::vector<uint32_t> dex_pcs; if (segment.IsSingleReceiver()) {
DCHECK_EQ(segments.size(), 1u);
dex_pcs.resize(1, -1); // TODO This single invoke format should really be phased out and // removed. if (!HasSingleInvoke(class_ref, method_index, &dex_pcs[0])) { returnfalse;
}
} else { // Get the type-ref the method code will use.
std::string_view receiver_descriptor = segment.GetReceiverType();
TypeReference receiver_ref(/* dex_file= */ nullptr, dex::TypeIndex()); if (!FindClass(class_ref.dex_file, receiver_descriptor, &receiver_ref)) {
LOG(WARNING) << "Could not find class: "
<< segment.GetReceiverType() << " in dex-file "
<< class_ref.dex_file << ". Ignoring IC group: '"
<< segment << "'"; continue;
}
dex::TypeIndex target_index = receiver_ref.TypeIndex();
GetAllInvokes(resolved_class_method_ref->type_,
resolved_class_method_ref->method_index_,
target_index,
&dex_pcs);
} bool missing_types = segment.GetIcTargets()[0] == kMissingTypesMarker; bool megamorphic_types =
segment.GetIcTargets()[0] == kMegamorphicTypesMarker;
std::vector<TypeReference> classes; if (!missing_types && !megamorphic_types) {
classes.reserve(segment.NumIcTargets()); for (const std::string_view& ic_class : segment.GetIcTargets()) { if (ic_class.empty()) { break;
} if (!IsValidDescriptor(std::string(ic_class).c_str())) {
LOG(ERROR) << "Invalid descriptor for inline cache: " << ic_class; returnfalse;
} // TODO: Allow referencing classes without a `dex::TypeId` in any of the dex files.
TypeReference ic_class_ref(/* dex_file= */ nullptr, dex::TypeIndex()); if (!FindClass(dex_files, ic_class, &ic_class_ref)) {
LOG(segment.IsSingleReceiver() ? ERROR : WARNING)
<< "Could not find class: " << ic_class << " in " << segment; if (segment.IsSingleReceiver()) { returnfalse;
} else { // Be a bit more forgiving with profiles from servers.
missing_types = true;
classes.clear(); break;
}
}
classes.push_back(ic_class_ref);
}
} for (size_t dex_pc : dex_pcs) {
inline_caches.emplace_back(dex_pc, missing_types, classes, megamorphic_types);
}
}
}
MethodReference ref(class_ref.dex_file, method_index); if (is_hot) {
ClassMethodReference orig_cmr { class_ref, method_index }; if (!inline_caches.empty() &&
resolved_class_method_ref &&
orig_cmr != *resolved_class_method_ref) { // We have inline-caches on a method that doesn't actually exist. We // want to put the inline caches on the resolved version of the method // (if we could find one) and just mark the actual method as present. const DexFile *dex = resolved_class_method_ref->type_.dex_file;
LOG(VERBOSE) << "Adding "
<< dex->PrettyMethod(
resolved_class_method_ref->method_index_)
<< " as alias for " << dex->PrettyMethod(method_index); // The inline-cache refers to a supertype of the actual profile line. // Include this supertype method in the profile as well.
MethodReference resolved_ref(class_ref.dex_file,
resolved_class_method_ref->method_index_);
profile->AddMethod(
ProfileMethodInfo(resolved_ref, inline_caches), static_cast<ProfileCompilationInfo::MethodHotness::Flag>(flags),
annotation);
profile->AddMethod(
ProfileMethodInfo(ref), static_cast<ProfileCompilationInfo::MethodHotness::Flag>(flags),
annotation);
} else {
profile->AddMethod(
ProfileMethodInfo(ref, inline_caches), static_cast<ProfileCompilationInfo::MethodHotness::Flag>(flags),
annotation);
}
} if (flags != 0) { if (!profile->AddMethod(ProfileMethodInfo(ref), static_cast<ProfileCompilationInfo::MethodHotness::Flag>(flags),
annotation)) { returnfalse;
}
DCHECK(profile->GetMethodHotness(ref, annotation).IsInProfile()) << method_spec;
} returntrue;
}
int OpenReferenceProfile() const { int fd = reference_profile_file_fd_; if (!FdIsValid(fd)) {
CHECK(!reference_profile_file_.empty()); #ifdef _WIN32 int flags = O_CREAT | O_TRUNC | O_WRONLY; #else int flags = O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC; #endif
fd = open(reference_profile_file_.c_str(), flags, 0644); if (fd < 0) {
PLOG(ERROR) << "Cannot open " << reference_profile_file_; return File::kInvalidFd;
}
} return fd;
}
// Create and store a ProfileBootInfo. int CreateBootProfile() { // Validate parameters for this command. if (apk_files_.empty() && apks_fd_.empty()) {
Usage("APK files must be specified");
} if (dex_locations_.empty()) {
Usage("DEX locations must be specified");
} if (reference_profile_file_.empty() && !FdIsValid(reference_profile_file_fd_)) {
Usage("Reference profile must be specified with --reference-profile-file or " "--reference-profile-file-fd");
} if (!profile_files_.empty() || !profile_files_fd_.empty()) {
Usage("Profile must be specified with --reference-profile-file or " "--reference-profile-file-fd");
} // Open the profile output file if needed. int fd = OpenReferenceProfile(); if (!FdIsValid(fd)) { return -1;
} // Read the user-specified list of methods.
std::unique_ptr<std::vector<std::string>>
user_lines(ReadCommentedInputFromFile<std::vector<std::string>>(
create_profile_from_file_.c_str(), nullptr)); // No post-processing.
// Open the dex files to look up classes and methods.
std::vector<std::unique_ptr<const DexFile>> dex_files;
OpenApkFilesFromLocations(&dex_files);
// Process the lines one by one and add the successful ones to the profile.
ProfileBootInfo info;
for (constauto& line : *user_lines) {
ProcessBootLine(dex_files, line, &info);
}
// Write the profile file.
CHECK(info.Save(fd));
if (close(fd) < 0) {
PLOG(WARNING) << "Failed to close descriptor";
}
return0;
}
// Creates a profile from a human friendly textual representation. // The expected input format is: // # Classes // Ljava/lang/Comparable; // Ljava/lang/Math; // # Methods with inline caches // LTestInline;->inlinePolymorphic(LSuper;)I+LSubA;,LSubB;,LSubC; // LTestInline;->noInlineCache(LSuper;)I int CreateProfile() { // Validate parameters for this command. if (apk_files_.empty() && apks_fd_.empty()) {
Usage("APK files must be specified");
} if (dex_locations_.empty()) {
Usage("DEX locations must be specified");
} if (reference_profile_file_.empty() && !FdIsValid(reference_profile_file_fd_)) {
Usage("Reference profile must be specified with --reference-profile-file or " "--reference-profile-file-fd");
} if (!profile_files_.empty() || !profile_files_fd_.empty()) {
Usage("Profile must be specified with --reference-profile-file or " "--reference-profile-file-fd");
} // Open the profile output file if needed. int fd = OpenReferenceProfile(); if (!FdIsValid(fd)) { return -1;
} // Read the user-specified list of classes and methods.
std::unique_ptr<std::unordered_set<std::string>>
user_lines(ReadCommentedInputFromFile<std::unordered_set<std::string>>(
create_profile_from_file_.c_str(), nullptr)); // No post-processing.
// Open the dex files to look up classes and methods.
std::vector<std::unique_ptr<const DexFile>> dex_files;
OpenApkFilesFromLocations(&dex_files);
// Process the lines one by one and add the successful ones to the profile. bool for_boot_image = GetOutputProfileType() == OutputProfileType::kBoot;
ProfileCompilationInfo info(for_boot_image);
if (for_boot_image) { // Add all dex files to the profile. This is needed for jitzygote to indicate // which dex files are part of the boot image extension to compile in memory. for (const std::unique_ptr<const DexFile>& dex_file : dex_files) { if (info.FindOrAddDexFile(*dex_file) == info.MaxProfileIndex()) {
LOG(ERROR) << "Failed to add dex file to boot image profile: " << dex_file->GetLocation(); return -1;
}
}
}
for (constauto& line : *user_lines) {
ProcessLine(dex_files, line, &info);
}
if (boot_image_options_.record_preloaded_classes_denylist) {
ProcessPreloadedClassesDenylist(dex_files, &info);
}
// Write the profile file.
CHECK(info.Save(fd)); if (close(fd) < 0) {
PLOG(WARNING) << "Failed to close descriptor";
} return0;
}
// Create and store a ProfileCompilationInfo for the boot image. int CreateBootImageProfile() { // Open the input profile file. if (profile_files_.size() < 1) {
LOG(ERROR) << "At least one --profile-file must be specified."; return -1;
} // Open the dex files.
std::vector<std::unique_ptr<const DexFile>> dex_files;
OpenApkFilesFromLocations(&dex_files); if (dex_files.empty()) {
PLOG(ERROR) << "Expected dex files for creating boot profile"; return -2;
}
if (!GenerateBootImageProfile(dex_files,
profile_files_,
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;
} return0;
}
int GenerateTestProfile() { // Validate parameters for this command. if (test_profile_method_percerntage_ > 100) {
Usage("Invalid percentage for --generate-test-profile-method-percentage");
} if (test_profile_class_percentage_ > 100) {
Usage("Invalid percentage for --generate-test-profile-class-percentage");
} // If given APK files or DEX locations, check that they're ok. if (!apk_files_.empty() || !apks_fd_.empty() || !dex_locations_.empty()) { if (apk_files_.empty() && apks_fd_.empty()) {
Usage("APK files must be specified when passing DEX locations to --generate-test-profile");
} if (dex_locations_.empty()) {
Usage("DEX locations must be specified when passing APK files to --generate-test-profile");
}
} // ShouldGenerateTestProfile confirms !test_profile_.empty(). #ifdef _WIN32 int flags = O_CREAT | O_TRUNC | O_WRONLY; #else int flags = O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC; #endif int profile_test_fd = open(test_profile_.c_str(), flags, 0644); if (profile_test_fd < 0) {
PLOG(ERROR) << "Cannot open " << test_profile_; return -1;
} bool result; if (apk_files_.empty() && apks_fd_.empty() && dex_locations_.empty()) {
result = ProfileCompilationInfo::GenerateTestProfile(profile_test_fd,
test_profile_num_dex_,
test_profile_method_percerntage_,
test_profile_class_percentage_,
test_profile_seed_);
} else { // Open the dex files to look up classes and methods.
std::vector<std::unique_ptr<const DexFile>> dex_files;
OpenApkFilesFromLocations(&dex_files); // Create a random profile file based on the set of dex files.
result = ProfileCompilationInfo::GenerateTestProfile(profile_test_fd,
dex_files,
test_profile_method_percerntage_,
test_profile_class_percentage_,
test_profile_seed_);
}
close(profile_test_fd); // ignore close result. return result ? 0 : -1;
}
ProfmanResult::CopyAndUpdateResult CopyAndUpdateProfileKey() { // Validate that at least one profile file was passed, as well as a reference profile. if (!(profile_files_.size() == 1 ^ profile_files_fd_.size() == 1)) {
Usage("Only one profile file should be specified.");
} if (reference_profile_file_.empty() && !FdIsValid(reference_profile_file_fd_)) {
Usage("No reference profile file specified.");
}
if (apk_files_.empty() && apks_fd_.empty()) {
Usage("No apk files specified");
}
bool use_fds = profile_files_fd_.size() == 1;
ProfileCompilationInfo profile; // Do not clear if invalid. The input might be an archive. bool load_ok = use_fds
? profile.Load(profile_files_fd_[0])
: profile.Load(profile_files_[0], /*clear_if_invalid=*/ false); if (load_ok) { // Open the dex files to look up classes and methods.
std::vector<std::unique_ptr<const DexFile>> dex_files;
OpenApkFilesFromLocations(&dex_files); bool matched = false; if (!profile.UpdateProfileKeys(dex_files, &matched)) { return ProfmanResult::kCopyAndUpdateErrorFailedToUpdateProfile;
} bool result = use_fds
? profile.Save(reference_profile_file_fd_)
: profile.Save(reference_profile_file_, /*bytes_written=*/ nullptr); if (!result) { return ProfmanResult::kCopyAndUpdateErrorFailedToSaveProfile;
} return matched ? ProfmanResult::kCopyAndUpdateSuccess : ProfmanResult::kCopyAndUpdateNoMatch;
} else { return ProfmanResult::kCopyAndUpdateErrorFailedToLoadProfile;
}
}
staticvoid CloseAllFds(const std::vector<int>& fds, constchar* descriptor) { for (size_t i = 0; i < fds.size(); i++) { if (close(fds[i]) < 0) {
PLOG(WARNING) << "Failed to close descriptor for "
<< descriptor << " at index " << i << ": " << fds[i];
}
}
}
// See ProfmanResult for return codes. staticint profman(int argc, char** argv) {
ProfMan profman;
// Parse arguments. Argument mistakes will lead to exit(EXIT_FAILURE) in UsageError.
profman.ParseArgs(argc, argv);
// Initialize MemMap for ZipArchive::OpenFromFd.
MemMap::Init();
if (profman.ShouldGenerateTestProfile()) { return profman.GenerateTestProfile();
} if (profman.ShouldOnlyDumpProfile()) { return profman.DumpProfileInfo();
} if (profman.ShouldOnlyDumpClassesAndMethods()) { return profman.DumpClassesAndMethods();
} if (profman.ShouldCreateProfile()) { if (profman.GetOutputProfileType() == OutputProfileType::kBprof) { return profman.CreateBootProfile();
} else { return profman.CreateProfile();
}
}
if (profman.ShouldCreateBootImageProfile()) { return profman.CreateBootImageProfile();
}
if (profman.ShouldCopyAndUpdateProfileKey()) { return profman.CopyAndUpdateProfileKey();
}
// Process profile information and assess if we need to do a profile guided compilation. // This operation involves I/O. return profman.ProcessProfiles();
}
} // namespace art
int main(int argc, char **argv) { return art::profman(argc, argv);
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.27 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.