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
17 #include "boot_image_profile.h"
18
19 #include <memory>
20 #include <set>
21
22 #include "android-base/file.h"
23 #include "base/unix_file/fd_file.h"
24 #include "dex/class_accessor-inl.h"
25 #include "dex/descriptors_names.h"
26 #include "dex/dex_file-inl.h"
27 #include "dex/method_reference.h"
28 #include "dex/type_reference.h"
29 #include "profile/profile_compilation_info.h"
30
31 namespace art {
32
33 using Hotness = ProfileCompilationInfo::MethodHotness;
34
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.
GetTypeDescriptor(const TypeReference & ref)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.
BootImageRepresentation(const MethodReference & ref)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.
BootImageRepresentation(const TypeReference & ref)61 static std::string BootImageRepresentation(const TypeReference& ref) {
62 return GetTypeDescriptor(ref);
63 }
64
65 // Returns the class representation used in preloaded classes.
PreloadedClassesRepresentation(const TypeReference & ref)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.
GetPackageUseString(const FlattenProfileData::ItemMetadata & metadata)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.
MethodToProfileFormat(const std::string & method,const FlattenProfileData::ItemMetadata & metadata,bool output_package_use)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.
ClassToProfileFormat(const std::string & classString,const FlattenProfileData::ItemMetadata & metadata,bool output_package_use)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.
MaybeIsClassClean(const TypeReference & ref)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)
IncludeItemInProfile(uint32_t max_aggregation_count,uint32_t item_threshold,const FlattenProfileData::ItemMetadata & metadata,const BootImageOptions & options)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.
IncludeMethodInProfile(uint32_t max_aggregation_count,const FlattenProfileData::ItemMetadata & metadata,const BootImageOptions & options)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.
IncludeClassInProfile(const TypeReference & type_ref,uint32_t max_aggregation_count,const FlattenProfileData::ItemMetadata & metadata,const BootImageOptions & options)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.
IncludeInPreloadedClasses(const std::string & class_name,uint32_t max_aggregation_count,const FlattenProfileData::ItemMetadata & metadata,const BootImageOptions & options)188 static bool IncludeInPreloadedClasses(const std::string& class_name,
189 uint32_t max_aggregation_count,
190 const FlattenProfileData::ItemMetadata& metadata,
191 const BootImageOptions& options) {
192 bool blacklisted = options.preloaded_classes_blacklist.find(class_name) !=
193 options.preloaded_classes_blacklist.end();
194 return !blacklisted && IncludeItemInProfile(
195 max_aggregation_count, options.preloaded_class_threshold, metadata, options);
196 }
197
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)198 bool GenerateBootImageProfile(
199 const std::vector<std::unique_ptr<const DexFile>>& dex_files,
200 const std::vector<std::string>& profile_files,
201 const BootImageOptions& options,
202 const std::string& boot_profile_out_path,
203 const std::string& preloaded_classes_out_path) {
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
211 std::unique_ptr<FlattenProfileData> flattend_data(new FlattenProfileData());
212 for (const std::string& profile_file : profile_files) {
213 ProfileCompilationInfo profile;
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 }
221
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;
229
230 for (const auto& it : flattend_data->GetMethodData()) {
231 if (IncludeMethodInProfile(flattend_data->GetMaxAggregationForMethods(), it.second, options)) {
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);
236 }
237 profile_methods.Put(BootImageRepresentation(it.first), metadata);
238 }
239 }
240
241 for (const auto& it : flattend_data->GetClassData()) {
242 const TypeReference& type_ref = it.first;
243 const FlattenProfileData::ItemMetadata& metadata = it.second;
244 if (IncludeClassInProfile(type_ref,
245 flattend_data->GetMaxAggregationForClasses(),
246 metadata,
247 options)) {
248 profile_classes.Put(BootImageRepresentation(it.first), it.second);
249 }
250 std::string preloaded_class_representation = PreloadedClassesRepresentation(it.first);
251 if (generate_preloaded_classes && IncludeInPreloadedClasses(
252 preloaded_class_representation,
253 flattend_data->GetMaxAggregationForClasses(),
254 metadata,
255 options)) {
256 preloaded_classes.Put(preloaded_class_representation, it.second);
257 }
258 }
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 }
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 }
277 }
278
279 return android::base::WriteStringToFile(profile_content, boot_profile_out_path)
280 && (!generate_preloaded_classes
281 || android::base::WriteStringToFile(preloaded_content, preloaded_classes_out_path));
282 }
283
284 } // namespace art
285