You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
285 lines
11 KiB
285 lines
11 KiB
/*
|
|
* 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
|