diff options
author | 2016-01-05 14:29:29 +0000 | |
---|---|---|
committer | 2016-01-19 11:17:24 -0800 | |
commit | 877fd963548a3175665bfef25b0d24bc0e5a0135 (patch) | |
tree | db4cae18266f7cd9415a362c21d50fef93a8488f | |
parent | 37a5abcf5e7644ae1fd1a85e865c8a71e38a9af2 (diff) |
Improve profile processing
- allow file descriptors in addition to file names for profiles
- fix some minor issues (wrong comparison signs, unhandled errors)
- added gtests for profile_compilation_info, profile_assistant
and compiler_driver
Bug: 26080105
Change-Id: I136039fa1f25858399000049e48b01eafae54eb1
-rw-r--r-- | build/Android.gtest.mk | 7 | ||||
-rw-r--r-- | compiler/common_compiler_test.cc | 8 | ||||
-rw-r--r-- | compiler/common_compiler_test.h | 3 | ||||
-rw-r--r-- | compiler/driver/compiler_driver_test.cc | 89 | ||||
-rw-r--r-- | compiler/profile_assistant.cc | 128 | ||||
-rw-r--r-- | compiler/profile_assistant.h | 11 | ||||
-rw-r--r-- | compiler/profile_assistant_test.cc | 279 | ||||
-rw-r--r-- | dex2oat/dex2oat.cc | 62 | ||||
-rw-r--r-- | runtime/base/scoped_flock.cc | 31 | ||||
-rw-r--r-- | runtime/base/scoped_flock.h | 15 | ||||
-rw-r--r-- | runtime/base/unix_file/fd_file.cc | 17 | ||||
-rw-r--r-- | runtime/base/unix_file/fd_file.h | 5 | ||||
-rw-r--r-- | runtime/jit/offline_profiling_info.cc | 125 | ||||
-rw-r--r-- | runtime/jit/offline_profiling_info.h | 22 | ||||
-rw-r--r-- | runtime/jit/profile_compilation_info_test.cc | 166 | ||||
-rw-r--r-- | runtime/jit/profile_saver.cc | 8 | ||||
-rw-r--r-- | test/ProfileTestMultiDex/Main.java | 27 | ||||
-rw-r--r-- | test/ProfileTestMultiDex/Second.java | 27 | ||||
-rw-r--r-- | test/ProfileTestMultiDex/main.jpp | 3 | ||||
-rw-r--r-- | test/ProfileTestMultiDex/main.list | 1 |
20 files changed, 913 insertions, 121 deletions
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index 3d16c49fe4..2fec791af3 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -39,6 +39,7 @@ GTEST_DEX_DIRECTORIES := \ NonStaticLeafMethods \ ProtoCompare \ ProtoCompare2 \ + ProfileTestMultiDex \ StaticLeafMethods \ Statics \ StaticsFromCode \ @@ -65,7 +66,7 @@ $(ART_TEST_TARGET_GTEST_MainStripped_DEX): $(ART_TEST_TARGET_GTEST_Main_DEX) # Dex file dependencies for each gtest. ART_GTEST_class_linker_test_DEX_DEPS := Interfaces MultiDex MyClass Nested Statics StaticsFromCode -ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod StaticLeafMethods +ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod StaticLeafMethods ProfileTestMultiDex ART_GTEST_dex_cache_test_DEX_DEPS := Main ART_GTEST_dex_file_test_DEX_DEPS := GetMethodSignature Main Nested ART_GTEST_exception_test_DEX_DEPS := ExceptionHandle @@ -78,6 +79,8 @@ ART_GTEST_oat_test_DEX_DEPS := Main ART_GTEST_object_test_DEX_DEPS := ProtoCompare ProtoCompare2 StaticsFromCode XandY ART_GTEST_proxy_test_DEX_DEPS := Interfaces ART_GTEST_reflection_test_DEX_DEPS := Main NonStaticLeafMethods StaticLeafMethods +ART_GTEST_profile_assistant_test_DEX_DEPS := ProfileTestMultiDex +ART_GTEST_profile_compilation_info_test_DEX_DEPS := ProfileTestMultiDex ART_GTEST_stub_test_DEX_DEPS := AllFields ART_GTEST_transaction_test_DEX_DEPS := Transaction ART_GTEST_type_lookup_table_test_DEX_DEPS := Lookup @@ -208,6 +211,7 @@ RUNTIME_GTEST_COMMON_SRC_FILES := \ runtime/interpreter/safe_math_test.cc \ runtime/interpreter/unstarted_runtime_test.cc \ runtime/java_vm_ext_test.cc \ + runtime/jit/profile_compilation_info_test.cc \ runtime/lambda/closure_test.cc \ runtime/lambda/shorty_field_type_test.cc \ runtime/leb128_test.cc \ @@ -267,6 +271,7 @@ COMPILER_GTEST_COMMON_SRC_FILES := \ compiler/optimizing/ssa_test.cc \ compiler/optimizing/stack_map_test.cc \ compiler/optimizing/suspend_check_test.cc \ + compiler/profile_assistant_test.cc \ compiler/utils/arena_allocator_test.cc \ compiler/utils/dedupe_set_test.cc \ compiler/utils/swap_space_test.cc \ diff --git a/compiler/common_compiler_test.cc b/compiler/common_compiler_test.cc index b5fd1e074f..9e69312316 100644 --- a/compiler/common_compiler_test.cc +++ b/compiler/common_compiler_test.cc @@ -168,6 +168,12 @@ std::unordered_set<std::string>* CommonCompilerTest::GetCompiledMethods() { return nullptr; } +// Get ProfileCompilationInfo that should be passed to the driver. +ProfileCompilationInfo* CommonCompilerTest::GetProfileCompilationInfo() { + // Null, profile information will not be taken into account. + return nullptr; +} + void CommonCompilerTest::SetUp() { CommonRuntimeTest::SetUp(); { @@ -209,7 +215,7 @@ void CommonCompilerTest::CreateCompilerDriver(Compiler::Kind kind, InstructionSe timer_.get(), -1, /* dex_to_oat_map */ nullptr, - /* profile_compilation_info */ nullptr)); + GetProfileCompilationInfo())); // We typically don't generate an image in unit tests, disable this optimization by default. compiler_driver_->SetSupportBootImageFixup(false); } diff --git a/compiler/common_compiler_test.h b/compiler/common_compiler_test.h index b491946dc3..7e0fbabff8 100644 --- a/compiler/common_compiler_test.h +++ b/compiler/common_compiler_test.h @@ -23,6 +23,7 @@ #include "common_runtime_test.h" #include "compiler.h" +#include "jit/offline_profiling_info.h" #include "oat_file.h" namespace art { @@ -75,6 +76,8 @@ class CommonCompilerTest : public CommonRuntimeTest { // driver assumes ownership of the set, so the test should properly release the set. virtual std::unordered_set<std::string>* GetCompiledMethods(); + virtual ProfileCompilationInfo* GetProfileCompilationInfo(); + virtual void TearDown(); void CompileClass(mirror::ClassLoader* class_loader, const char* class_name) diff --git a/compiler/driver/compiler_driver_test.cc b/compiler/driver/compiler_driver_test.cc index 82c0e86b25..4c03e5ddfe 100644 --- a/compiler/driver/compiler_driver_test.cc +++ b/compiler/driver/compiler_driver_test.cc @@ -31,6 +31,7 @@ #include "mirror/object_array-inl.h" #include "mirror/object-inl.h" #include "handle_scope-inl.h" +#include "jit/offline_profiling_info.h" #include "scoped_thread_state_change.h" namespace art { @@ -240,6 +241,94 @@ TEST_F(CompilerDriverMethodsTest, Selection) { EXPECT_TRUE(expected->empty()); } +class CompilerDriverProfileTest : public CompilerDriverTest { + protected: + ProfileCompilationInfo* GetProfileCompilationInfo() OVERRIDE { + ScopedObjectAccess soa(Thread::Current()); + std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("ProfileTestMultiDex"); + + ProfileCompilationInfo info; + for (const std::unique_ptr<const DexFile>& dex_file : dex_files) { + std::cout << std::string(dex_file->GetLocation()); + profile_info_.AddData(dex_file->GetLocation(), dex_file->GetLocationChecksum(), 1); + profile_info_.AddData(dex_file->GetLocation(), dex_file->GetLocationChecksum(), 2); + } + return &profile_info_; + } + + std::unordered_set<std::string> GetExpectedMethodsForClass(const std::string& clazz) { + if (clazz == "Main") { + return std::unordered_set<std::string>({ + "java.lang.String Main.getA()", + "java.lang.String Main.getB()"}); + } else if (clazz == "Second") { + return std::unordered_set<std::string>({ + "java.lang.String Second.getX()", + "java.lang.String Second.getY()"}); + } else { + return std::unordered_set<std::string>(); + } + } + + void CheckCompiledMethods(jobject class_loader, + const std::string& clazz, + const std::unordered_set<std::string>& expected_methods) { + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + StackHandleScope<1> hs(self); + Handle<mirror::ClassLoader> h_loader(hs.NewHandle( + reinterpret_cast<mirror::ClassLoader*>(self->DecodeJObject(class_loader)))); + mirror::Class* klass = class_linker->FindClass(self, clazz.c_str(), h_loader); + ASSERT_NE(klass, nullptr); + + const auto pointer_size = class_linker->GetImagePointerSize(); + size_t number_of_compiled_methods = 0; + for (auto& m : klass->GetVirtualMethods(pointer_size)) { + std::string name = PrettyMethod(&m, true); + const void* code = m.GetEntryPointFromQuickCompiledCodePtrSize(pointer_size); + ASSERT_NE(code, nullptr); + if (expected_methods.find(name) != expected_methods.end()) { + number_of_compiled_methods++; + EXPECT_FALSE(class_linker->IsQuickToInterpreterBridge(code)); + } else { + EXPECT_TRUE(class_linker->IsQuickToInterpreterBridge(code)); + } + } + EXPECT_EQ(expected_methods.size(), number_of_compiled_methods); + } + + private: + ProfileCompilationInfo profile_info_; +}; + +TEST_F(CompilerDriverProfileTest, ProfileGuidedCompilation) { + TEST_DISABLED_FOR_HEAP_REFERENCE_POISONING_WITH_QUICK(); + TEST_DISABLED_FOR_READ_BARRIER_WITH_QUICK(); + TEST_DISABLED_FOR_READ_BARRIER_WITH_OPTIMIZING_FOR_UNSUPPORTED_INSTRUCTION_SETS(); + Thread* self = Thread::Current(); + jobject class_loader; + { + ScopedObjectAccess soa(self); + class_loader = LoadDex("ProfileTestMultiDex"); + } + ASSERT_NE(class_loader, nullptr); + + // Need to enable dex-file writability. Methods rejected to be compiled will run through the + // dex-to-dex compiler. + ProfileCompilationInfo info; + for (const DexFile* dex_file : GetDexFiles(class_loader)) { + ASSERT_TRUE(dex_file->EnableWrite()); + } + + CompileAll(class_loader); + + std::unordered_set<std::string> m = GetExpectedMethodsForClass("Main"); + std::unordered_set<std::string> s = GetExpectedMethodsForClass("Second"); + CheckCompiledMethods(class_loader, "LMain;", m); + CheckCompiledMethods(class_loader, "LSecond;", s); +} + // TODO: need check-cast test (when stub complete & we can throw/catch } // namespace art diff --git a/compiler/profile_assistant.cc b/compiler/profile_assistant.cc index 81f2a5692d..0871722440 100644 --- a/compiler/profile_assistant.cc +++ b/compiler/profile_assistant.cc @@ -16,54 +16,150 @@ #include "profile_assistant.h" +#include "base/unix_file/fd_file.h" +#include "os.h" + namespace art { // Minimum number of new methods that profiles must contain to enable recompilation. static constexpr const uint32_t kMinNewMethodsForCompilation = 10; -bool ProfileAssistant::ProcessProfiles( - const std::vector<std::string>& profile_files, - const std::vector<std::string>& reference_profile_files, - /*out*/ ProfileCompilationInfo** profile_compilation_info) { +bool ProfileAssistant::ProcessProfilesInternal( + const std::vector<ScopedFlock>& profile_files, + const std::vector<ScopedFlock>& reference_profile_files, + /*out*/ ProfileCompilationInfo** profile_compilation_info) { DCHECK(!profile_files.empty()); - DCHECK(reference_profile_files.empty() || + DCHECK(!reference_profile_files.empty() || (profile_files.size() == reference_profile_files.size())); - std::vector<ProfileCompilationInfo> new_info(profile_files.size()); bool should_compile = false; // Read the main profile files. - for (size_t i = 0; i < profile_files.size(); i++) { - if (!new_info[i].Load(profile_files[i])) { - LOG(WARNING) << "Could not load profile file: " << profile_files[i]; + for (size_t i = 0; i < new_info.size(); i++) { + if (!new_info[i].Load(profile_files[i].GetFile()->Fd())) { + LOG(WARNING) << "Could not load profile file at index " << i; return false; } // Do we have enough new profiled methods that will make the compilation worthwhile? should_compile |= (new_info[i].GetNumberOfMethods() > kMinNewMethodsForCompilation); } + if (!should_compile) { *profile_compilation_info = nullptr; return true; } std::unique_ptr<ProfileCompilationInfo> result(new ProfileCompilationInfo()); + // Merge information. for (size_t i = 0; i < new_info.size(); i++) { + if (!reference_profile_files.empty()) { + if (!new_info[i].Load(reference_profile_files[i].GetFile()->Fd())) { + LOG(WARNING) << "Could not load reference profile file at index " << i; + return false; + } + } // Merge all data into a single object. - result->Load(new_info[i]); - // If we have any reference profile information merge their information with - // the current profiles and save them back to disk. + if (!result->Load(new_info[i])) { + LOG(WARNING) << "Could not merge profile data at index " << i; + return false; + } + } + // We were successful in merging all profile information. Update the files. + for (size_t i = 0; i < new_info.size(); i++) { if (!reference_profile_files.empty()) { - if (!new_info[i].Load(reference_profile_files[i])) { - LOG(WARNING) << "Could not load reference profile file: " << reference_profile_files[i]; + if (!reference_profile_files[i].GetFile()->ClearContent()) { + PLOG(WARNING) << "Could not clear reference profile file at index " << i; + return false; + } + if (!new_info[i].Save(reference_profile_files[i].GetFile()->Fd())) { + LOG(WARNING) << "Could not save reference profile file at index " << i; return false; } - if (!new_info[i].Save(reference_profile_files[i])) { - LOG(WARNING) << "Could not save reference profile file: " << reference_profile_files[i]; + if (!profile_files[i].GetFile()->ClearContent()) { + PLOG(WARNING) << "Could not clear profile file at index " << i; return false; } } } + *profile_compilation_info = result.release(); return true; } +class ScopedCollectionFlock { + public: + explicit ScopedCollectionFlock(size_t size) : flocks_(size) {} + + // Will block until all the locks are acquired. + bool Init(const std::vector<std::string>& filenames, /* out */ std::string* error) { + for (size_t i = 0; i < filenames.size(); i++) { + if (!flocks_[i].Init(filenames[i].c_str(), O_RDWR, /* block */ true, error)) { + *error += " (index=" + std::to_string(i) + ")"; + return false; + } + } + return true; + } + + // Will block until all the locks are acquired. + bool Init(const std::vector<uint32_t>& fds, /* out */ std::string* error) { + for (size_t i = 0; i < fds.size(); i++) { + // We do not own the descriptor, so disable auto-close and don't check usage. + File file(fds[i], false); + file.DisableAutoClose(); + if (!flocks_[i].Init(&file, error)) { + *error += " (index=" + std::to_string(i) + ")"; + return false; + } + } + return true; + } + + const std::vector<ScopedFlock>& Get() const { return flocks_; } + + private: + std::vector<ScopedFlock> flocks_; +}; + +bool ProfileAssistant::ProcessProfiles( + const std::vector<uint32_t>& profile_files_fd, + const std::vector<uint32_t>& reference_profile_files_fd, + /*out*/ ProfileCompilationInfo** profile_compilation_info) { + std::string error; + ScopedCollectionFlock profile_files_flocks(profile_files_fd.size()); + if (!profile_files_flocks.Init(profile_files_fd, &error)) { + LOG(WARNING) << "Could not lock profile files: " << error; + return false; + } + ScopedCollectionFlock reference_profile_files_flocks(reference_profile_files_fd.size()); + if (!reference_profile_files_flocks.Init(reference_profile_files_fd, &error)) { + LOG(WARNING) << "Could not lock reference profile files: " << error; + return false; + } + + return ProcessProfilesInternal(profile_files_flocks.Get(), + reference_profile_files_flocks.Get(), + profile_compilation_info); +} + +bool ProfileAssistant::ProcessProfiles( + const std::vector<std::string>& profile_files, + const std::vector<std::string>& reference_profile_files, + /*out*/ ProfileCompilationInfo** profile_compilation_info) { + std::string error; + ScopedCollectionFlock profile_files_flocks(profile_files.size()); + if (!profile_files_flocks.Init(profile_files, &error)) { + LOG(WARNING) << "Could not lock profile files: " << error; + return false; + } + ScopedCollectionFlock reference_profile_files_flocks(reference_profile_files.size()); + if (!reference_profile_files_flocks.Init(reference_profile_files, &error)) { + LOG(WARNING) << "Could not lock reference profile files: " << error; + return false; + } + + return ProcessProfilesInternal(profile_files_flocks.Get(), + reference_profile_files_flocks.Get(), + profile_compilation_info); +} + } // namespace art diff --git a/compiler/profile_assistant.h b/compiler/profile_assistant.h index 088c8bd1c7..ad5e2163cf 100644 --- a/compiler/profile_assistant.h +++ b/compiler/profile_assistant.h @@ -20,6 +20,7 @@ #include <string> #include <vector> +#include "base/scoped_flock.h" #include "jit/offline_profiling_info.cc" namespace art { @@ -52,7 +53,17 @@ class ProfileAssistant { const std::vector<std::string>& reference_profile_files, /*out*/ ProfileCompilationInfo** profile_compilation_info); + static bool ProcessProfiles( + const std::vector<uint32_t>& profile_files_fd_, + const std::vector<uint32_t>& reference_profile_files_fd_, + /*out*/ ProfileCompilationInfo** profile_compilation_info); + private: + static bool ProcessProfilesInternal( + const std::vector<ScopedFlock>& profile_files, + const std::vector<ScopedFlock>& reference_profile_files, + /*out*/ ProfileCompilationInfo** profile_compilation_info); + DISALLOW_COPY_AND_ASSIGN(ProfileAssistant); }; diff --git a/compiler/profile_assistant_test.cc b/compiler/profile_assistant_test.cc new file mode 100644 index 0000000000..cecb865f82 --- /dev/null +++ b/compiler/profile_assistant_test.cc @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2016 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 <gtest/gtest.h> + +#include "base/unix_file/fd_file.h" +#include "common_runtime_test.h" +#include "compiler/profile_assistant.h" +#include "jit/offline_profiling_info.h" + +namespace art { + +class ProfileAssistantTest : public CommonRuntimeTest { + protected: + void SetupProfile(const std::string& id, + uint32_t checksum, + uint16_t number_of_methods, + const ScratchFile& profile, + ProfileCompilationInfo* info, + uint16_t start_method_index = 0) { + std::string dex_location1 = "location1" + id; + uint32_t dex_location_checksum1 = checksum; + std::string dex_location2 = "location2" + id; + uint32_t dex_location_checksum2 = 10 * checksum; + for (uint16_t i = start_method_index; i < start_method_index + number_of_methods; i++) { + ASSERT_TRUE(info->AddData(dex_location1, dex_location_checksum1, i)); + ASSERT_TRUE(info->AddData(dex_location2, dex_location_checksum2, i)); + } + ASSERT_TRUE(info->Save(GetFd(profile))); + ASSERT_EQ(0, profile.GetFile()->Flush()); + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + } + + uint32_t GetFd(const ScratchFile& file) const { + return static_cast<uint32_t>(file.GetFd()); + } +}; + +TEST_F(ProfileAssistantTest, AdviseCompilationEmptyReferences) { + ScratchFile profile1; + ScratchFile profile2; + ScratchFile reference_profile1; + ScratchFile reference_profile2; + + std::vector<uint32_t> profile_fds({ + GetFd(profile1), + GetFd(profile2)}); + std::vector<uint32_t> reference_profile_fds({ + GetFd(reference_profile1), + GetFd(reference_profile2)}); + + const uint16_t kNumberOfMethodsToEnableCompilation = 100; + ProfileCompilationInfo info1; + SetupProfile("p1", 1, kNumberOfMethodsToEnableCompilation, profile1, &info1); + ProfileCompilationInfo info2; + SetupProfile("p2", 2, kNumberOfMethodsToEnableCompilation, profile2, &info2); + + // We should advise compilation. + ProfileCompilationInfo* result; + ASSERT_TRUE(ProfileAssistant::ProcessProfiles(profile_fds, reference_profile_fds, &result)); + ASSERT_TRUE(result != nullptr); + + // The resulting compilation info must be equal to the merge of the inputs. + ProfileCompilationInfo expected; + ASSERT_TRUE(expected.Load(info1)); + ASSERT_TRUE(expected.Load(info2)); + ASSERT_TRUE(expected.Equals(*result)); + + // The information from profiles must be transfered to the reference profiles. + ProfileCompilationInfo file_info1; + ASSERT_TRUE(reference_profile1.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info1.Load(GetFd(reference_profile1))); + ASSERT_TRUE(file_info1.Equals(info1)); + + ProfileCompilationInfo file_info2; + ASSERT_TRUE(reference_profile2.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info2.Load(GetFd(reference_profile2))); + ASSERT_TRUE(file_info2.Equals(info2)); + + // Initial profiles must be cleared. + ASSERT_EQ(0, profile1.GetFile()->GetLength()); + ASSERT_EQ(0, profile2.GetFile()->GetLength()); +} + +TEST_F(ProfileAssistantTest, AdviseCompilationNonEmptyReferences) { + ScratchFile profile1; + ScratchFile profile2; + ScratchFile reference_profile1; + ScratchFile reference_profile2; + + std::vector<uint32_t> profile_fds({ + GetFd(profile1), + GetFd(profile2)}); + std::vector<uint32_t> reference_profile_fds({ + GetFd(reference_profile1), + GetFd(reference_profile2)}); + + // The new profile info will contain the methods with indices 0-100. + const uint16_t kNumberOfMethodsToEnableCompilation = 100; + ProfileCompilationInfo info1; + SetupProfile("p1", 1, kNumberOfMethodsToEnableCompilation, profile1, &info1); + ProfileCompilationInfo info2; + SetupProfile("p2", 2, kNumberOfMethodsToEnableCompilation, profile2, &info2); + + + // The reference profile info will contain the methods with indices 50-150. + const uint16_t kNumberOfMethodsAlreadyCompiled = 100; + ProfileCompilationInfo reference_info1; + SetupProfile("p1", 1, kNumberOfMethodsAlreadyCompiled, reference_profile1, + &reference_info1, kNumberOfMethodsToEnableCompilation / 2); + ProfileCompilationInfo reference_info2; + SetupProfile("p2", 2, kNumberOfMethodsAlreadyCompiled, reference_profile2, + &reference_info2, kNumberOfMethodsToEnableCompilation / 2); + + // We should advise compilation. + ProfileCompilationInfo* result; + ASSERT_TRUE(ProfileAssistant::ProcessProfiles(profile_fds, reference_profile_fds, &result)); + ASSERT_TRUE(result != nullptr); + + // The resulting compilation info must be equal to the merge of the inputs + ProfileCompilationInfo expected; + ASSERT_TRUE(expected.Load(info1)); + ASSERT_TRUE(expected.Load(info2)); + ASSERT_TRUE(expected.Load(reference_info1)); + ASSERT_TRUE(expected.Load(reference_info2)); + ASSERT_TRUE(expected.Equals(*result)); + + // The information from profiles must be transfered to the reference profiles. + ProfileCompilationInfo file_info1; + ProfileCompilationInfo merge1; + ASSERT_TRUE(merge1.Load(info1)); + ASSERT_TRUE(merge1.Load(reference_info1)); + ASSERT_TRUE(reference_profile1.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info1.Load(GetFd(reference_profile1))); + ASSERT_TRUE(file_info1.Equals(merge1)); + + ProfileCompilationInfo file_info2; + ProfileCompilationInfo merge2; + ASSERT_TRUE(merge2.Load(info2)); + ASSERT_TRUE(merge2.Load(reference_info2)); + ASSERT_TRUE(reference_profile2.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info2.Load(GetFd(reference_profile2))); + ASSERT_TRUE(file_info2.Equals(merge2)); + + // Initial profiles must be cleared. + ASSERT_EQ(0, profile1.GetFile()->GetLength()); + ASSERT_EQ(0, profile2.GetFile()->GetLength()); +} + +TEST_F(ProfileAssistantTest, DoNotAdviseCompilation) { + ScratchFile profile1; + ScratchFile profile2; + ScratchFile reference_profile1; + ScratchFile reference_profile2; + + std::vector<uint32_t> profile_fds({ + GetFd(profile1), + GetFd(profile2)}); + std::vector<uint32_t> reference_profile_fds({ + GetFd(reference_profile1), + GetFd(reference_profile2)}); + + const uint16_t kNumberOfMethodsToSkipCompilation = 1; + ProfileCompilationInfo info1; + SetupProfile("p1", 1, kNumberOfMethodsToSkipCompilation, profile1, &info1); + ProfileCompilationInfo info2; + SetupProfile("p2", 2, kNumberOfMethodsToSkipCompilation, profile2, &info2); + + // We should not advise compilation. + ProfileCompilationInfo* result; + ASSERT_TRUE(ProfileAssistant::ProcessProfiles(profile_fds, reference_profile_fds, &result)); + ASSERT_TRUE(result == nullptr); + + // The information from profiles must remain the same. + ProfileCompilationInfo file_info1; + ASSERT_TRUE(profile1.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info1.Load(GetFd(profile1))); + ASSERT_TRUE(file_info1.Equals(info1)); + + ProfileCompilationInfo file_info2; + ASSERT_TRUE(profile2.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info2.Load(GetFd(profile2))); + ASSERT_TRUE(file_info2.Equals(info2)); + + // Reference profile files must remain empty. + ASSERT_EQ(0, reference_profile1.GetFile()->GetLength()); + ASSERT_EQ(0, reference_profile2.GetFile()->GetLength()); +} + +TEST_F(ProfileAssistantTest, FailProcessingBecauseOfProfiles) { + ScratchFile profile1; + ScratchFile profile2; + ScratchFile reference_profile1; + ScratchFile reference_profile2; + + std::vector<uint32_t> profile_fds({ + GetFd(profile1), + GetFd(profile2)}); + std::vector<uint32_t> reference_profile_fds({ + GetFd(reference_profile1), + GetFd(reference_profile2)}); + + const uint16_t kNumberOfMethodsToEnableCompilation = 100; + // Assign different hashes for the same dex file. This will make merging of information to fail. + ProfileCompilationInfo info1; + SetupProfile("p1", 1, kNumberOfMethodsToEnableCompilation, profile1, &info1); + ProfileCompilationInfo info2; + SetupProfile("p1", 2, kNumberOfMethodsToEnableCompilation, profile2, &info2); + + // We should fail processing. + ProfileCompilationInfo* result; + ASSERT_FALSE(ProfileAssistant::ProcessProfiles(profile_fds, reference_profile_fds, &result)); + ASSERT_TRUE(result == nullptr); + + // The information from profiles must still remain the same. + ProfileCompilationInfo file_info1; + ASSERT_TRUE(profile1.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info1.Load(GetFd(profile1))); + ASSERT_TRUE(file_info1.Equals(info1)); + + ProfileCompilationInfo file_info2; + ASSERT_TRUE(profile2.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info2.Load(GetFd(profile2))); + ASSERT_TRUE(file_info2.Equals(info2)); + + // Reference profile files must still remain empty. + ASSERT_EQ(0, reference_profile1.GetFile()->GetLength()); + ASSERT_EQ(0, reference_profile2.GetFile()->GetLength()); +} + +TEST_F(ProfileAssistantTest, FailProcessingBecauseOfReferenceProfiles) { + ScratchFile profile1; + ScratchFile reference_profile; + + std::vector<uint32_t> profile_fds({ + GetFd(profile1)}); + std::vector<uint32_t> reference_profile_fds({ + GetFd(reference_profile)}); + + const uint16_t kNumberOfMethodsToEnableCompilation = 100; + // Assign different hashes for the same dex file. This will make merging of information to fail. + ProfileCompilationInfo info1; + SetupProfile("p1", 1, kNumberOfMethodsToEnableCompilation, profile1, &info1); + ProfileCompilationInfo reference_info; + SetupProfile("p1", 2, kNumberOfMethodsToEnableCompilation, reference_profile, &reference_info); + + // We should not advise compilation. + ProfileCompilationInfo* result; + ASSERT_TRUE(profile1.GetFile()->ResetOffset()); + ASSERT_TRUE(reference_profile.GetFile()->ResetOffset()); + ASSERT_FALSE(ProfileAssistant::ProcessProfiles(profile_fds, reference_profile_fds, &result)); + ASSERT_TRUE(result == nullptr); + + // The information from profiles must still remain the same. + ProfileCompilationInfo file_info1; + ASSERT_TRUE(profile1.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info1.Load(GetFd(profile1))); + ASSERT_TRUE(file_info1.Equals(info1)); + + ProfileCompilationInfo file_info2; + ASSERT_TRUE(reference_profile.GetFile()->ResetOffset()); + ASSERT_TRUE(file_info2.Load(GetFd(reference_profile))); + ASSERT_TRUE(file_info2.Equals(reference_info)); +} + +} // namespace art diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 32a237a126..178d606fd4 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -341,6 +341,12 @@ NO_RETURN static void Usage(const char* fmt, ...) { UsageError(" --profile-file will be merged into --reference-profile-file. Valid only when"); UsageError(" specified together with --profile-file."); UsageError(""); + UsageError(" --profile-file-fd=<number>: same as --profile-file but accepts a file descriptor."); + UsageError(" Cannot be used together with --profile-file."); + UsageError(""); + UsageError(" --reference-profile-file-fd=<number>: same as --reference-profile-file but"); + UsageError(" accepts a file descriptor. Cannot be used together with"); + UsageError(" --reference-profile-file."); UsageError(" --print-pass-names: print a list of pass names"); UsageError(""); UsageError(" --disable-passes=<pass-names>: disable one or more passes separated by comma."); @@ -498,6 +504,14 @@ static bool UseSwap(bool is_image, std::vector<const DexFile*>& dex_files) { return dex_files_size >= kMinDexFileCumulativeSizeForSwap; } +static void CloseAllFds(const std::vector<uint32_t>& fds, const char* descriptor) { + for (size_t i = 0; i < fds.size(); i++) { + if (close(fds[i]) < 0) { + PLOG(WARNING) << "Failed to close descriptor for " << descriptor << " at index " << i; + } + } +} + class Dex2Oat FINAL { public: explicit Dex2Oat(TimingLogger* timings) : @@ -576,6 +590,14 @@ class Dex2Oat FINAL { ParseUintOption(option, "--oat-fd", &oat_fd_, Usage); } + void ParseFdForCollection(const StringPiece& option, + const char* arg_name, + std::vector<uint32_t>* fds) { + uint32_t fd; + ParseUintOption(option, arg_name, &fd, Usage); + fds->push_back(fd); + } + void ParseJ(const StringPiece& option) { ParseUintOption(option, "-j", &thread_count_, Usage, /* is_long_option */ false); } @@ -779,11 +801,25 @@ class Dex2Oat FINAL { } } + if (!profile_files_.empty() && !profile_files_fd_.empty()) { + Usage("Profile files should not be specified with both --profile-file-fd and --profile-file"); + } if (!profile_files_.empty()) { if (!reference_profile_files_.empty() && (reference_profile_files_.size() != profile_files_.size())) { Usage("If specified, --reference-profile-file should match the number of --profile-file."); } + } else if (!reference_profile_files_.empty()) { + Usage("--reference-profile-file should only be supplied with --profile-file"); + } + if (!profile_files_fd_.empty()) { + if (!reference_profile_files_fd_.empty() && + (reference_profile_files_fd_.size() != profile_files_fd_.size())) { + Usage("If specified, --reference-profile-file-fd should match the number", + " of --profile-file-fd."); + } + } else if (!reference_profile_files_fd_.empty()) { + Usage("--reference-profile-file-fd should only be supplied with --profile-file-fd"); } if (!parser_options->oat_symbols.empty()) { @@ -1080,6 +1116,10 @@ class Dex2Oat FINAL { } else if (option.starts_with("--reference-profile-file=")) { reference_profile_files_.push_back( option.substr(strlen("--reference-profile-file=")).ToString()); + } else if (option.starts_with("--profile-file-fd=")) { + ParseFdForCollection(option, "--profile-file-fd", &profile_files_fd_); + } else if (option.starts_with("--reference-profile-file-fd=")) { + ParseFdForCollection(option, "--reference_profile-file-fd", &reference_profile_files_fd_); } else if (option == "--no-profile-file") { // No profile } else if (option == "--host") { @@ -1810,17 +1850,27 @@ class Dex2Oat FINAL { } bool UseProfileGuidedCompilation() const { - return !profile_files_.empty(); + return !profile_files_.empty() || !profile_files_fd_.empty(); } bool ProcessProfiles() { DCHECK(UseProfileGuidedCompilation()); ProfileCompilationInfo* info = nullptr; - if (ProfileAssistant::ProcessProfiles(profile_files_, reference_profile_files_, &info)) { - profile_compilation_info_.reset(info); - return true; + bool result = false; + if (profile_files_.empty()) { + DCHECK(!profile_files_fd_.empty()); + result = ProfileAssistant::ProcessProfiles( + profile_files_fd_, reference_profile_files_fd_, &info); + CloseAllFds(profile_files_fd_, "profile_files_fd_"); + CloseAllFds(reference_profile_files_fd_, "reference_profile_files_fd_"); + } else { + result = ProfileAssistant::ProcessProfiles( + profile_files_, reference_profile_files_, &info); } - return false; + + profile_compilation_info_.reset(info); + + return result; } bool ShouldCompileBasedOnProfiles() const { @@ -2304,6 +2354,8 @@ class Dex2Oat FINAL { int app_image_fd_; std::vector<std::string> profile_files_; std::vector<std::string> reference_profile_files_; + std::vector<uint32_t> profile_files_fd_; + std::vector<uint32_t> reference_profile_files_fd_; std::unique_ptr<ProfileCompilationInfo> profile_compilation_info_; TimingLogger* timings_; std::unique_ptr<CumulativeLogger> compiler_phases_timings_; diff --git a/runtime/base/scoped_flock.cc b/runtime/base/scoped_flock.cc index 71e0590272..814cbd093a 100644 --- a/runtime/base/scoped_flock.cc +++ b/runtime/base/scoped_flock.cc @@ -26,16 +26,25 @@ namespace art { bool ScopedFlock::Init(const char* filename, std::string* error_msg) { + return Init(filename, O_CREAT | O_RDWR, true, error_msg); +} + +bool ScopedFlock::Init(const char* filename, int flags, bool block, std::string* error_msg) { while (true) { if (file_.get() != nullptr) { UNUSED(file_->FlushCloseOrErase()); // Ignore result. } - file_.reset(OS::OpenFileWithFlags(filename, O_CREAT | O_RDWR)); + file_.reset(OS::OpenFileWithFlags(filename, flags)); if (file_.get() == nullptr) { *error_msg = StringPrintf("Failed to open file '%s': %s", filename, strerror(errno)); return false; } - int flock_result = TEMP_FAILURE_RETRY(flock(file_->Fd(), LOCK_EX)); + int operation = block ? LOCK_EX : (LOCK_EX | LOCK_NB); + int flock_result = TEMP_FAILURE_RETRY(flock(file_->Fd(), operation)); + if (flock_result == EWOULDBLOCK) { + // File is locked by someone else and we are required not to block; + return false; + } if (flock_result != 0) { *error_msg = StringPrintf("Failed to lock file '%s': %s", filename, strerror(errno)); return false; @@ -51,11 +60,23 @@ bool ScopedFlock::Init(const char* filename, std::string* error_msg) { if (stat_result != 0) { PLOG(WARNING) << "Failed to stat, will retry: " << filename; // ENOENT can happen if someone racing with us unlinks the file we created so just retry. - continue; + if (block) { + continue; + } else { + // Note that in theory we could race with someone here for a long time and end up retrying + // over and over again. This potential behavior does not fit well in the non-blocking + // semantics. Thus, if we are not require to block return failure when racing. + return false; + } } if (fstat_stat.st_dev != stat_stat.st_dev || fstat_stat.st_ino != stat_stat.st_ino) { LOG(WARNING) << "File changed while locking, will retry: " << filename; - continue; + if (block) { + continue; + } else { + // See comment above. + return false; + } } return true; } @@ -78,7 +99,7 @@ bool ScopedFlock::Init(File* file, std::string* error_msg) { return true; } -File* ScopedFlock::GetFile() { +File* ScopedFlock::GetFile() const { CHECK(file_.get() != nullptr); return file_.get(); } diff --git a/runtime/base/scoped_flock.h b/runtime/base/scoped_flock.h index 08612e3016..cc22056443 100644 --- a/runtime/base/scoped_flock.h +++ b/runtime/base/scoped_flock.h @@ -32,10 +32,15 @@ class ScopedFlock { // Attempts to acquire an exclusive file lock (see flock(2)) on the file // at filename, and blocks until it can do so. // - // Returns true if the lock could be acquired, or false if an error - // occurred. It is an error if the file does not exist, or if its inode - // changed (usually due to a new file being created at the same path) - // between attempts to lock it. + // Returns true if the lock could be acquired, or false if an error occurred. + // It is an error if its inode changed (usually due to a new file being + // created at the same path) between attempts to lock it. In blocking mode, + // locking will be retried if the file changed. In non-blocking mode, false + // is returned and no attempt is made to re-acquire the lock. + // + // The file is opened with the provided flags. + bool Init(const char* filename, int flags, bool block, std::string* error_msg); + // Calls Init(filename, O_CREAT | O_RDWR, true, errror_msg) bool Init(const char* filename, std::string* error_msg); // Attempt to acquire an exclusive file lock (see flock(2)) on 'file'. // Returns true if the lock could be acquired or false if an error @@ -43,7 +48,7 @@ class ScopedFlock { bool Init(File* file, std::string* error_msg); // Returns the (locked) file associated with this instance. - File* GetFile(); + File* GetFile() const; // Returns whether a file is held. bool HasFile(); diff --git a/runtime/base/unix_file/fd_file.cc b/runtime/base/unix_file/fd_file.cc index 78bc3d5f9f..e17bebb4fb 100644 --- a/runtime/base/unix_file/fd_file.cc +++ b/runtime/base/unix_file/fd_file.cc @@ -316,4 +316,21 @@ void FdFile::MarkUnchecked() { guard_state_ = GuardState::kNoCheck; } +bool FdFile::ClearContent() { + if (SetLength(0) < 0) { + PLOG(art::ERROR) << "Failed to reset the length"; + return false; + } + return ResetOffset(); +} + +bool FdFile::ResetOffset() { + off_t rc = TEMP_FAILURE_RETRY(lseek(fd_, 0, SEEK_SET)); + if (rc == static_cast<off_t>(-1)) { + PLOG(art::ERROR) << "Failed to reset the offset"; + return false; + } + return true; +} + } // namespace unix_file diff --git a/runtime/base/unix_file/fd_file.h b/runtime/base/unix_file/fd_file.h index 231a1ab145..1e2d8af151 100644 --- a/runtime/base/unix_file/fd_file.h +++ b/runtime/base/unix_file/fd_file.h @@ -79,6 +79,11 @@ class FdFile : public RandomAccessFile { // Copy data from another file. bool Copy(FdFile* input_file, int64_t offset, int64_t size); + // Clears the file content and resets the file offset to 0. + // Returns true upon success, false otherwise. + bool ClearContent(); + // Resets the file offset to the beginning of the file. + bool ResetOffset(); // This enum is public so that we can define the << operator over it. enum class GuardState { diff --git a/runtime/jit/offline_profiling_info.cc b/runtime/jit/offline_profiling_info.cc index a132701796..b4b872ff50 100644 --- a/runtime/jit/offline_profiling_info.cc +++ b/runtime/jit/offline_profiling_info.cc @@ -24,8 +24,11 @@ #include "art_method-inl.h" #include "base/mutex.h" +#include "base/scoped_flock.h" #include "base/stl_util.h" +#include "base/unix_file/fd_file.h" #include "jit/profiling_info.h" +#include "os.h" #include "safe_map.h" namespace art { @@ -37,8 +40,17 @@ bool ProfileCompilationInfo::SaveProfilingInfo(const std::string& filename, return true; } + ScopedFlock flock; + std::string error; + if (!flock.Init(filename.c_str(), O_RDWR | O_NOFOLLOW | O_CLOEXEC, /* block */ false, &error)) { + LOG(WARNING) << "Couldn't lock the profile file " << filename << ": " << error; + return false; + } + + int fd = flock.GetFile()->Fd(); + ProfileCompilationInfo info; - if (!info.Load(filename)) { + if (!info.Load(fd)) { LOG(WARNING) << "Could not load previous profile data from file " << filename; return false; } @@ -54,9 +66,14 @@ bool ProfileCompilationInfo::SaveProfilingInfo(const std::string& filename, } } + if (!flock.GetFile()->ClearContent()) { + PLOG(WARNING) << "Could not clear profile file: " << filename; + return false; + } + // This doesn't need locking because we are trying to lock the file for exclusive // access and fail immediately if we can't. - bool result = info.Save(filename); + bool result = info.Save(fd); if (result) { VLOG(profiler) << "Successfully saved profile info to " << filename << " Size: " << GetFileSizeBytes(filename); @@ -66,64 +83,20 @@ bool ProfileCompilationInfo::SaveProfilingInfo(const std::string& filename, return result; } -enum OpenMode { - READ, - READ_WRITE -}; - -static int OpenFile(const std::string& filename, OpenMode open_mode) { - int fd = -1; - switch (open_mode) { - case READ: - fd = open(filename.c_str(), O_RDONLY); - break; - case READ_WRITE: - // TODO(calin) allow the shared uid of the app to access the file. - fd = open(filename.c_str(), O_WRONLY | O_TRUNC | O_NOFOLLOW | O_CLOEXEC); - break; - } - - if (fd < 0) { - PLOG(WARNING) << "Failed to open profile file " << filename; - return -1; - } - - // Lock the file for exclusive access but don't wait if we can't lock it. - int err = flock(fd, LOCK_EX | LOCK_NB); - if (err < 0) { - PLOG(WARNING) << "Failed to lock profile file " << filename; - return -1; - } - return fd; -} - -static bool CloseDescriptorForFile(int fd, const std::string& filename) { - // Now unlock the file, allowing another process in. - int err = flock(fd, LOCK_UN); - if (err < 0) { - PLOG(WARNING) << "Failed to unlock profile file " << filename; - return false; - } - - // Done, close the file. - err = ::close(fd); - if (err < 0) { - PLOG(WARNING) << "Failed to close descriptor for profile file" << filename; - return false; - } - - return true; -} - -static void WriteToFile(int fd, const std::ostringstream& os) { +static bool WriteToFile(int fd, const std::ostringstream& os) { std::string data(os.str()); const char *p = data.c_str(); size_t length = data.length(); do { - int n = ::write(fd, p, length); + int n = TEMP_FAILURE_RETRY(write(fd, p, length)); + if (n < 0) { + PLOG(WARNING) << "Failed to write to descriptor: " << fd; + return false; + } p += n; length -= n; } while (length > 0); + return true; } static constexpr const char kFieldSeparator = ','; @@ -137,13 +110,8 @@ static constexpr const char kLineSeparator = '\n'; * /system/priv-app/app/app.apk,131232145,11,23,454,54 * /system/priv-app/app/app.apk:classes5.dex,218490184,39,13,49,1 **/ -bool ProfileCompilationInfo::Save(const std::string& filename) { - int fd = OpenFile(filename, READ_WRITE); - if (fd == -1) { - return false; - } - - // TODO(calin): Merge with a previous existing profile. +bool ProfileCompilationInfo::Save(uint32_t fd) { + DCHECK_GE(fd, 0u); // TODO(calin): Profile this and see how much memory it takes. If too much, // write to file directly. std::ostringstream os; @@ -158,9 +126,7 @@ bool ProfileCompilationInfo::Save(const std::string& filename) { os << kLineSeparator; } - WriteToFile(fd, os); - - return CloseDescriptorForFile(fd, filename); + return WriteToFile(fd, os); } // TODO(calin): This a duplicate of Utils::Split fixing the case where the first character @@ -222,7 +188,9 @@ bool ProfileCompilationInfo::ProcessLine(const std::string& line) { LOG(WARNING) << "Cannot parse method_idx " << parts[i]; return false; } - AddData(dex_location, checksum, method_idx); + if (!AddData(dex_location, checksum, method_idx)) { + return false; + } } return true; } @@ -249,23 +217,18 @@ static int GetLineFromBuffer(char* buffer, int n, int start_from, std::string& l return new_line_pos == -1 ? new_line_pos : new_line_pos + 1; } -bool ProfileCompilationInfo::Load(const std::string& filename) { - int fd = OpenFile(filename, READ); - if (fd == -1) { - return false; - } +bool ProfileCompilationInfo::Load(uint32_t fd) { + DCHECK_GE(fd, 0u); std::string current_line; const int kBufferSize = 1024; char buffer[kBufferSize]; - bool success = true; - while (success) { - int n = read(fd, buffer, kBufferSize); + while (true) { + int n = TEMP_FAILURE_RETRY(read(fd, buffer, kBufferSize)); if (n < 0) { - PLOG(WARNING) << "Error when reading profile file " << filename; - success = false; - break; + PLOG(WARNING) << "Error when reading profile file"; + return false; } else if (n == 0) { break; } @@ -278,17 +241,13 @@ bool ProfileCompilationInfo::Load(const std::string& filename) { break; } if (!ProcessLine(current_line)) { - success = false; - break; + return false; } // Reset the current line (we just processed it). current_line.clear(); } } - if (!success) { - info_.clear(); - } - return CloseDescriptorForFile(fd, filename) && success; + return true; } bool ProfileCompilationInfo::Load(const ProfileCompilationInfo& other) { @@ -369,4 +328,8 @@ std::string ProfileCompilationInfo::DumpInfo(const std::vector<const DexFile*>* return os.str(); } +bool ProfileCompilationInfo::Equals(ProfileCompilationInfo& other) { + return info_.Equals(other.info_); +} + } // namespace art diff --git a/runtime/jit/offline_profiling_info.h b/runtime/jit/offline_profiling_info.h index 26e1ac385f..ffd14335d7 100644 --- a/runtime/jit/offline_profiling_info.h +++ b/runtime/jit/offline_profiling_info.h @@ -39,15 +39,18 @@ class ArtMethod; */ class ProfileCompilationInfo { public: + // Saves profile information about the given methods in the given file. + // Note that the saving proceeds only if the file can be locked for exclusive access. + // If not (the locking is not blocking), the function does not save and returns false. static bool SaveProfilingInfo(const std::string& filename, const std::vector<ArtMethod*>& methods); - // Loads profile information from the given file. - bool Load(const std::string& profile_filename); + // Loads profile information from the given file descriptor. + bool Load(uint32_t fd); // Loads the data from another ProfileCompilationInfo object. bool Load(const ProfileCompilationInfo& info); - // Saves the profile data to the given file. - bool Save(const std::string& profile_filename); + // Saves the profile data to the given file descriptor. + bool Save(uint32_t fd); // Returns the number of methods that were profiled. uint32_t GetNumberOfMethods() const; @@ -61,6 +64,9 @@ class ProfileCompilationInfo { std::string DumpInfo(const std::vector<const DexFile*>* dex_files, bool print_full_dex_location = true) const; + // For testing purposes. + bool Equals(ProfileCompilationInfo& other); + private: bool AddData(const std::string& dex_location, uint32_t checksum, uint16_t method_idx); bool ProcessLine(const std::string& line); @@ -69,10 +75,18 @@ class ProfileCompilationInfo { explicit DexFileData(uint32_t location_checksum) : checksum(location_checksum) {} uint32_t checksum; std::set<uint16_t> method_set; + + bool operator==(const DexFileData& other) const { + return checksum == other.checksum && method_set == other.method_set; + } }; using DexFileToProfileInfoMap = SafeMap<const std::string, DexFileData>; + friend class ProfileCompilationInfoTest; + friend class CompilerDriverProfileTest; + friend class ProfileAssistantTest; + DexFileToProfileInfoMap info_; }; diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc new file mode 100644 index 0000000000..482ea06395 --- /dev/null +++ b/runtime/jit/profile_compilation_info_test.cc @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2016 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 <gtest/gtest.h> + +#include "base/unix_file/fd_file.h" +#include "art_method-inl.h" +#include "class_linker-inl.h" +#include "common_runtime_test.h" +#include "dex_file.h" +#include "mirror/class-inl.h" +#include "mirror/class_loader.h" +#include "handle_scope-inl.h" +#include "jit/offline_profiling_info.h" +#include "scoped_thread_state_change.h" + +namespace art { + +class ProfileCompilationInfoTest : public CommonRuntimeTest { + protected: + std::vector<ArtMethod*> GetVirtualMethods(jobject class_loader, + const std::string& clazz) { + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + StackHandleScope<1> hs(self); + Handle<mirror::ClassLoader> h_loader(hs.NewHandle( + reinterpret_cast<mirror::ClassLoader*>(self->DecodeJObject(class_loader)))); + mirror::Class* klass = class_linker->FindClass(self, clazz.c_str(), h_loader); + + const auto pointer_size = class_linker->GetImagePointerSize(); + std::vector<ArtMethod*> methods; + for (auto& m : klass->GetVirtualMethods(pointer_size)) { + methods.push_back(&m); + } + return methods; + } + + bool AddData(const std::string& dex_location, + uint32_t checksum, + uint16_t method_index, + ProfileCompilationInfo* info) { + return info->AddData(dex_location, checksum, method_index); + } + + uint32_t GetFd(const ScratchFile& file) { + return static_cast<uint32_t>(file.GetFd()); + } +}; + +TEST_F(ProfileCompilationInfoTest, SaveArtMethods) { + ScratchFile profile; + + Thread* self = Thread::Current(); + jobject class_loader; + { + ScopedObjectAccess soa(self); + class_loader = LoadDex("ProfileTestMultiDex"); + } + ASSERT_NE(class_loader, nullptr); + + // Save virtual methods from Main. + std::vector<ArtMethod*> main_methods = GetVirtualMethods(class_loader, "LMain;"); + ASSERT_TRUE(ProfileCompilationInfo::SaveProfilingInfo(profile.GetFilename(), main_methods)); + + // Check that what we saved is in the profile. + ProfileCompilationInfo info1; + ASSERT_TRUE(info1.Load(GetFd(profile))); + ASSERT_EQ(info1.GetNumberOfMethods(), main_methods.size()); + { + ScopedObjectAccess soa(self); + for (ArtMethod* m : main_methods) { + ASSERT_TRUE(info1.ContainsMethod(MethodReference(m->GetDexFile(), m->GetDexMethodIndex()))); + } + } + + // Save virtual methods from Second. + std::vector<ArtMethod*> second_methods = GetVirtualMethods(class_loader, "LSecond;"); + ASSERT_TRUE(ProfileCompilationInfo::SaveProfilingInfo(profile.GetFilename(), second_methods)); + + // Check that what we saved is in the profile (methods form Main and Second). + ProfileCompilationInfo info2; + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + ASSERT_TRUE(info2.Load(GetFd(profile))); + ASSERT_EQ(info2.GetNumberOfMethods(), main_methods.size() + second_methods.size()); + { + ScopedObjectAccess soa(self); + for (ArtMethod* m : main_methods) { + ASSERT_TRUE(info2.ContainsMethod(MethodReference(m->GetDexFile(), m->GetDexMethodIndex()))); + } + for (ArtMethod* m : second_methods) { + ASSERT_TRUE(info2.ContainsMethod(MethodReference(m->GetDexFile(), m->GetDexMethodIndex()))); + } + } +} + +TEST_F(ProfileCompilationInfoTest, SaveFd) { + ScratchFile profile; + + ProfileCompilationInfo saved_info; + // Save a few methods. + for (uint16_t i = 0; i < 10; i++) { + ASSERT_TRUE(AddData("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info)); + ASSERT_TRUE(AddData("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info)); + } + ASSERT_TRUE(saved_info.Save(GetFd(profile))); + ASSERT_EQ(0, profile.GetFile()->Flush()); + + // Check that we get back what we saved. + ProfileCompilationInfo loaded_info; + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + ASSERT_TRUE(loaded_info.Load(GetFd(profile))); + ASSERT_TRUE(loaded_info.Equals(saved_info)); + + // Save more methods. + for (uint16_t i = 0; i < 100; i++) { + ASSERT_TRUE(AddData("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info)); + ASSERT_TRUE(AddData("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info)); + ASSERT_TRUE(AddData("dex_location3", /* checksum */ 3, /* method_idx */ i, &saved_info)); + } + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + ASSERT_TRUE(saved_info.Save(GetFd(profile))); + ASSERT_EQ(0, profile.GetFile()->Flush()); + + // Check that we get back everything we saved. + ProfileCompilationInfo loaded_info2; + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + ASSERT_TRUE(loaded_info2.Load(GetFd(profile))); + ASSERT_TRUE(loaded_info2.Equals(saved_info)); +} + +TEST_F(ProfileCompilationInfoTest, AddDataFail) { + ScratchFile profile; + + ProfileCompilationInfo info; + ASSERT_TRUE(AddData("dex_location", /* checksum */ 1, /* method_idx */ 1, &info)); + // Trying to add info for an existing file but with a different checksum. + ASSERT_FALSE(AddData("dex_location", /* checksum */ 2, /* method_idx */ 2, &info)); +} + +TEST_F(ProfileCompilationInfoTest, LoadFail) { + ScratchFile profile; + + ProfileCompilationInfo info1; + ASSERT_TRUE(AddData("dex_location", /* checksum */ 1, /* method_idx */ 1, &info1)); + // Use the same file, change the checksum. + ProfileCompilationInfo info2; + ASSERT_TRUE(AddData("dex_location", /* checksum */ 2, /* method_idx */ 2, &info2)); + + ASSERT_FALSE(info1.Load(info2)); +} + +} // namespace art diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc index ec289ea2b5..fc257c0441 100644 --- a/runtime/jit/profile_saver.cc +++ b/runtime/jit/profile_saver.cc @@ -86,12 +86,14 @@ void ProfileSaver::Run() { } bool ProfileSaver::ProcessProfilingInfo() { - VLOG(profiler) << "Initiating save profiling information to: " << output_filename_; + VLOG(profiler) << "Save profiling information to: " << output_filename_; uint64_t last_update_time_ns = jit_code_cache_->GetLastUpdateTimeNs(); if (last_update_time_ns - code_cache_last_update_time_ns_ - > kMinimumTimeBetweenCodeCacheUpdatesNs) { - VLOG(profiler) << "Not enough time has passed since the last code cache update."; + < kMinimumTimeBetweenCodeCacheUpdatesNs) { + VLOG(profiler) << "Not enough time has passed since the last code cache update." + << "Last update: " << last_update_time_ns + << " Last save: " << code_cache_last_update_time_ns_; return false; } diff --git a/test/ProfileTestMultiDex/Main.java b/test/ProfileTestMultiDex/Main.java new file mode 100644 index 0000000000..41532ea8f7 --- /dev/null +++ b/test/ProfileTestMultiDex/Main.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2016 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. + */ + +class Main { + public String getA() { + return "A"; + } + public String getB() { + return "B"; + } + public String getC() { + return "C"; + } +} diff --git a/test/ProfileTestMultiDex/Second.java b/test/ProfileTestMultiDex/Second.java new file mode 100644 index 0000000000..4ac5abc300 --- /dev/null +++ b/test/ProfileTestMultiDex/Second.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2016 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. + */ + +class Second { + public String getX() { + return "X"; + } + public String getY() { + return "Y"; + } + public String getZ() { + return "Z"; + } +} diff --git a/test/ProfileTestMultiDex/main.jpp b/test/ProfileTestMultiDex/main.jpp new file mode 100644 index 0000000000..f2e3b4e14c --- /dev/null +++ b/test/ProfileTestMultiDex/main.jpp @@ -0,0 +1,3 @@ +main: + @@com.android.jack.annotations.ForceInMainDex + class Second diff --git a/test/ProfileTestMultiDex/main.list b/test/ProfileTestMultiDex/main.list new file mode 100644 index 0000000000..44ba78ead5 --- /dev/null +++ b/test/ProfileTestMultiDex/main.list @@ -0,0 +1 @@ +Main.class |