| /* |
| * 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 <memory> |
| #include <set> |
| |
| #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<float>(max_aggregation_count); |
| for (const auto& annotIt : metadata.GetAnnotations()) { |
| const auto&thresholdIt = |
| options.special_packages_thresholds.find(annotIt.GetOriginPackageName()); |
| if (thresholdIt != options.special_packages_thresholds.end()) { |
| if (item_percent >= (thresholdIt->second / 100.f)) { |
| return true; |
| } |
| } |
| } |
| return item_percent >= (item_threshold / 100.f); |
| } |
| |
| // Returns true iff a method with the given metada should be included in the profile. |
| static bool IncludeMethodInProfile(uint32_t max_aggregation_count, |
| const FlattenProfileData::ItemMetadata& metadata, |
| const BootImageOptions& options) { |
| return IncludeItemInProfile(max_aggregation_count, options.method_threshold, metadata, options); |
| } |
| |
| // Returns true iff a class with the given metada should be included in the profile. |
| static bool IncludeClassInProfile(const TypeReference& type_ref, |
| uint32_t max_aggregation_count, |
| const FlattenProfileData::ItemMetadata& metadata, |
| const BootImageOptions& options) { |
| uint32_t threshold = MaybeIsClassClean(type_ref) |
| ? options.image_class_clean_threshold |
| : options.image_class_threshold; |
| return IncludeItemInProfile(max_aggregation_count, threshold, metadata, options); |
| } |
| |
| // Returns true iff a class with the given metada should be included in the list of |
| // prelaoded classes. |
| static bool IncludeInPreloadedClasses(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<std::unique_ptr<const DexFile>>& dex_files, |
| const std::vector<std::string>& 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<FlattenProfileData> 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<FlattenProfileData> 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<std::string, FlattenProfileData::ItemMetadata> profile_methods; |
| SafeMap<std::string, FlattenProfileData::ItemMetadata> profile_classes; |
| SafeMap<std::string, FlattenProfileData::ItemMetadata> 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 |