/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "boot_image_profile.h" #include #include #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" #include "profile/profile_compilation_info.h" namespace art { using Hotness = ProfileCompilationInfo::MethodHotness; 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(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(const std::string& class_name, uint32_t max_aggregation_count, const FlattenProfileData::ItemMetadata& metadata, const BootImageOptions& options) { bool denylisted = options.preloaded_classes_denylist.find(class_name) != options.preloaded_classes_denylist.end(); return !denylisted && IncludeItemInProfile( max_aggregation_count, options.preloaded_class_threshold, metadata, options); } bool GenerateBootImageProfile( const std::vector>& dex_files, const std::vector& profile_files, const BootImageOptions& options, const std::string& boot_profile_out_path, const std::string& preloaded_classes_out_path) { if (boot_profile_out_path.empty()) { LOG(ERROR) << "No output file specified"; return false; } bool generate_preloaded_classes = !preloaded_classes_out_path.empty(); std::unique_ptr flattend_data(new FlattenProfileData()); for (const std::string& profile_file : profile_files) { ProfileCompilationInfo profile(/*for_boot_image=*/ true); if (!profile.Load(profile_file, /*clear_if_invalid=*/ false)) { LOG(ERROR) << "Profile is not a valid: " << profile_file; return false; } std::unique_ptr currentData = profile.ExtractProfileData(dex_files); flattend_data->MergeData(*currentData); } // 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 profile_methods; SafeMap profile_classes; SafeMap preloaded_classes; for (const auto& it : flattend_data->GetMethodData()) { if (IncludeMethodInProfile(flattend_data->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); } profile_methods.Put(BootImageRepresentation(it.first), metadata); } } for (const auto& it : flattend_data->GetClassData()) { const TypeReference& type_ref = it.first; const FlattenProfileData::ItemMetadata& metadata = it.second; if (IncludeClassInProfile(type_ref, flattend_data->GetMaxAggregationForClasses(), metadata, options)) { profile_classes.Put(BootImageRepresentation(it.first), it.second); } std::string preloaded_class_representation = PreloadedClassesRepresentation(it.first); if (generate_preloaded_classes && IncludeInPreloadedClasses( preloaded_class_representation, flattend_data->GetMaxAggregationForClasses(), metadata, options)) { preloaded_classes.Put(preloaded_class_representation, 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"; } if (generate_preloaded_classes) { 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) && (!generate_preloaded_classes || android::base::WriteStringToFile(preloaded_content, preloaded_classes_out_path)); } } // namespace art