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