blob: fc20f20a65ed0d05d28bd5cf3c88642f130dd904 [file] [log] [blame]
/*
* 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