Mathieu Chartier | 2f79455 | 2017-06-19 10:58:08 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2017 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
Calin Juravle | 0f7f4fc | 2020-05-11 20:16:50 -0700 | [diff] [blame] | 17 | #include "boot_image_profile.h" |
| 18 | |
Mathieu Chartier | 2f79455 | 2017-06-19 10:58:08 -0700 | [diff] [blame] | 19 | #include <memory> |
| 20 | #include <set> |
| 21 | |
Calin Juravle | 0f7f4fc | 2020-05-11 20:16:50 -0700 | [diff] [blame] | 22 | #include "android-base/file.h" |
| 23 | #include "base/unix_file/fd_file.h" |
Mathieu Chartier | 20f4992 | 2018-05-24 16:04:17 -0700 | [diff] [blame] | 24 | #include "dex/class_accessor-inl.h" |
Calin Juravle | 0f7f4fc | 2020-05-11 20:16:50 -0700 | [diff] [blame] | 25 | #include "dex/descriptors_names.h" |
David Sehr | 9e734c7 | 2018-01-04 17:56:19 -0800 | [diff] [blame] | 26 | #include "dex/dex_file-inl.h" |
David Sehr | 312f3b2 | 2018-03-19 08:39:26 -0700 | [diff] [blame] | 27 | #include "dex/method_reference.h" |
| 28 | #include "dex/type_reference.h" |
David Sehr | 82d046e | 2018-04-23 08:14:19 -0700 | [diff] [blame] | 29 | #include "profile/profile_compilation_info.h" |
Mathieu Chartier | 2f79455 | 2017-06-19 10:58:08 -0700 | [diff] [blame] | 30 | |
| 31 | namespace art { |
| 32 | |
| 33 | using Hotness = ProfileCompilationInfo::MethodHotness; |
| 34 | |
Calin Juravle | 0f7f4fc | 2020-05-11 20:16:50 -0700 | [diff] [blame] | 35 | static const std::string kMethodSep = "->"; // NOLINT [runtime/string] [4] |
| 36 | static const std::string kPackageUseDelim = "@"; // NOLINT [runtime/string] [4] |
| 37 | static constexpr char kMethodFlagStringHot = 'H'; |
| 38 | static constexpr char kMethodFlagStringStartup = 'S'; |
| 39 | static constexpr char kMethodFlagStringPostStartup = 'P'; |
| 40 | |
| 41 | // Returns the type descriptor of the given reference. |
| 42 | static std::string GetTypeDescriptor(const TypeReference& ref) { |
| 43 | const dex::TypeId& type_id = ref.dex_file->GetTypeId(ref.TypeIndex()); |
| 44 | return ref.dex_file->GetTypeDescriptor(type_id); |
| 45 | } |
| 46 | |
| 47 | // Returns the method representation used in the text format of the boot image profile. |
| 48 | static std::string BootImageRepresentation(const MethodReference& ref) { |
| 49 | const DexFile* dex_file = ref.dex_file; |
| 50 | const dex::MethodId& id = ref.GetMethodId(); |
| 51 | std::string signature_string(dex_file->GetMethodSignature(id).ToString()); |
| 52 | std::string type_string(dex_file->GetTypeDescriptor(dex_file->GetTypeId(id.class_idx_))); |
| 53 | std::string method_name(dex_file->GetMethodName(id)); |
| 54 | return type_string + |
| 55 | kMethodSep + |
| 56 | method_name + |
| 57 | signature_string; |
| 58 | } |
| 59 | |
| 60 | // Returns the class representation used in the text format of the boot image profile. |
| 61 | static std::string BootImageRepresentation(const TypeReference& ref) { |
| 62 | return GetTypeDescriptor(ref); |
| 63 | } |
| 64 | |
| 65 | // Returns the class representation used in preloaded classes. |
| 66 | static std::string PreloadedClassesRepresentation(const TypeReference& ref) { |
| 67 | std::string descriptor = GetTypeDescriptor(ref); |
| 68 | return DescriptorToDot(descriptor.c_str()); |
| 69 | } |
| 70 | |
| 71 | // Formats the list of packages from the item metadata as a debug string. |
| 72 | static std::string GetPackageUseString(const FlattenProfileData::ItemMetadata& metadata) { |
| 73 | std::string result; |
| 74 | for (const auto& it : metadata.GetAnnotations()) { |
| 75 | result += it.GetOriginPackageName() + ","; |
| 76 | } |
| 77 | |
| 78 | return metadata.GetAnnotations().empty() |
| 79 | ? result |
| 80 | : result.substr(0, result.size() - 1); |
| 81 | } |
| 82 | |
| 83 | // Converts a method representation to its final profile format. |
| 84 | static std::string MethodToProfileFormat( |
| 85 | const std::string& method, |
| 86 | const FlattenProfileData::ItemMetadata& metadata, |
| 87 | bool output_package_use) { |
| 88 | std::string flags_string; |
| 89 | if (metadata.HasFlagSet(Hotness::kFlagHot)) { |
| 90 | flags_string += kMethodFlagStringHot; |
| 91 | } |
| 92 | if (metadata.HasFlagSet(Hotness::kFlagStartup)) { |
| 93 | flags_string += kMethodFlagStringStartup; |
| 94 | } |
| 95 | if (metadata.HasFlagSet(Hotness::kFlagPostStartup)) { |
| 96 | flags_string += kMethodFlagStringPostStartup; |
| 97 | } |
| 98 | std::string extra; |
| 99 | if (output_package_use) { |
| 100 | extra = kPackageUseDelim + GetPackageUseString(metadata); |
| 101 | } |
| 102 | |
| 103 | return flags_string + method + extra; |
| 104 | } |
| 105 | |
| 106 | // Converts a class representation to its final profile or preloaded classes format. |
| 107 | static std::string ClassToProfileFormat( |
| 108 | const std::string& classString, |
| 109 | const FlattenProfileData::ItemMetadata& metadata, |
| 110 | bool output_package_use) { |
| 111 | std::string extra; |
| 112 | if (output_package_use) { |
| 113 | extra = kPackageUseDelim + GetPackageUseString(metadata); |
| 114 | } |
| 115 | |
| 116 | return classString + extra; |
| 117 | } |
| 118 | |
| 119 | // Tries to asses if the given type reference is a clean class. |
| 120 | static bool MaybeIsClassClean(const TypeReference& ref) { |
| 121 | const dex::ClassDef* class_def = ref.dex_file->FindClassDef(ref.TypeIndex()); |
| 122 | if (class_def == nullptr) { |
| 123 | return false; |
| 124 | } |
| 125 | |
| 126 | ClassAccessor accessor(*ref.dex_file, *class_def); |
| 127 | for (auto& it : accessor.GetStaticFields()) { |
| 128 | if (!it.IsFinal()) { |
| 129 | // Not final static field will probably dirty the class. |
| 130 | return false; |
| 131 | } |
| 132 | } |
| 133 | for (auto& it : accessor.GetMethods()) { |
| 134 | uint32_t flags = it.GetAccessFlags(); |
| 135 | if ((flags & kAccNative) != 0) { |
| 136 | // Native method will get dirtied. |
| 137 | return false; |
| 138 | } |
| 139 | if ((flags & kAccConstructor) != 0 && (flags & kAccStatic) != 0) { |
| 140 | // Class initializer, may get dirtied (not sure). |
| 141 | return false; |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | return true; |
| 146 | } |
| 147 | |
| 148 | // Returns true iff the item should be included in the profile. |
| 149 | // (i.e. it passes the given aggregation thresholds) |
| 150 | static bool IncludeItemInProfile(uint32_t max_aggregation_count, |
| 151 | uint32_t item_threshold, |
| 152 | const FlattenProfileData::ItemMetadata& metadata, |
| 153 | const BootImageOptions& options) { |
| 154 | CHECK_NE(max_aggregation_count, 0u); |
| 155 | float item_percent = metadata.GetAnnotations().size() / static_cast<float>(max_aggregation_count); |
| 156 | for (const auto& annotIt : metadata.GetAnnotations()) { |
| 157 | const auto&thresholdIt = |
| 158 | options.special_packages_thresholds.find(annotIt.GetOriginPackageName()); |
| 159 | if (thresholdIt != options.special_packages_thresholds.end()) { |
| 160 | if (item_percent >= (thresholdIt->second / 100.f)) { |
| 161 | return true; |
| 162 | } |
| 163 | } |
| 164 | } |
| 165 | return item_percent >= (item_threshold / 100.f); |
| 166 | } |
| 167 | |
| 168 | // Returns true iff a method with the given metada should be included in the profile. |
| 169 | static bool IncludeMethodInProfile(uint32_t max_aggregation_count, |
| 170 | const FlattenProfileData::ItemMetadata& metadata, |
| 171 | const BootImageOptions& options) { |
| 172 | return IncludeItemInProfile(max_aggregation_count, options.method_threshold, metadata, options); |
| 173 | } |
| 174 | |
| 175 | // Returns true iff a class with the given metada should be included in the profile. |
| 176 | static bool IncludeClassInProfile(const TypeReference& type_ref, |
| 177 | uint32_t max_aggregation_count, |
| 178 | const FlattenProfileData::ItemMetadata& metadata, |
| 179 | const BootImageOptions& options) { |
| 180 | uint32_t threshold = MaybeIsClassClean(type_ref) |
| 181 | ? options.image_class_clean_threshold |
| 182 | : options.image_class_threshold; |
| 183 | return IncludeItemInProfile(max_aggregation_count, threshold, metadata, options); |
| 184 | } |
| 185 | |
| 186 | // Returns true iff a class with the given metada should be included in the list of |
| 187 | // prelaoded classes. |
Calin Juravle | 48030c4 | 2020-06-17 19:16:01 -0700 | [diff] [blame] | 188 | static bool IncludeInPreloadedClasses(const std::string& class_name, |
| 189 | uint32_t max_aggregation_count, |
Calin Juravle | 0f7f4fc | 2020-05-11 20:16:50 -0700 | [diff] [blame] | 190 | const FlattenProfileData::ItemMetadata& metadata, |
| 191 | const BootImageOptions& options) { |
Orion Hodson | 3e8caeb | 2020-08-07 15:41:24 +0100 | [diff] [blame] | 192 | bool denylisted = options.preloaded_classes_denylist.find(class_name) != |
| 193 | options.preloaded_classes_denylist.end(); |
| 194 | return !denylisted && IncludeItemInProfile( |
Calin Juravle | 0f7f4fc | 2020-05-11 20:16:50 -0700 | [diff] [blame] | 195 | max_aggregation_count, options.preloaded_class_threshold, metadata, options); |
| 196 | } |
| 197 | |
| 198 | bool GenerateBootImageProfile( |
Mathieu Chartier | 2f79455 | 2017-06-19 10:58:08 -0700 | [diff] [blame] | 199 | const std::vector<std::unique_ptr<const DexFile>>& dex_files, |
Calin Juravle | 0e02d61 | 2020-06-18 18:05:29 -0700 | [diff] [blame] | 200 | const std::vector<std::string>& profile_files, |
Mathieu Chartier | 2f79455 | 2017-06-19 10:58:08 -0700 | [diff] [blame] | 201 | const BootImageOptions& options, |
Calin Juravle | 0f7f4fc | 2020-05-11 20:16:50 -0700 | [diff] [blame] | 202 | const std::string& boot_profile_out_path, |
| 203 | const std::string& preloaded_classes_out_path) { |
Calin Juravle | d4e6992 | 2020-05-18 18:32:22 -0700 | [diff] [blame] | 204 | if (boot_profile_out_path.empty()) { |
| 205 | LOG(ERROR) << "No output file specified"; |
| 206 | return false; |
| 207 | } |
| 208 | |
| 209 | bool generate_preloaded_classes = !preloaded_classes_out_path.empty(); |
| 210 | |
Calin Juravle | 0e02d61 | 2020-06-18 18:05:29 -0700 | [diff] [blame] | 211 | std::unique_ptr<FlattenProfileData> flattend_data(new FlattenProfileData()); |
| 212 | for (const std::string& profile_file : profile_files) { |
Vladimir Marko | c63d967 | 2021-03-31 15:50:39 +0100 | [diff] [blame] | 213 | ProfileCompilationInfo profile(/*for_boot_image=*/ true); |
Calin Juravle | 0e02d61 | 2020-06-18 18:05:29 -0700 | [diff] [blame] | 214 | if (!profile.Load(profile_file, /*clear_if_invalid=*/ false)) { |
| 215 | LOG(ERROR) << "Profile is not a valid: " << profile_file; |
| 216 | return false; |
| 217 | } |
| 218 | std::unique_ptr<FlattenProfileData> currentData = profile.ExtractProfileData(dex_files); |
| 219 | flattend_data->MergeData(*currentData); |
| 220 | } |
Mathieu Chartier | 2f79455 | 2017-06-19 10:58:08 -0700 | [diff] [blame] | 221 | |
Calin Juravle | 0f7f4fc | 2020-05-11 20:16:50 -0700 | [diff] [blame] | 222 | // We want the output sorted by the method/class name. |
| 223 | // So we use an intermediate map for that. |
| 224 | // There's no attempt to optimize this as it's not part of any critical path, |
| 225 | // and mostly executed on hosts. |
| 226 | SafeMap<std::string, FlattenProfileData::ItemMetadata> profile_methods; |
| 227 | SafeMap<std::string, FlattenProfileData::ItemMetadata> profile_classes; |
| 228 | SafeMap<std::string, FlattenProfileData::ItemMetadata> preloaded_classes; |
Mathieu Chartier | 2f79455 | 2017-06-19 10:58:08 -0700 | [diff] [blame] | 229 | |
Calin Juravle | 0e02d61 | 2020-06-18 18:05:29 -0700 | [diff] [blame] | 230 | for (const auto& it : flattend_data->GetMethodData()) { |
| 231 | if (IncludeMethodInProfile(flattend_data->GetMaxAggregationForMethods(), it.second, options)) { |
Calin Juravle | 0f7f4fc | 2020-05-11 20:16:50 -0700 | [diff] [blame] | 232 | FlattenProfileData::ItemMetadata metadata(it.second); |
| 233 | if (options.upgrade_startup_to_hot |
| 234 | && ((metadata.GetFlags() & Hotness::Flag::kFlagStartup) != 0)) { |
| 235 | metadata.AddFlag(Hotness::Flag::kFlagHot); |
Mathieu Chartier | 2f79455 | 2017-06-19 10:58:08 -0700 | [diff] [blame] | 236 | } |
Calin Juravle | 0f7f4fc | 2020-05-11 20:16:50 -0700 | [diff] [blame] | 237 | profile_methods.Put(BootImageRepresentation(it.first), metadata); |
Mathieu Chartier | 2f79455 | 2017-06-19 10:58:08 -0700 | [diff] [blame] | 238 | } |
| 239 | } |
Calin Juravle | 0f7f4fc | 2020-05-11 20:16:50 -0700 | [diff] [blame] | 240 | |
Calin Juravle | 0e02d61 | 2020-06-18 18:05:29 -0700 | [diff] [blame] | 241 | for (const auto& it : flattend_data->GetClassData()) { |
Calin Juravle | 0f7f4fc | 2020-05-11 20:16:50 -0700 | [diff] [blame] | 242 | const TypeReference& type_ref = it.first; |
| 243 | const FlattenProfileData::ItemMetadata& metadata = it.second; |
| 244 | if (IncludeClassInProfile(type_ref, |
Calin Juravle | 0e02d61 | 2020-06-18 18:05:29 -0700 | [diff] [blame] | 245 | flattend_data->GetMaxAggregationForClasses(), |
Calin Juravle | 0f7f4fc | 2020-05-11 20:16:50 -0700 | [diff] [blame] | 246 | metadata, |
| 247 | options)) { |
| 248 | profile_classes.Put(BootImageRepresentation(it.first), it.second); |
| 249 | } |
Calin Juravle | 48030c4 | 2020-06-17 19:16:01 -0700 | [diff] [blame] | 250 | std::string preloaded_class_representation = PreloadedClassesRepresentation(it.first); |
Calin Juravle | d4e6992 | 2020-05-18 18:32:22 -0700 | [diff] [blame] | 251 | if (generate_preloaded_classes && IncludeInPreloadedClasses( |
Calin Juravle | 48030c4 | 2020-06-17 19:16:01 -0700 | [diff] [blame] | 252 | preloaded_class_representation, |
Calin Juravle | 0e02d61 | 2020-06-18 18:05:29 -0700 | [diff] [blame] | 253 | flattend_data->GetMaxAggregationForClasses(), |
Calin Juravle | 0f7f4fc | 2020-05-11 20:16:50 -0700 | [diff] [blame] | 254 | metadata, |
| 255 | options)) { |
Calin Juravle | 48030c4 | 2020-06-17 19:16:01 -0700 | [diff] [blame] | 256 | preloaded_classes.Put(preloaded_class_representation, it.second); |
Calin Juravle | 0f7f4fc | 2020-05-11 20:16:50 -0700 | [diff] [blame] | 257 | } |
Mathieu Chartier | 2f79455 | 2017-06-19 10:58:08 -0700 | [diff] [blame] | 258 | } |
Calin Juravle | 0f7f4fc | 2020-05-11 20:16:50 -0700 | [diff] [blame] | 259 | |
| 260 | // Create the output content |
| 261 | std::string profile_content; |
| 262 | std::string preloaded_content; |
| 263 | for (const auto& it : profile_classes) { |
| 264 | profile_content += ClassToProfileFormat(it.first, it.second, options.append_package_use_list) |
| 265 | + "\n"; |
| 266 | } |
| 267 | for (const auto& it : profile_methods) { |
| 268 | profile_content += MethodToProfileFormat(it.first, it.second, options.append_package_use_list) |
| 269 | + "\n"; |
| 270 | } |
Calin Juravle | d4e6992 | 2020-05-18 18:32:22 -0700 | [diff] [blame] | 271 | |
| 272 | if (generate_preloaded_classes) { |
| 273 | for (const auto& it : preloaded_classes) { |
| 274 | preloaded_content += |
| 275 | ClassToProfileFormat(it.first, it.second, options.append_package_use_list) + "\n"; |
| 276 | } |
Calin Juravle | 0f7f4fc | 2020-05-11 20:16:50 -0700 | [diff] [blame] | 277 | } |
| 278 | |
| 279 | return android::base::WriteStringToFile(profile_content, boot_profile_out_path) |
Calin Juravle | d4e6992 | 2020-05-18 18:32:22 -0700 | [diff] [blame] | 280 | && (!generate_preloaded_classes |
| 281 | || android::base::WriteStringToFile(preloaded_content, preloaded_classes_out_path)); |
Mathieu Chartier | 2f79455 | 2017-06-19 10:58:08 -0700 | [diff] [blame] | 282 | } |
| 283 | |
| 284 | } // namespace art |